Description
🚀 Feature: Ligthweight custom annotations
I have a big need for allowing custom RPC-annotations and adding special logic in my gateway that reacts according to those annotations.
Use case 1:
The first use case are caching-annotations: I want to be able to specify caching-behaviour (no-cache/max-age/...) inside my protobuf, and have a special http middleware/interceptor in my gateway that can set HTTP caching-headers according to that definition. Ideally, the gRPC server is not involved in the Rest-API caching.
Use case 2:
Another use case is authentication. I'm handling authentication and authorization in the API gateway, not the individual gRPC servers. Therefore, I need to annotate individual API calls with the required user roles or permissions, or mark them as "no authentication needed" (for the login-request for example). An interceptor middleware can then verify that the user session (stored in the request-context) is valid for the given RPC method and disallow the call if needed.
Solution
I have already seen similar feature-requests like #410 or #1243, which turned out to "implement a fully-functional plugin system". While it seems that I'm not the only one who needs this feature, a plugin system requires a lot of time and effort to implement, and that is (apparently) not possible with the project's current resources (at least not in the near future). And to be honest, I think a plugin-system does much more than I actually need or want for my purposes and is not really neccessary.
Custom annotation support could be implemented much easier: The only thing that would be needed is the registration of interceptors for certain (or all) RPC methods. The interceptor method would wrap the client-Call and is therefore able to get the method name / protobuf request object and access protobuf annotations from there. It can then use this information to set headers, rewrite protobuf messages, check session information in the context or abort the call altogether.
I'm currently evaluating if grpc-gateway is suitable for my purposes, so I'm still new here (and this is going to be a deciding factor). I'm thinking about providing a serverMux option like this:
mux := runtime.NewServeMux(runtime.WithMethodInterceptor(methodInterceptor))
The method interceptor would receive every argument that the gRPC client call receives and has all the same return types.
Additionally, it receives a callback-function for the client-call itself:
type ClientCall func(ctx context.Context, req proto.Message, headerMD, trailerMD metadata.MD) (proto.Message, runtime.ServerMetadata, error)
type MethodInterceptor func(ctx context.Context, req proto.Message, headerMD, trailerMD metadata.MD, client ClientCall) (proto.Message, runtime.ServerMetadata, error)
An interceptor that does nothing would simply return client(ctx, req, headerMD, trailerMD)
, not modifying the gateways behaviour at all.