Spring boot 2.1.5 Project Reactor 3.2.9
In my webflux project I extensively use the reactor contexts in order to pass around some values.
I set up a filter and am trying to log things which are in the context and to log different things in case of error/success.
I have checked this documentation: https://projectreactor.io/docs/core/release/reference/#context
I still struggle (especially on the error side) to get it.
Basically, I have this filter:
@Component
public class MdcWebFilter implements WebFilter {
@NotNull
@Override
public Mono<Void> filter(@NotNull ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
Mono<Void> filter = webFilterChain.filter(serverWebExchange);
return filter
.doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
@Override
public void accept(Void aVoid, Throwable throwable) {
//Here i would like to be able to access to the request's context
System.out.println("doAfterSuccessOrError:" + (throwable==null ? "OK" : throwable.getMessage())+"log the context");
}
})
.doOnEach(new Consumer<Signal<Void>>() {
@Override
public void accept(Signal<Void> voidSignal) {
//Here i have the context but i don't really know if i am in success or error
System.out.println("doOnEach:"+"Log OK/KO and the exception" + voidSignal.getContext());
}
})
.subscriberContext(context -> context.put("somevar", "whatever"));
}
}
I also tried with a flatMap() and a Mono.subscriberContext() but i am not sure how to plug correctly with the filter (especially in error).
What would be the best way to achieve this ?
I'm not sure whether it possible access request reactor context from within WebFilter. WebFilter context exists in another Mono chain. But it is do possible to assosiate attributes with request and able to fetch these attributes during request life time RequestContextHolder for Reactive Web Very similar to Servlet API.
Controller:
@GetMapping(path = "/v1/customers/{customerId}")
public Mono<Customer> getCustomerById(
@PathVariable("customerId") String customerId,
ServerWebExchange serverWebExchange)
{
serverWebExchange.getAttributes().put("traceId", "your_trace_id");
return customerService.findById(customerId);
}
WebFilter:
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// ...
String traceId = exchange.getAttributeOrDefault("traceId", "default_value_goes_here");
//...
return chain.filter(exchange);
}
I know this is probably not the cleanest of the solutions, but you could create a container class that would keep the context between your two callbacks.
You would store the context at doOnEach
and then you would be able to load it back at doAfterSuccessOrError
:
public Mono<Void> filter(@NotNull ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
@lombok.Data
class MyContextContainer {
private Context context;
}
MyContextContainer container = new MyContextContainer();
Mono<Void> filter = webFilterChain.filter(serverWebExchange);
return filter
.doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
@Override
public void accept(Void aVoid, Throwable throwable) {
// load the context here
Context context = container.getContext();
// then do your stuff here
}
})
.doOnEach(new Consumer<Signal<Void>>() {
@Override
public void accept(Signal<Void> voidSignal) {
// store the context here
container.setContext(voidSignal.getContext());
}
})
.subscriberContext(context -> context.put("somevar", "whatever"));
}
It doesn't need to be a class, really. It could be an AtomicReference
, but you get the idea.
Again, this might be just a workaround. I believe there must be a better way to access the context.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With