Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy of the request/response body on a Spring reactive app?

I'm looking into optimal ways of accessing the HTTP request and response bodies for tracing in a Spring reactive application.

For previous versions, we've leveraged Servlet filters and Servlet request wrappers to consume the incoming request's input stream and hold a copy of it for asynchronous processing of the traces (we send them to Elasticsearch).

But for a Spring reactive app (using webflux), I'm wondering what'd be the most appropriate way to access the requests before they're decoded. Any thoughts?

like image 511
javabeats Avatar asked Nov 08 '17 15:11

javabeats


People also ask

What is @RequestBody annotation in Spring boot?

The @RequestBody annotation allows us to retrieve the request's body. We can then return it as a String or deserialize it into a Plain Old Java Object (POJO). Spring has built-in mechanisms for deserializing JSON and XML objects into POJOs, which makes this task a lot easier as well.

How do you read a body request in Spring boot?

@RequestBody: Annotation is used to get request body in the incoming request. Note: First we need to establish the spring application in our project. Step 2: Click on Generate which will download the starter project. Step 3: Extract the zip file.

What is response body annotation in Spring?

The @ResponseBody annotation tells a controller that the object returned is automatically serialized into JSON and passed back into the HttpResponse object. When you use the @ResponseBody annotation on a method, Spring converts the return value and writes it to the HTTP response automatically.

What is the use of @RequestBody?

Simply put, the @RequestBody annotation maps the HttpRequest body to a transfer or domain object, enabling automatic deserialization of the inbound HttpRequest body onto a Java object. Spring automatically deserializes the JSON into a Java type, assuming an appropriate one is specified.


1 Answers

Turns out this can be achieved using the provided decorators: ServerWebExchangeDecorator, ServerHttpRequestDecorator and ServerHttpResponseDecorator, respectively.

Here's a sample request decorator that accumulates the DataBuffer contents as its read by the request's default subscriber:

@Slf4j
public class CachingServerHttpRequestDecorator extends ServerHttpRequestDecorator {

    @Getter
    private final OffsetDateTime timestamp = OffsetDateTime.now();
    private final StringBuilder cachedBody = new StringBuilder();

    CachingServerHttpRequestDecorator(ServerHttpRequest delegate) {
        super(delegate);
    }

    @Override
    public Flux<DataBuffer> getBody() {
        return super.getBody().doOnNext(this::cache);
    }

    @SneakyThrows
    private void cache(DataBuffer buffer) {
        cachedBody.append(UTF_8.decode(buffer.asByteBuffer())
         .toString());
    }

    public String getCachedBody() {
        return cachedBody.toString();
    }

Just make sure that, when you decorate the ServerWebExchange passed by the WebFilter, you also override getRequest() to return the request decorator as well:

public final class PartnerServerWebExchangeDecorator extends ServerWebExchangeDecorator {

    private final ServerHttpRequestDecorator requestDecorator;
    private final ServerHttpResponseDecorator responseDecorator;

    public PartnerServerWebExchangeDecorator(ServerWebExchange delegate) {
        super(delegate);
        this.requestDecorator = new PartnerServerHttpRequestDecorator(delegate.getRequest());
        this.responseDecorator = new PartnerServerHttpResponseDecorator(delegate.getResponse());
    }

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

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

}

On the filter:

@Component
public class TracingFilter implements WebFilter {

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

Which can be used as such (beware the statically imported functions):

@Bean
public HttpHandler myRoute(MyHandler handler) {
    final RouterFunction<ServerResponse> routerFunction =
        route(POST("/myResource"), handler::persistNotification);
    return webHandler(toWebHandler(routerFunction))
        .filter(new TracingFilter())
        .build();
}
like image 130
javabeats Avatar answered Oct 17 '22 19:10

javabeats