'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
  1. WebFilter gets the full URI that was originally requested, but gateway filter only gets the portion that is extracted from the Path predicate
  2. the trace ID is available in the WebFilter pre. That solved the conundrum where the Trace ID was not available when I was doing the GatewayFilter pre-filter earlier.

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