Spring reactive WebClient GET json response with Content-Type "text/plain;charset=UTF-8"

Posted

技术标签:

【中文标题】Spring reactive WebClient GET json response with Content-Type "text/plain;charset=UTF-8"【英文标题】: 【发布时间】:2020-07-30 03:33:39 【问题描述】:

我在使用 Spring 5 响应式 WebClient 时遇到问题,当我请求一个端点,该端点返回格式正确的 json 响应,内容类型为“text/plain;charset=UTF-8”。 例外是

org.springframework.web.reactive.function.UnsupportedMediaTypeException:
Content type 'text/plain;charset=UTF-8' not supported for bodyType=MyDTOClass

这是我提出请求的方式:

webClient.get().uri(endpoint).retrieve().bodyToFlux(MyDTOClass.class)

编辑:标题已“正确”设置(接受、内容类型),我尝试了不同的内容类型(json、json + UTF8、纯文本、纯文本 + UTF8)组合,没有成功。我认为问题是.bodyToFlux(MyDTOClass.class) 不知道如何将“文本”翻译成MyDTOClass 对象。 如果我将请求更改为:

webClient.get().uri(endpoint).retrieve().bodyToFlux(String.class)

我可以读取字符串。

EDIT 2:下一句摘自 Spring 文档 (https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-codecs-jackson)

默认情况下Jackson2EncoderJackson2Decoder 都不支持 String 类型的元素。相反,默认假设是 字符串或字符串序列表示序列化的 JSON 内容,以 由CharSequenceEncoder 呈现。如果你需要的是渲染 来自Flux<String> 的 JSON 数组,使用 Flux#collectToList() 并编码 Mono<List<String>>.

我认为解决方案是定义一个新的解码器/读取器,以便将字符串转换为 MyDTOClass,但我不知道该怎么做。

【问题讨论】:

你可以试试this.webClient = WebClient.builder() .baseUrl(clientUrl) .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_UTF8_VALUE) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE) .build(); 这个问题与我的请求头无关,我已经在帖子中解释过了。 您是否尝试将响应映射为字符串并将其打印出来仅用于调试目的?只是为了确定会发生什么作为回应。还有一件事端点返回的是什么?就像端点返回的响应真的可以映射到您的 DTO 中? 您也可以在这里发布您的 DTO 课程吗? 是的,如果我使用 .bodyToFlux(String.class) 并且我的 DTO 与响应完全匹配,我可以读取字符串。我手动将响应提取到资源目录中名为 response.json 的文件中,然后我将网络客户端指向localhost/response.json 可以毫无问题地读取它,如果我将文件扩展名更改为response.txt 它会引发相同的异常。 【参考方案1】:

为 webclient 设置内容类型。

webClient.get()
            .uri(endpoint)
           .contentType(MediaType.APPLICATION_JSON_UTF8)

【讨论】:

它不能解决问题。我在帖子中添加了更多信息。【参考方案2】:

如果有人需要,这里是解决方案:

这个答案 (https://***.com/a/57046640/13333357) 是关键。我们必须添加一个自定义解码器来指定反序列化响应的内容和方式。

但我们要记住这一点:类级别注解@JsonIgnoreProperties默认设置为json映射器,对其他映射器没有影响。因此,如果您的 DTO 不匹配所有响应“json”属性,则反序列化将失败。

以下是如何配置 ObjectMapper 和 WebClient 以从文本响应中反序列化 json 对象:

...
WebClient.builder()
        .baseUrl(url)
        .exchangeStrategies(ExchangeStrategies.builder().codecs(configurer ->
                ObjectMapper mapper = new ObjectMapper();
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                configurer.customCodecs().decoder(new Jackson2JsonDecoder(mapper, MimeTypeUtils.parseMimeType(MediaType.TEXT_PLAIN_VALUE)));
                ).build())
        .build();
...

干杯!

【讨论】:

【参考方案3】:

根据上述 Charlie 的 this answer,您现在可以添加额外的“编解码器”而无需替换它们。

你也可以通过Jackson2ObjectMapperBuilder.json().build()轻松构建Spring默认配置的ObjectMapper

这是一个重用内置 Jackson2JsonDecoder 中的 ObjectMapper 的示例

var webClient = webClientBuilder
    .baseUrl(properties.getBaseUrl())
    .codecs(configurer -> 
        // This API returns JSON with content type text/plain, so need to register a custom
        // decoder to deserialize this response via Jackson
        
        // Get existing decoder's ObjectMapper if available, or create new one
        ObjectMapper objectMapper = configurer.getReaders().stream()
            .filter(reader -> reader instanceof Jackson2JsonDecoder)
            .map(reader -> (Jackson2JsonDecoder) reader)
            .map(reader -> reader.getObjectMapper())
            .findFirst()
            .orElseGet(() -> Jackson2ObjectMapperBuilder.json().build());
        
        Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(objectMapper, MediaType.TEXT_PLAIN);
        configurer.customCodecs().registerWithDefaultConfig(decoder);
    )
    .build();

【讨论】:

以上是关于Spring reactive WebClient GET json response with Content-Type "text/plain;charset=UTF-8"的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Security OAuth 时 Spring Boot Reactive WebClient “serverWebExchange must be null”

spring 5 webclient使用指南

Spring reactive WebClient GET json response with Content-Type "text/plain;charset=UTF-8"

Spring Webflux Webclient |内容类型标题设置问题

Spring WebClient vs. RestTemplate

Spring WebClient 作为 RestTemplate 的替代品