How to read the request body with spring webflux

user9349304 picture user9349304 · Apr 26, 2018 · Viewed 12.1k times · Source

I'm using Spring 5, Netty and Spring webflux to develop and API Gateway. Sometime I want that the request should be stopped by the gateway but I also want to read the body of the request to log it for example and return an error to the client.

I try to do this in a WebFilter by subscribing to the body.

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    if (enabled) {
        logger.debug("gateway is enabled. The Request is routed.");
        return chain.filter(exchange);
    } else {
        logger.debug("gateway is disabled. A 404 error is returned.");

        exchange.getRequest().getBody().subscribe();
        exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
        return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().allocateBuffer(0)));
    }
}

When I do this it works when the content of the body is small. But when I have a large boby, only the first element of the flux is read so I can't have the entire body. Any idea how to do this ?

Answer

Brian Clozel picture Brian Clozel · Apr 26, 2018

The problem here is that you are subscribing manually within the filter, which means you're disconnecting the reading of the request from the rest of the pipeline. Calling subscribe() gives you a Disposable that helps you manage the underlying Subscription.

So you need to turn connect the whole process as a single pipeline, a bit like:

Flux<DataBuffer> requestBody = exchange.getRequest().getBody();
// decode the request body as a Mono or a Flux
Mono<String> decodedBody = decodeBody(requestBody); 
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return decodedBody.doOnNext(s -> logger.info(s))
                  .then(exchange.getResponse().setComplete());

Note that decoding the whole request body as a Mono means your gateway will have to buffer the whole request body in memory.

DataBuffer is, on purpose, a low level type. If you'd like to decode it (i.e. implement the sample decodeBodymethod) as a String, you can use one of the various Decoder implementations in Spring, like StringDecoder.

Now because this is a rather large and complex space, you can use and/or take a look at Spring Cloud Gateway, which does just that and way more.