org.springframework.web.reactive.function.UnsupportedMediaTypeException: bodyType= 不支持内容类型 'text/html;charset=iso-8859-1'

org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/html;charset=iso-8859-1' not supported for bodyType=

使用Java11、Springt Boot WebClient

创建了一个 REST Web 服务(服务 1),它尝试使用一个 REST Web 服务(服务 2),预计 return JSON 将映射到我的自定义响应对象。但是因为要消费的服务(服务2)暂时不在线我会收到404 not found.

问题似乎是 404 响应以 'text/html;charset=iso-8859-1' 的形式出现,无法映射到我的 DocumentResponse 对象上。

Web 客户端配置为接受 xml 和 json。

如何拦截和处理那些 http 状态响应而不会出现此异常。

通常我会在我的请求链上收到 using.block() 的响应,并为正在使用该响应的服务 1 创建适当的响应。

例如:

Documentresponse response = invoiceDeliveryControllerApi.deliverInvoiceUsingPOST(request,"test").block(); 
public Mono<Documentresponse> deliverInvoiceUsingPOST(Documentrequest request, String name) throws WebClientResponseException {
        Object postBody = request;
        // verify the required parameter 'request' is set
        if (request == null) {
            throw new WebClientResponseException("Missing the required parameter 'request' when calling deliverInvoiceUsingPOST", HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), null, null, null);
        }
        // create path and map variables
        final Map<String, Object> pathParams = new HashMap<String, Object>();

        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders headerParams = new HttpHeaders();
        final MultiValueMap<String, String> cookieParams = new LinkedMultiValueMap<String, String>();
        final MultiValueMap<String, Object> formParams = new LinkedMultiValueMap<String, Object>();

        queryParams.putAll(apiClient.parameterToMultiValueMap(null, "name", name));

        final String[] localVarAccepts = { 
            "application/xml", "text/xml"
        };
        final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
        final String[] localVarContentTypes = { 
            "application/xml", "text/xml"
        };
        final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);

        String[] localVarAuthNames = new String[] {  };

        ParameterizedTypeReference<Documentresponse> localVarReturnType = new ParameterizedTypeReference<Documentresponse>() {};
        return apiClient.invokeAPI("/api/v1/deliverinvoice", HttpMethod.POST, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
    }
 public <T> Mono<T> invokeAPI(String path, HttpMethod method, Map<String, Object> pathParams, MultiValueMap<String, String> queryParams, Object body, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams, MultiValueMap<String, Object> formParams, List<MediaType> accept, MediaType contentType, String[] authNames, ParameterizedTypeReference<T> returnType) throws RestClientException {
        final WebClient.RequestBodySpec requestBuilder = prepareRequest(path, method, pathParams, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames);
        return requestBuilder.retrieve().bodyToMono(returnType);
    }
   public static WebClient buildWebClient(ObjectMapper mapper) {
        ExchangeStrategies strategies = ExchangeStrategies
            .builder()
            .codecs(clientDefaultCodecsConfigurer -> {
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper, MediaType.APPLICATION_JSON));
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper, MediaType.APPLICATION_JSON));
            }).build();
        WebClient.Builder webClient = WebClient.builder().filters(exchangeFilterFunctions -> {
            exchangeFilterFunctions.add(logRequest());
            exchangeFilterFunctions.add(logResponse());
        }).exchangeStrategies(strategies);
        return webClient.build();
    }

现在我想找到一种方法来对那些 html 响应做出反应,并为服务 1 创建一个响应,告诉用户在调用服务 2 时出现了 http 连接错误

有什么建议吗?

PS:客户端代码是使用 openapi 代码生成器生成的,并针对日志目的进行了微调。

编辑:我发现通常它就像在 .block() 中捕获 WebClientException 一样简单,并且使用状态代码我可以生成令人满意的响应消息。

但是我发现一个奇怪的行为是,如果服务 2 的响应是 f.e。 400 Bad Request 它作为 text/html 交付,没有添加 charset=iso-8859-1。

在这种情况下,它会导致预期的 WebClientResponseException。但是对于 404 not found 的实例 text/html charset=iso-8859-1 被传递并导致 UnexpectedMediaTypeException。有没有办法解释为什么?

更令我困惑的是,在预期的 404 情况下,我通过 WebClientFilters 进行的日志记录提供了状态 307 重定向(到我首先调用的相同 url)和 302 发现 text/html charset=iso-8859-1 也导致 MediaTypeException。虽然通过 Postman 使用完全相同的数据进行相同的调用,但发送 404。有人知道发生了什么吗?

来自 WebBlient#bodyToMono java 文档:

Extract the body to a {@code Mono}. By default, if the response has status code 4xx or 5xx, the {@code Mono} will contain a {@link WebClientException}. This can be overridden with {@link #onStatus(Predicate, Function)}.

根据状态代码实现错误处理:

requestBuilder
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, res -> Mono<? extends Throwable>)
            .onStatus(HttpStatus::is5xxServerError, res -> Mono<? extends Throwable>)
            .bodyToMono(responseType)
            .onErrorResume(Status400Throwable.class, th -> Mono.just(fallbackResponseHere)) 
            .onErrorResume(Status500Throwable.class, th -> Mono.just(fallbackResponseHere));

查看 WebClient.ResponseSpec#onStatus java 文档以及类似 Mono#onErrorResume 的内容,以防您不希望默认错误处理程序 return WebClientResponseException.

回答我自己的问题:

在预期的 404 状态代码的情况下,客户端给我 UnsupportedMediaTypeException 的奇怪行为不知何故是由重定向 307 后跟 302 found 引起的,这是由我使用 http url 而不是 https url.

我不知道为什么只有 404 状态代码才会出现这种情况。但是在将 baseURI 更改为 https 之后,我不再收到重定向或不支持的媒体类型异常,而是收到预期的 WEbClientResponseExceptions。

现在我将尝试使用 Serg Vasylchak 的答案来处理错误而不捕获异常。