SpringBoot 2.4.5, spring-boot-starter-webflux
WebFilter
places a value into reactive context at the beginning of the request processing and retrieves it after. It works fine when request is handled by Controller
but if the request is made to the non-existent path (no Controller
is mapped to the request path) the value is missing in the reactive context (ctx.get()
call fails).
WebFilter
code is:
@Component
public class CorrelationIdFilter implements WebFilter {
public static final String CORRELATION_ID_HEADER_NAME = "X-correlationId";
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
exchange.getResponse().beforeCommit(() -> Mono.deferContextual(ctx -> {
logger.info("### Setting response header");
exchange.getResponse().getHeaders().add(CORRELATION_ID_HEADER_NAME, ctx.get(CORRELATION_ID_HEADER_NAME));
return Mono.empty();
}));
return chain.filter(exchange)
.contextWrite(ctx -> {
String correlationId = UUID.randomUUID().toString();
logger.info("### CorrelationId generated: {}", correlationId);
return ctx.put(CORRELATION_ID_HEADER_NAME, correlationId);
});
}
}
Code to reproduce: https://github.com/maximdim/webflux-context
Specifically HomeControllerTest.testNonExistingPath
Comment From: rstoyanchev
In the 404 case, the error signal with the ResponseStatusException
flows out past the CorrelationIdFilter
and is caught by Boot's error handling which then writes error response details and that's when the commit actions are triggered. Given that a WebExceptionHandler
s are ahead of all WebFilter
s, effectively any handling in a WebExceptionHandler
is downstream from WebFilter
s and this is why the Reactor context at that point doesn't contain anything inserted by CorrelationIdFilter
.
Using HttpHandlerDecoratorFactory
is earlier and better at inserting context for the entire processing chain:
@Component
public class CorrelationIdFilter implements WebFilter, HttpHandlerDecoratorFactory {
public static final String CORRELATION_ID_HEADER_NAME = "X-correlationId";
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
exchange.getResponse().beforeCommit(() -> Mono.deferContextual(ctx -> {
logger.info("### Setting response header");
exchange.getResponse().getHeaders().add(CORRELATION_ID_HEADER_NAME, ctx.get(CORRELATION_ID_HEADER_NAME));
return Mono.empty();
}));
return chain.filter(exchange);
}
@Override
public HttpHandler apply(HttpHandler httpHandler) {
return (request, response) ->
httpHandler.handle(request, response)
.contextWrite(ctx -> {
String correlationId = UUID.randomUUID().toString();
logger.info("### CorrelationId generated: {}", correlationId);
return ctx.put(CORRELATION_ID_HEADER_NAME, correlationId);
});
}
}