'How do you make WebFilter process requests that are not already handled by GlobalFilter?
Similar vein to GlobalFilter vs WebFilter. I want to do some header manipulation and logging but if something is already processed by GlobalFilter by the gateway the response is already set.
Primarily I only want to put the webfilter for calls that are handled by the requesthandlers I have in the code but not the gateway itself.
The GlobalFilter I wrote is
@Component
@RequiredArgsConstructor
@Slf4j(topic = "request")
public class PerformanceRequestIDPostGatewayFilter implements GlobalFilter {
private final Tracing tracing;
private static final String LOG_MESSAGE_FORMAT = "{} {} {} {}ms";
@Override
public Mono<Void> filter(final ServerWebExchange exchange, GatewayFilterChain chain) {
final long startNanos = System.nanoTime();
return chain
.filter(exchange)
.doOnEach(
WebFluxSleuthOperators.withSpanInScope(
() -> {
final String traceId = tracing.currentTraceContext().get().traceIdString();
exchange.getResponse().getHeaders().add("X-B3-Traceid", traceId);
exchange.getResponse().getHeaders().add("X-Trace-ID", Util.toXRay(traceId));
}))
.then(
Mono.fromRunnable(
() -> {
final String requestURI = exchange.getRequest().getURI().toASCIIString();
final String method = exchange.getRequest().getMethodValue();
final long requestTimeNano = System.nanoTime() - startNanos;
final double requestTimeInMillis = requestTimeNano * 0.000001;
final HttpStatus statusCode =
Objects.requireNonNull(exchange.getResponse().getStatusCode());
final int status = statusCode.value();
final String requestTimeInMillisText =
String.format("%.03f", requestTimeInMillis);
if (requestTimeInMillis > 5000) {
log.error(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else if (statusCode.is4xxClientError() || requestTimeInMillis > 3000) {
log.warn(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else if (statusCode.isError()) {
log.error(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else {
log.info(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
}
// add CORS
final HttpHeaders responseHeaders =
exchange.getResponse().getHeaders();
if (responseHeaders.getAccessControlAllowOrigin() == null) {
responseHeaders.setAccessControlAllowOrigin("*");
}
}));
}
}
In the WebFilter I want to implement the same thing note the only change was the removal of @Component since it's going to break the app if I use it and the implements WebFilter and use of WebFilterChain chain
@Slf4j(topic = "request")
@RequiredArgsConstructor
public class PerformanceRequestIDPostFilter implements WebFilter {
private final Tracing tracing;
private static final String LOG_MESSAGE_FORMAT = "{} {} {} {}ms";
@Override
public Mono<Void> filter(final ServerWebExchange exchange, WebFilterChain chain) {
final long startNanos = System.nanoTime();
return chain
.filter(exchange)
.doOnEach(
WebFluxSleuthOperators.withSpanInScope(
() -> {
final String traceId = tracing.currentTraceContext().get().traceIdString();
exchange.getResponse().getHeaders().add("X-B3-Traceid", traceId);
exchange.getResponse().getHeaders().add("X-Trace-ID", Util.toXRay(traceId));
}))
.then(
Mono.fromRunnable(
() -> {
final String requestURI = exchange.getRequest().getURI().toASCIIString();
final String method = exchange.getRequest().getMethodValue();
final long requestTimeNano = System.nanoTime() - startNanos;
final double requestTimeInMillis = requestTimeNano * 0.000001;
final HttpStatus statusCode =
Objects.requireNonNull(exchange.getResponse().getStatusCode());
final int status = statusCode.value();
final String requestTimeInMillisText =
String.format("%.03f", requestTimeInMillis);
if (requestTimeInMillis > 5000) {
log.error(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else if (statusCode.is4xxClientError() || requestTimeInMillis > 3000) {
log.warn(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else if (statusCode.isError()) {
log.error(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else {
log.info(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
}
// add CORS
final HttpHeaders responseHeaders = exchange.getResponse().getHeaders();
if (responseHeaders.getAccessControlAllowOrigin() == null) {
responseHeaders.setAccessControlAllowOrigin("*");
}
}));
}
}
I tried putting in a ServerWebExchangeUtils.isAlreadyRouted in the WebFilter but that didn't work out too well.
Solution 1:[1]
I eventually got my use case to work, when I'm on the GatewayFilter I can't get the trace context unless I do
.doOnEach(
WebFluxSleuthOperators.withSpanInScope(
() -> {
final String traceId = tracing.currentTraceContext().get().traceIdString();
exchange.getResponse().getHeaders().add("X-B3-Traceid", traceId);
exchange.getResponse().getHeaders().add("X-Trace-ID", Util.toXRay(traceId));
}))
But that will only occur after the message is sent if I put it on the web filter. However, WebFilter actually has it in their "pre" filter stage. So what I did was drop the GatewayFilter (which was what I had originally before learning about WebFilter order which isn't documented) altogether.
The experiment I did was...
public Mono<Void> filter(final ServerWebExchange exchange, WebFilterChain chain) {
log.info("WebFilter pre {}", exchange.getRequest().getURI());
return chain
.filter(exchange)
.then(
Mono.fromRunnable(()->log.info("WebFilter post {}", exchange.getRequest().getURI()));
}
public Mono<Void> filter(final ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("WebFilter pre {}", exchange.getRequest().getURI());
return chain
.filter(exchange)
.then(
Mono.fromRunnable(()->log.info("WebFilter post {}", exchange.getRequest().getURI()));
}
The log results confirm what is discussed in the comments along with some revelations:
... traceID ... WebFilter pre /blah/foo
... traceID ... GatewayFilter pre /foo
... traceID ... GatewayFilter post /foo
... traceID ... WebFilter post /blah/foo
- WebFilter gets the full URI that was originally requested, but gateway filter only gets the portion that is extracted from the Path predicate
- the trace ID is available in the
WebFilter pre. That solved the conundrum where the Trace ID was not available when I was doing theGatewayFilter pre-filterearlier.
The final result is a single WebFilter.
@Component
@Slf4j(topic = "request")
@RequiredArgsConstructor
public class PerformanceRequestIDPostFilter implements WebFilter {
private final Tracing tracing;
private final ExcludedPathPatterns excludedPathPatterns;
private static final String LOG_MESSAGE_FORMAT = "{} {} {} {}ms";
@Override
public Mono<Void> filter(final ServerWebExchange exchange, WebFilterChain chain) {
final long startNanos = System.nanoTime();
final String traceId = tracing.currentTraceContext().get().traceIdString();
exchange.getResponse().getHeaders().add("X-B3-Traceid", traceId);
exchange.getResponse().getHeaders().add("X-Trace-ID", Util.toXRay(traceId));
return chain
.filter(exchange)
.then(
Mono.fromRunnable(
() -> {
if (excludedPathPatterns.isExcludedForServer(exchange.getRequest().getPath().pathWithinApplication())) {
return;
}
final String requestURI = exchange.getRequest().getURI().toASCIIString();
final String method = exchange.getRequest().getMethodValue();
final long requestTimeNano = System.nanoTime() - startNanos;
final double requestTimeInMillis = requestTimeNano * 0.000001;
final HttpStatus statusCode =
Objects.requireNonNull(exchange.getResponse().getStatusCode());
final int status = statusCode.value();
final String requestTimeInMillisText =
String.format("%.03f", requestTimeInMillis);
if (requestTimeInMillis > 5000) {
log.error(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else if (statusCode.is4xxClientError() || requestTimeInMillis > 3000) {
log.warn(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else if (statusCode.isError()) {
log.error(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
} else {
log.info(
LOG_MESSAGE_FORMAT, method, requestURI, status, requestTimeInMillisText);
}
}));
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Archimedes Trajano |
