The gRPC server can be extended by middlewares. Middleware hooks are called at the various corresponding stages of handling of each incoming RPC. Different middlewares handle the call in the defined order. A middleware may decide to reject the call or call the next middleware in the stack. Middlewares may implement almost any enhancement to the gRPC server including authorization and authentication, ratelimiting, logging, tracing, audit, etc.
If you add these middlewares to the components::ComponentList, these middlewares will be enabled by default. To register core gRPC server components and a set of builtin middlewares use ugrpc::server::DefaultComponentList or ugrpc::server::MinimalComponentList. As will be shown below, custom middlewares require additional actions to work: registering in grpc-server-middleware-pipeline and writing a required static config entry.
OnCallStart is called after the client metadata is received. OnCallFinish is called before the last message is sent or before error status is sent to a client.
OnCallStart hooks are called in the order of middlewares. OnCallFinish hooks are called in the reverse order of middlewares
Per-Call (RPC) hooks implementation example
class Middleware final : public ugrpc::server::MiddlewareBase {
public:
// Name of a middleware-factory that creates this middleware.
If you throw an exception in middlewares hooks, that exception will be translated to grpc::Status (by default grpc::StatusCode::UNKNOWN) and next hooks won't be called. server::handlers::CustomHandlerException is translated to a relevant grpc::Status.
All errors will be logged just like an exception or error status from the user handler:
The actual status is passed to OnCallFinish hooks. Each OnCallFinish hook gets the status from a previous OnCallFinish call and can change that by SetError (or exception). An error status from a handler will be passed to a first OnCallFinish and that hook can change that status, next hooks will get the new status. If all OnCallFinish hooks don't change the status, that status will be the final status for a client.
The call path example with errors in the pipeline
There are 3 middlewares A, B, C. Cases:
A::OnCallStart and B::OnCallStart succeed, but C::OnCallStart fails (by SetError or exception) ⇒ B::OnCallFinish and A::OnCallFinish will be called (remember that OnCallFinish order is reversed).
If all OnCallStart succeed and C::OnCallFinish fails, B::OnCallStart and A::OnCallStart will be called and these hooks get an error status from C::OnCallFinish.
If a handler returns an error, all OnCallFinish will be called.
If there are errors in PostRecvMessage/PreSendMessage ⇒ RPC is failed ⇒ all OnCallFinish hooks will be called.
Using static config options in middlewares
There are two ways to implement a middleware component. You can see above ugrpc::server::SimpleMiddlewareFactoryComponent. This component is needed for simple cases without static config options of a middleware.
There are simple cases above: we just set Auth group for one middleware and use a default constructor of MiddlewareDependencyBuilder in other middleware. Here we say that all server middlewares are located in these groups.
PreCore group is called firstly, then Logging and so forth...