userver: gRPC middlewares order
Loading...
Searching...
No Matches
gRPC middlewares order

How to implement server and client middlewares:

  1. gRPC server middlewares
  2. gRPC client middlewares

Middlewares pipeline

There is a main component that manages a middlewares pipeline.

  1. ugrpc::server::MiddlewarePipelineComponent for the server side
  2. ugrpc::client::MiddlewarePipelineComponent for the client side

These components allow you to globally enable/disable middlewares. There isn't one explicit global ordered list of middlewares. That approach is not flexible and not scalable. Instead, each middleware can provide an information of position relative to other middlewares. Each middleware sets some dependency order relative to other middlewares using middlewares::MiddlewareDependencyBuilder. Then Pipeline collects that set of dependencies between middlewares and builds DAG (directed acyclic graph). Then Pipeline builds an ordered middlewares list from that DAG.

You can enable/disable middleware per service or per client. For more information see Enable/disable middlewares.

Middlewares groups

Each middleware belongs to some group. You need to start defining the order of middleware from a group of that middleware.

Groups:

  1. middlewares::groups::PreCore
  2. middlewares::groups::Logging
  3. middlewares::groups::Auth
  4. middlewares::groups::Core
  5. middlewares::groups::PostCore
  6. middlewares::groups::User

The pipeline calls middlewares in that order. PreCore group is called first, then Logging and so forth. User group is called the last one. User group is the default group for all middlewares.

There are Post-hooks and Pre-hooks for each middleware interface. Post-hooks are always called in the reverse order. For more information see special implementation of server or client middlewares:

  1. grpc_server_hooks
  2. grpc_client_hooks

Defining dependencies of middleware

If your middleware doesn't care about an order, your middleware will be in the User group by default.

MiddlewareComponent::MiddlewareComponent(const components::ComponentConfig& config, const components::ComponentContext& context)
: ugrpc::server::MiddlewareFactoryComponentBase(config, context) {} // OK.

To specify middleware group and dependencies, you should use middlewares::MiddlewareDependencyBuilder.

InGroup

Each middleware is in the middlewares::groups::User group by default.

If your middleware component is inherited from ugrpc::{client,server}::MiddlewareFactoryComponentBase, you can choose some group for that middleware and pass it in the MiddlewareFactoryComponentBase constructor:

Component::Component(const components::ComponentConfig& config, const components::ComponentContext& context)
config,
context,
USERVER_NAMESPACE::middlewares::MiddlewareDependencyBuilder()
.InGroup<USERVER_NAMESPACE::middlewares::groups::Logging>()
) {}

If your middleware component is a short-cut ugrpc::{server, client}::SimpleMiddlewareFactoryComponent, you can choose some group with kDependency field:

class AuthMiddleware final : public ugrpc::client::MiddlewareBase {
public:
// Name of a middleware-factory that creates this middleware.
static constexpr std::string_view kName = "grpc-auth-client";
// 'Auth' is a group for authentication. See middlewares groups for more information.
static inline const auto kDependency =
middlewares::MiddlewareDependencyBuilder().InGroup<middlewares::groups::Auth>();
AuthMiddleware();
~AuthMiddleware() override;
void PreStartCall(ugrpc::client::MiddlewareCallContext& context) const override;
};
// This component creates Middleware. Name of the component is 'Middleware::kName'.
// In this case we use a short-cut for defining a middleware-factory, but you can create your own factory by
// inheritance from 'ugrpc::client::MiddlewareFactoryComponentBase'

Before/After

MetaFilterComponent::MetaFilterComponent(
)
config,
context,
middlewares::MiddlewareDependencyBuilder()
.InGroup<middlewares::groups::User>()
.After<ugrpc::server::middlewares::headers_propagator::Component>()
) {}
Warning
If your dependencies create a cycle in the middleware graph, there will be a fatal error in the start ⇒ be careful.

Now the middleware of MetaFilterComponent will be ordered after ugrpc::server::middlewares::headers_propagator::Component. You can create dependencies between middlewares only if they are in the same group, otherwise there will be an exception on the start of the service.

Also, you can use the method middlewares::MiddlewareDependencyBuilder::Before. You can use methods Before/After together and effect will be accumulated. E.g. A.Before<B>().After<C>() ⇒ order from left to right: C, A, B.

Note
Middleware are independent of each other, are lexicographically ordered by component names.

E.g. there are one independent middleware and two chains: A.After<Z> and C.After<B>. Final order is B, Z, A, C, F.

Weak dependency

What happens if some MiddlewareA requires After<MiddlewareB>() and MiddlewareB is disabled?

If the After method was called without arguments, there will be a fatal error at the start of the service.

If you want to skip this dependency in that case, just pass it middlewares::DependencyType::kWeak in After.

MiddlewareComponent::MiddlewareComponent(const components::ComponentConfig& config, const components::ComponentContext& context)
: ugrpc::server::MiddlewareFactoryComponentBase(
config,
context,
middlewares::MiddlewareDependencyBuilder()
.After<OtherMiddlewareComponent>(middlewares::DependencyType::kWeak)
) {}