user9349304
user9349304

Reputation:

How to read the request body with spring webflux

I'm using Spring 5, Netty and Spring webflux to develop and API Gateway. Sometime I want the request to 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 body, only the first element of the flux is read so I can't have the entire body. Any idea how to do this ?

Upvotes: 9

Views: 23077

Answers (2)

benaonreg
benaonreg

Reputation: 618

1.Add "readBody()" to the post route:

builder.routes()
.route("get_route", r -> r.path("/**")
    .and().method("GET")
    .filters(f -> f.filter(myFilter))
    .uri(myUrl))
.route("post_route", r -> r.path("/**")
    .and().method("POST")
    .and().readBody(String.class, requestBody -> {return true;})
    .filters(f -> f.filter(myFilter))
    .uri(myUrl))

2.Then you can get the body string in your filter:

String body = exchange.getAttribute("cachedRequestBodyObject");

Advantages:

  1. No blocking.

  2. No need to refill the body for further process.

Works with Spring Boot 2.0.6.RELEASE + Sring Cloud Finchley.SR2 + Spring Cloud Gateway.

Upvotes: 7

Brian Clozel
Brian Clozel

Reputation: 59056

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.

Upvotes: 7

Related Questions