I have a Webflux application, where I have a ServerWebExchangeDecorator that decorates the request and responses. I have overrides to do some logging and then call the super methods. This is what I have in code:

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Component
public class LoggingWebFilter implements WebFilter {

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    return chain.filter(decorate(exchange));
  }

  private ServerWebExchange decorate(ServerWebExchange exchange) {

    final ServerHttpRequest decoratedRequest = new LoggingServerHttpRequestDecorator(exchange.getRequest());
    final ServerHttpResponse decoratedResponse = new LoggingServerHttpResponseDecorator(exchange.getResponse());

    return new ServerWebExchangeDecorator(exchange) {

      @Override
      public ServerHttpRequest getRequest() {
        return decoratedRequest;
      }

      @Override
      public ServerHttpResponse getResponse() {
        return decoratedResponse;
      }

    };
  }

}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;

public class LoggingServerHttpRequestDecorator extends ServerHttpRequestDecorator {

  private static final Logger logger = LoggerFactory.getLogger(LoggingServerHttpRequestDecorator.class);

  public LoggingServerHttpRequestDecorator(ServerHttpRequest delegate) {
    super(delegate);
  }

  @Override
  public Flux<DataBuffer> getBody() {
    logger.info("getBody method");
    return super.getBody();
  }

}
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import reactor.core.publisher.Mono;

public class LoggingServerHttpResponseDecorator extends ServerHttpResponseDecorator {

  private static final Logger logger = LoggerFactory.getLogger(LoggingServerHttpResponseDecorator.class);

  public LoggingServerHttpResponseDecorator(ServerHttpResponse delegate) {
    super(delegate);
  }

  @Override
  public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
    logger.info("writeWith method");//THIS LINE IS NOT EXECUTED WHEN AN EXCEPTION IS THROWN
    return super.writeWith(body);
  }

  @Override
  public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
    logger.info("writeAndFlushWith method");
    return super.writeAndFlushWith(body);
  }

}

When I do a happy path with a POST request, this works fine , but when an exception is thrown, the Response Decorator is omitted and my custom code is not being executed.

This is a sample controller code to replicate the issue:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/decorator-demo")
public class DecoratorDemoController {

  /** The Constant logger. */
  private static final Logger logger = LoggerFactory.getLogger(DecoratorDemoController.class);


  @PostMapping(produces = MediaType.APPLICATION_STREAM_JSON_VALUE, consumes = MediaType.APPLICATION_STREAM_JSON_VALUE)
  public Mono<ResponseEntity<String>> postData(@RequestBody String id) {
    logger.info("attempting to post the data");
    if(id.length() == 1){
      Mono<String> created = Mono.just(id);
      return created.flatMap(vo -> Mono.just(ResponseEntity.status(HttpStatus.CREATED).body(vo)));
    }
    throw new IllegalArgumentException("String length must be 1");
  }

}

When I post a single character, I have the logs I am expecting:

LoggingServerHttpRequestDecorator  : getBody method
DecoratorDemoController            : attempting to post the data
LoggingServerHttpResponseDecorator : writeWith method

But when I post more than one character, this is the logs I am having:

LoggingServerHttpRequestDecorator  : getBody method
DecoratorDemoController            : attempting to post the data
AbstractErrorWebExceptionHandler : [0b933716]  500 Server Error for HTTP POST "/decorator-demo"

Am I doing something wrong, or missing something? I am expecting the Decorator code to be executed, regardless of how a request is processed (i.e. happy path or an exception thrown, just as in Spring MVC, the response wrappers work whether the request followed a happy path or an exception occurred.

I am not sure if this is expected behavior in Webflux.

Comment From: rstoyanchev

Thanks for getting in touch, but it feels like this is a question that would be better suited to Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use the issue tracker only for bugs and enhancements.

Comment From: rstoyanchev

Please see the docs. Specifically that requests are processed through " a chain of multiple WebExceptionHandler, multiple WebFilter, and a single WebHandler component". In other words exception handling is ahead of filters.

Comment From: erikrz

Yes, I read the docs, and I decided to use a filter because it says "you can use a WebFilter to apply interception-style logic before and after the rest of the processing chain of filters and the target WebHandler."

I want to intercept all the responses and log some metrics and data, but the Filter is only allowing me to handle all the request that do not result in throwing an exception. This logic is not applied when an exception occurs. If using filters and decorators is not the way, then, what options are left for me to use?

Comment From: rstoyanchev

The error signal does pass through the WebFilter which you can intercept with doOnError or any other error-related Mono operator. You could install your own WebExceptionHandler. Or go even earlier in the processing chain, and wrap the HttpHandler.

Comment From: mehmetsen80

I have the same problem, any solutions to this?