Accessing the POST request body in Spring WebClient filters
Tags: springboot, programming, java, kotlin
Published: 2023-01-31 14:56:04

---
title: Accessing the POST request body in Spring WebClient filters
published: true
date: 2023-01-16 00:34:13 UTC
tags: springboot,programming,java,kotlin
canonical_url: https://blog.dripto.xyz/accessing-the-post-request-body-in-spring-webclient-filters
cover_image: https://cdn.hashnode.com/res/hashnode/image/upload/v1673829618750/70d88bdc-ebbc-4dc7-9dfb-834165b86569.jpeg?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp
---

Accessing the POST request body in Spring WebClient filters can be a bit tricky, as the request body is typically passed as a stream. In this blog post, we'll explore two solutions to this problem.

## The problem

In some cases, you may need to access the POST request body in a Spring WebClient filter, in order to perform an operation such as signing the request with JWS header. However, because the request body is passed as a stream, it can be difficult to access and manipulate all at once.

## Solution 1

### Extending the `ClientHttpRequestDecorator` class

One approach to solving this problem is to extend the `ClientHttpRequestDecorator` class and override the `writeWith` method. This allows you to gain access to the `DataBuffer` publisher, which represents the stream of the request body. You can then concatenate and retrieve the original request by joining the published data buffers using `DataBufferUtils`.

```kotlin
class BufferingRequestDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) {

    override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> =
        DataBufferUtils.join(body)
            .flatMap { db -> setJwsHeader(extractBytes(db)).then(super.writeWith(Mono.just(db))) }

    private fun setJwsHeader(data: ByteArray = byteArrayOf()): Mono<Void> {
        headers.add("JWS-header", createJws(data)) // or do whatever you want with the data
        return Mono.empty()
    }

    private fun extractBytes(data: DataBuffer): ByteArray {
        val bytes = ByteArray(data.readableByteCount())
        data.read(bytes)
        data.readPosition(0)
        return bytes
    }
}

```

Once you have this class implemented, you can call it in the WebClient filter and deconstruct the old request and create a new one out of it.

```kotlin
class WebClientBufferingFilter : ExchangeFilterFunction {
    override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> =
        next.exchange(
            ClientRequest.from(request)
                .body { outputMessage, context ->
                    request.body().insert(
                        BufferingRequestDecorator(outputMessage),
                        context
                    )
                }.build()
        )
}

```

Create a webclient with this filter

```kotlin
WebClient.builder()
        .filter(WebClientBufferingFilter())
        .build()

```

## Solution 2

### Using a custom JSON Encoder

By default, WebClient uses Jackson to encode the data into JSON. When setting up the request, we use the utility BodyInserters.fromValue to create a body inserter for our data. The DefaultRequestBuilder for WebClient keeps this BodyInserter object at the time when the request will be sent, passing it all known message-writers and other relevant contextual information. However, we may need to access the request body before it is sent in order to perform additional actions such as signing or adding an authorization header.

Create a wrapper class around Jackson2JsonEncoder that allows us to intercept the encoded body. Specifically, we will be wrapping the encode method implementation from AbstractJackson2Encoder.

```kotlin
const val REQUEST_CONTEXT_KEY = "test"
class BodyProvidingJsonEncoder : Jackson2JsonEncoder() {
    override fun encode(
        inputStream: Publisher<out Any>,
        bufferFactory: DataBufferFactory,
        elementType: ResolvableType,
        mimeType: MimeType?,
        hints: MutableMap<String, Any>?
    ): Flux<DataBuffer> {
        return super.encode(inputStream, bufferFactory, elementType, mimeType, hints)
            .flatMap { db: DataBuffer ->
                Mono.deferContextual {
                    val clientHttpRequest = it.get<ClientHttpRequest>(REQUEST_CONTEXT_KEY)
                    db
                }
            }
    }

    private fun extractBytes(data: DataBuffer): ByteArray {
        val bytes = ByteArray(data.readableByteCount())
        data.read(bytes)
        data.readPosition(0)
        return bytes
    }
}

```

Build a custom `ReactorClientHttpConnector` to put the request in the context.

```kotlin
class MessageSigningHttpConnector : ReactorClientHttpConnector() {
    override fun connect(
        method: HttpMethod,
        uri: URI,
        requestCallback: Function<in ClientHttpRequest, Mono<Void>>
    ): Mono<ClientHttpResponse> {
        // execute the super-class method as usual, but insert an interception into the requestCallback that can
        // capture the request to be saved for this thread.
        return super.connect(
            method, uri
        ) { incomingRequest: ClientHttpRequest ->
            requestCallback.apply(incomingRequest).contextWrite {
                it.put(
                    REQUEST_CONTEXT_KEY,
                    incomingRequest
                )
            }
        }
    }
}

```

Define a custom `ExchangeFunction` using the utility `ExchangeFunctions.create()` which accepts a custom `HttpConnector`. This connector has access to the function that makes the request. It is at this point that we can get a handle on the `ClientHttpRequest` and wait for the body to be serialized so that the header can be added.

```kotlin
val httpConnector = MessageSigningHttpConnector()
    val bodyProvidingJsonEncoder = BodyProvidingJsonEncoder()

    val client = WebClient.builder()
        .exchangeFunction(ExchangeFunctions.create(
            httpConnector,
            ExchangeStrategies
                .builder()
                .codecs { clientDefaultCodecsConfigurer: ClientCodecConfigurer ->
                    clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(bodyProvidingJsonEncoder)
                    clientDefaultCodecsConfigurer.defaultCodecs()
                        .jackson2JsonDecoder(Jackson2JsonDecoder(ObjectMapper(), MediaType.APPLICATION_JSON))
                }
                .build()
        ))
        .build()

```

As always, be sure to test your codes thoroughly.

Solution 2 is originally found in [https://andrew-flower.com/blog/Custom-HMAC-Auth-with-Spring-WebClient](https://andrew-flower.com/blog/Custom-HMAC-Auth-with-Spring-WebClient). I tinkered with it a bit before finding solution 1 which is somehow inspired by https://github.com/spring-projects/spring-framework/issues/26489. Finally, I decided to go with solution 1 as I found it to be a bit more simple and elegant.

originally published at: https://blog.dripto.xyz/accessing-the-post-request-body-in-spring-webclient-filters