gRPC Interceptor

May 12, 2024

Overview

gRPC Interceptors are middleware that wrap clients and servers. This middleware becomes part of the clients and servers themselves. It does not become a whole separate service that sits between the client and server (as is possible with the definition of middleware). The order in which interceptors are called depends on the implementation (see below).

Related blog posts:

Table of Contents

Use Cases
Client and Server Implementation Details
Conclusion

Use Cases
^

Below are some use cases for gRPC interceptors.

Use CaseDescription
Authentication and Authorization- Adding required credentials to client-side (outgoing) requests.
- Ensuring that server-side (incoming) requests contain the required credentials.
LoggingLogging request-level details (e.g. headers) at either client or server-side. Note: It's a security best practice to omit logging of authorization/authentication tokens in production.
Monitoring and MetricsAsynchronously sending a request (i.e. emitting a metric) for the purpose of observability/monitoring. Warning: synchronous I/O tasks should be avoided since they can noticeably increase latency.
Error HandlingCentralized (in code in a single library that is consumed implicitly by all microservices) error handling can be located in interceptors. Retry mechanisms can be implemented here.
TracingInterceptors are one of the best locations to implement tracing. Tracing is a useful nice-to-have in any distributed system that comes with its limitations (e.g. workloads that use batching).
CachingChecking if a request for a resource has already been made recently and returning a cached result, rather than allowing the request to complete its journey across a distributed system can noticeably reduce overall system load.
Compression and DecompressionInterceptors are one way to add compression to your client/servers in a distributed system. But this is something that can be added when instantiating the client or server as well, which means it's also possible to alternatively create project-level client and server wrappers that enable compression/decompression.

Client and Server Implementation Details
^

Official Java examples can be found at the grpc-java GitHub.

Something that is not shown in the official Java example above is how you define multiple interceptors. You add additional interceptors as additional arguments to ServerInterceptors.intercept() or ClientInterceptors.intercept(). They're called in reverse order (last is called first). Additional control exists within each interceptor where you can decide to implement your logic before or after the next interceptor (indirectly through super.start() for clients and next.startCall() for servers), assuming multiple interceptors (unless it's the final interceptor, in which case they'd be calling the client/server code).

Learn more about the ServerInterceptors and ClientInterceptors classes.

Conclusion
^

gRPC Interceptors can be thought of as wrappers around clients and servers. However, the instantiation of gRPC clients and servers can alternatively be wrapped with logic that is intended to be used globally in a project (e.g. using a Builder Pattern). Whether you choose interceptors or your own client/server-side wrapper should depend on the use case, performance, and maintainability trade-off. When the client or server offers built-in functionality (as is the case with compression in gRPC), it's likely that the efficiency is higher than implementation at the interceptor-level.

To be updated with benchmarks comparing client/server-level compression vs interceptor-level compression.

To be updated with diagram depicting multi-layered wrapping of multiple interceptors.