Reactive WebClient GET Request with text/html response
Asked Answered
H

3

9

Currently I’m having an issue with new Spring 5 WebClient and I need some help to sort it out. The issue is:

I request some url that returns json response with content type text/html;charset=utf-8.

But unfortunately I’m still getting an exception: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/html;charset=utf-8' not supported. So I can’t convert response to DTO.

For request I use following code:

Flux<SomeDTO> response = WebClient.create("https://someUrl")
                .get()
                .uri("/someUri").accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToFlux(SomeDTO.class);

response.subscribe(System.out::println);

Btw, it really doesn’t matter which type I point in accept header, always returning text/html. So how could I get my response converted eventually?

Halvaard answered 31/1, 2018 at 23:40 Comment(2)
MyClientHttpResponseDecorator(response) - Argument is ClientHttpResponse and in the map you are getting CLientResponse. Is this working ?Enfeoff
Did you find any solution?Shir
M
1

Having a service send JSON with a "text/html" Content-Type is rather unusual.

There are two ways to deal with this:

  1. configure the Jackson decoder to decode "text/html" content as well; look into the WebClient.builder().exchangeStrategies(ExchangeStrategies) setup method
  2. change the "Content-Type" response header on the fly

Here's a proposal for the second solution:

WebClient client = WebClient.builder().filter((request, next) -> next.exchange(request)
                .map(response -> {
                    MyClientHttpResponseDecorator decorated = new 
                        MyClientHttpResponseDecorator(response); 
                    return decorated;
                })).build();

class MyClientHttpResponseDecorator extends ClientHttpResponseDecorator {

  private final HttpHeaders httpHeaders;

  public MyClientHttpResponseDecorator(ClientHttpResponse delegate) {
    super(delegate);
    this.httpHeaders = new HttpHeaders(this.getDelegate().getHeaders());
    // mutate the content-type header when necessary
  }

  @Override
  public HttpHeaders getHeaders() {
    return this.httpHeaders;
  }
}

Note that you should only use that client in that context (for this host). I'd strongly suggest to try and fix that strange content-type returned by the server, if you can.

Massarelli answered 1/2, 2018 at 8:58 Comment(4)
Second option doesn't work. UnsupportedOperationException trying to modify a unmodifiable map Can you elaborate on your first option?Clo
I've updated my answer for solution 2) - you can decorate the client response to avoid that problem. I've also pointed to the right build method for changing the exchange strategies.Massarelli
MyClientHttpResponseDecorator decorated = new MyClientHttpResponseDecorator(response); But it seems that the response isn't a ClientHttpResponse.Overlying
Second solution doesnt't work liek Q_SJ said "response" seems not to be a ClientHttpResponse.Expellant
A
14

As mentioned in previous answer, you can use exchangeStrategies method,

example:

            Flux<SomeDTO> response = WebClient.builder()
                .baseUrl(url)
                .exchangeStrategies(ExchangeStrategies.builder().codecs(this::acceptedCodecs).build())
                .build()
                .get()
                .uri(builder.toUriString(), 1L)
                .retrieve()
                .bodyToFlux( // .. business logic


private void acceptedCodecs(ClientCodecConfigurer clientCodecConfigurer) {
    clientCodecConfigurer.customCodecs().encoder(new Jackson2JsonEncoder(new ObjectMapper(), TEXT_HTML));
    clientCodecConfigurer.customCodecs().decoder(new Jackson2JsonDecoder(new ObjectMapper(), TEXT_HTML));
}
Akin answered 15/7, 2019 at 20:18 Comment(1)
How are you initializing the Mime type to use text/html here?Tollefson
M
1

Having a service send JSON with a "text/html" Content-Type is rather unusual.

There are two ways to deal with this:

  1. configure the Jackson decoder to decode "text/html" content as well; look into the WebClient.builder().exchangeStrategies(ExchangeStrategies) setup method
  2. change the "Content-Type" response header on the fly

Here's a proposal for the second solution:

WebClient client = WebClient.builder().filter((request, next) -> next.exchange(request)
                .map(response -> {
                    MyClientHttpResponseDecorator decorated = new 
                        MyClientHttpResponseDecorator(response); 
                    return decorated;
                })).build();

class MyClientHttpResponseDecorator extends ClientHttpResponseDecorator {

  private final HttpHeaders httpHeaders;

  public MyClientHttpResponseDecorator(ClientHttpResponse delegate) {
    super(delegate);
    this.httpHeaders = new HttpHeaders(this.getDelegate().getHeaders());
    // mutate the content-type header when necessary
  }

  @Override
  public HttpHeaders getHeaders() {
    return this.httpHeaders;
  }
}

Note that you should only use that client in that context (for this host). I'd strongly suggest to try and fix that strange content-type returned by the server, if you can.

Massarelli answered 1/2, 2018 at 8:58 Comment(4)
Second option doesn't work. UnsupportedOperationException trying to modify a unmodifiable map Can you elaborate on your first option?Clo
I've updated my answer for solution 2) - you can decorate the client response to avoid that problem. I've also pointed to the right build method for changing the exchange strategies.Massarelli
MyClientHttpResponseDecorator decorated = new MyClientHttpResponseDecorator(response); But it seems that the response isn't a ClientHttpResponse.Overlying
Second solution doesnt't work liek Q_SJ said "response" seems not to be a ClientHttpResponse.Expellant
C
1

If you need to set the maxInMemorySize along with text/html response use:

  WebClient invoicesWebClient() {
    return WebClient.builder()
        .exchangeStrategies(ExchangeStrategies.builder().codecs(this::acceptedCodecs).build())
        .build();
  }

  private void acceptedCodecs(ClientCodecConfigurer clientCodecConfigurer) {
    clientCodecConfigurer.defaultCodecs().maxInMemorySize(BUFFER_SIZE_16MB);
    clientCodecConfigurer.customCodecs().registerWithDefaultConfig(new Jackson2JsonDecoder(new ObjectMapper(), TEXT_HTML));
    clientCodecConfigurer.customCodecs().registerWithDefaultConfig(new Jackson2JsonEncoder(new ObjectMapper(), TEXT_HTML));
  }
Cockspur answered 4/10, 2021 at 10:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.