Here's what I want to do at a high-level
- Capture some http headers in a WebFilter
- In the Controller method, I make a grpc call
- I'd like to propagate the http headers as grpc Metadata headers
Currently, my working implementation is
- WebFilter captures http headers and writes to reactor Context
- Controller method extracts reactor Context and passes it into a Grpc ClientInterceptor
- Grpc ClientInterceptor extracts http headers from Context and injects into Grpc Metadata headers
But I'd like to avoid having the Controller method doing any work (step 2 above).
Here's an implementation but looking for a way to get the http headers to Grpc Metadata without explicitly passing them in from Controller method.
WebFilter
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { return chain.filter(exchange) .contextWrite(Context.of("my-header", "header-value")); }
Controller method
@GetMapping public Mono<String> testHeaderPropagation() throws Exception { return Mono.deferContextual(reactorContext -> { Response response = grpcStub .withInterceptors(new GrpcClientInterceptor(reactorContext)) .call(request); return Mono.just(response.getMessage()); }); }
GrpcClientInterceptor
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { final ClientCall<ReqT, RespT> call = next.newCall(method, callOptions); return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) { @Override public void start(Listener<RespT> responseListener, Metadata headers) { Metadata.Key < String > key = Metadata.Key.of("filter-context", Metadata.ASCII_STRING_MARSHALLER); headers.put(key, context.get("filter-context")); delegate().start(responseListener, headers); } }; }
I'd like simplify my Controller method (remove the explicit passing in of the reactor Context into a clientInterceptor)
grpcStub.call(request)
I believe Spring Sleuth has a way of doing this but not sure how to adapt its approach. What clever thing am I missing?
Edit
The reason I'm pushing for a version that involves minimal controller method code is because other developers will be writing the controller and methods. I want to establish a pattern that doesn't require extra wiring if possible, otherwise there's a chance someone forgets to do it or does it wrong.
Edit
Follow-up question. Instead of having the controller method pass in the Context to the clientInterceptor, I tried to get the Context inside the Grpc ClientInterceptor but that doesn't seem to work.
Here's is what I tried to do
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { final ClientCall<ReqT, RespT> call = next.newCall(method, callOptions); return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) { @Override public void start(Listener<RespT> responseListener, Metadata headers) { Mono.deferContextual(context -> { Metadata.Key < String > key = Metadata.Key.of("CONTEXT-HEADER", Metadata.ASCII_STRING_MARSHALLER); headers.put(key, context.get("CONTEXT-HEADER")); delegate().start(responseListener, headers); return Mono.empty(); }).subscribe(); } }; }
But I get an error
reactor.core.Exceptions$ErrorCallbackNotImplemented: java.util.NoSuchElementException: Context does not contain key CONTEXT-HEADER
Trying to understand why the reactor pipeline breaks here
https://stackoverflow.com/questions/66880712/can-i-pass-reactor-context-into-grpc-clientinterceptor-implicitly March 31, 2021 at 10:09AM
没有评论:
发表评论