4

I'm using WebClient (SpringBoot 2.0.2.RELEASE) to send a POST with SOAP request, but it is missing "Content-Length" header required by the legacy API.

Is it possible to configure WebClient to include "Content-Length" header? There is an Spring Framework Issue resolved and introduced for EncoderHttpMessageWriter in SpringBoot 2.0.1, but it seems not to work for JAXB.

I tried to use BodyInserters:

webClient.post().body(BodyInserters.fromObject(request)).exchange();

and syncBody:

webClient.post().syncBody(request).exchange();

None of them worked for WebClient. Though, when RestTemplate is used, Content-Length is set and API responds with success

Wojciech Marusarz
  • 342
  • 1
  • 2
  • 10

3 Answers3

4

I am struggling with the same problem, as an ugly work-around I am manually serializing the request (JSON in my case) and setting the length (Kotlin code):

open class PostRetrieverWith411ErrorFix(
    private val objectMapper: ObjectMapper
) {

protected fun <T : Any> post(webClient: WebClient, body: Any, responseClass: Class<T>): Mono<T> {
    val bodyJson = objectMapper.writeValueAsString(body)

    return webClient.post()
        .contentType(MediaType.APPLICATION_JSON_UTF8)
        .contentLength(bodyJson.toByteArray(Charset.forName("UTF-8")).size.toLong())
        .syncBody(bodyJson)
        .retrieve()
        .bodyToMono(responseClass)
    }
}
Milosz Tylenda
  • 342
  • 2
  • 10
  • 1
    It seems that it is the only way for now. After some research it seems that there's no way to add this header using current webflux implementation. – Wojciech Marusarz May 25 '18 at 16:28
3

If you apply Sven's colleague(Max) solution like we did you can also adapt it for cases like your body being a custom obj but you have to serialize it once:

String req = objectMapper.writeValueAsString(requestObject)

and passed that to

webClient.syncBody(req)

Keep in mind that with SpringBoot 2.0.3.RELEASE, if you'll pass a String to webClient as a request, it will put as ContentType header MediaType.TEXT_PLAIN and that made our integration with other service to fail. We fixed that by setting specifically content type header like this:

httpHeaders.setContentType(MediaType.APPLICATION_JSON);
brebDev
  • 774
  • 10
  • 27
2

WebClient is a streaming client and it's kind of difficult to set the content length until the stream has finished. By then the headers are long gone. If you work with legacy, you can re-use your mono (Mono/Flux can be reused, Java streams not) and check the length.

    public void post() {

    Mono<String> mono = Mono.just("HELLO WORLDZ");

    final String response = WebClient.create("http://httpbin.org")
            .post()
            .uri("/post")
            .header(HttpHeaders.CONTENT_LENGTH,
                    mono.map(s -> String.valueOf(s.getBytes(StandardCharsets.UTF_8).length)).block())
            .body(BodyInserters.fromPublisher(mono, String.class))
            .retrieve()
            .bodyToMono(String.class)
            .block();

    System.out.println(response);

}

A colleague (well done Max!) of mine came up with cleaner solution, I added some wrapping code so it can be tested:

    Mono<String> my = Mono.just("HELLO WORLDZZ")
            .flatMap(body -> WebClient.create("http://httpbin.org")
                    .post()
                    .uri("/post")
                    .header(HttpHeaders.CONTENT_LENGTH,
                            String.valueOf(body.getBytes(StandardCharsets.UTF_8).length))
                    .syncBody(body)
                    .retrieve()
                    .bodyToMono(String.class));

    System.out.println(my.block());
Sven
  • 892
  • 14
  • 29
  • 1
    This only works for pure String bodies, not for mapped Objects like POJO to JSON, or does it? – Björn Jul 14 '20 at 09:35
  • 1
    There is a reply since 2018 that answer your question... – Sven Jul 14 '20 at 19:27
  • Even the answer by brebDev does not adress the case when you just pass a Pojo as syncBody using the auto-mapping features of WebClient `.syncBody(myPojo)`, because in this case I cannot know how many characters the serialisation will be, or can I? Or is explicit mapping the only way out? Sorry for my first question being sloppily formulated. – Björn Jul 15 '20 at 15:18
  • Easy Solution: Use 2.2 or higher. The issue has been fixed: https://github.com/spring-projects/spring-framework/issues/21085 in 2019 – Björn Jul 17 '20 at 08:17