Two main classes
There are two main interfaces for implementing a middleware:
ugrpc::client::MiddlewareBase . Class that implements the main logic of a middleware.
ugrpc::client::SimpleMiddlewareFactoryComponent short-cut for simple cases without static options.
MiddlewareBase
ugrpc::client::MiddlewareBase
PreStartCall and PostFinish
Methods ugrpc::client::MiddlewareBase::PreStartCall and ugrpc::client::MiddlewareBase::PostFinish are called once per grpc Call (RPC).
PreStartCall
is called before the first message is sent. PostFinish
is called after the last message is received or after an error status is received from the downstream service.
PreStartCall
hooks are called in the direct middlewares order. PostFinish
hooks are called in the reversed order.
Streaming RPCs can have multiple requests and responses, but PreStartCall
and PostFinish
are called once per RPC in any case.
For more information about the middlewares order:
See also gRPC middlewares order .
Per-call (RPC) hooks implementation example
public :
static constexpr std::string_view kName = "grpc-auth-client" ;
static inline const auto kDependency =
AuthMiddleware();
~AuthMiddleware() override ;
};
void ApplyCredentials(::grpc::ClientContext& context) { context.AddMetadata(kKey, kCredentials); }
AuthMiddleware::AuthMiddleware() = default ;
AuthMiddleware::~AuthMiddleware() = default ;
}
Register the Middleware component in the component system.
int main(int argc, char * argv[]) {
.Append<components::TestsuiteSupport>()
.Append<samples::grpc::auth::GreeterClient>()
.Append<samples::grpc::auth::GreeterServiceComponent>()
.Append<samples::grpc::auth::GreeterHttpHandler>()
.Append<samples::grpc::auth::server::AuthComponent>()
.Append<samples::grpc::auth::server::MetaFilterComponent>()
.Append<samples::grpc::auth::client::AuthComponent>()
.Append<samples::grpc::auth::client::ChaosComponent>();
}
The static YAML config.
grpc-auth-client:
grpc-client-middlewares-pipeline:
middlewares:
grpc-auth-client:
enabled: true # register the middleware in the pipeline
PreSendMessage and PostRecvMessage
PreSendMessage
hooks are called in the order of middlewares. PostRecvMessage
hooks are called in the reverse order of middlewares.
For more information about the middlewares order:
See also gRPC middlewares order .
These hooks are called on each message.
PreSendMessage
:
unary: is called exactly once
stream: is called 0, 1 or more
PostRecvMessage
:
unary: is called 0 or 1 (0 if service doesn't return a message)
stream: is called 0, 1 or more
Per-message hooks implementation example
class Middleware final : public MiddlewareBase {
public :
explicit Middleware(const Settings& settings);
void PreStartCall(MiddlewareCallContext& context) const override ;
void PreSendMessage(MiddlewareCallContext& context, const google::protobuf::Message& message) const override ;
void PostRecvMessage(MiddlewareCallContext& context, const google::protobuf::Message& message) const override ;
void PostFinish(MiddlewareCallContext& context, const grpc::Status& status) const override ;
private :
Settings settings_;
};
void Middleware::PreSendMessage(MiddlewareCallContext& context, const google::protobuf::Message& message) const {
const SpanLogger logger{context.GetSpan(), settings_.log_level};
{ugrpc::impl::kTypeTag, "request" },
{ugrpc::impl::kBodyTag, GetMessageForLogging(message, settings_)},
{ugrpc::impl::kMessageMarshalledLenTag, message.ByteSizeLong()},
};
if (context.IsClientStreaming()) {
logger.Log(settings_.msg_log_level, "gRPC request stream message" , std::move(extra));
} else {
logger.Log(settings_.msg_log_level, "gRPC request" , std::move(extra));
}
}
void Middleware::PostRecvMessage(MiddlewareCallContext& context, const google::protobuf::Message& message) const {
const SpanLogger logger{context.GetSpan(), settings_.log_level};
{ugrpc::impl::kTypeTag, "response" },
{ugrpc::impl::kBodyTag, GetMessageForLogging(message, settings_)},
};
if (context.IsServerStreaming()) {
logger.Log(settings_.msg_log_level, "gRPC response stream message" , std::move(extra));
} else {
logger.Log(settings_.msg_log_level, "gRPC response" , std::move(extra));
}
}
The static YAML config and component registration are identical as in the example above. So, let's not focus on this.
MiddlewareFactoryComponent
We use a simple short-cut ugrpc::client::SimpleMiddlewareFactoryComponent in the example above. To declare static config options of your middleware see gRPC middlewares configuration .
Exceptions and errors in middlewares
To fully understand what happens when middlewares hooks are failed, you should understand the middlewares order:
See also grpc_client_middlewares_order .
All exceptions are rethrown to the user code from client's RPC creating methods, Read
/ Write
(for streaming), and from methods that return the RPC status.
Using static config options in middlewares
There are two ways to implement a middleware component. You can see above ugrpc::client::SimpleMiddlewareFactoryComponent . This component is need for simple cases without static config options of a middleware.
Note In that case, kName
and kDependency
(middlewares::MiddlewareDependencyBuilder ) must be in a middleware class (as shown above).
If you want to use static config options for your middleware, use ugrpc::client::MiddlewareFactoryComponentBase .
See also gRPC middlewares configuration .
To override static config options of a middleware per a client see grpc_middlewares_config_override .
Middlewares order
Before starting to read specifics of client middlewares ordering:
See also gRPC middlewares order .
There are simple cases above: we just set Auth
group for one middleware.
Here we say that all client middlewares are located in these groups.
PreCore
group is called firstly, then Logging
and so forth...