使用 Spring 的 RestClient 优雅地处理 404 错误

问题描述 投票:0回答:1

我目前正在使用 Spring Boot 3.2/Spring Framework 5.1 中的新 RestClient,并且在处理 404 错误方面遇到了挑战。我的目标是优雅地处理这些错误,而不导致代码中的后续步骤失败,特别是在转换响应正文时。

示例:

class CmsClient {
    pubic Policy getPolicy() {
        return restClient.get()
            .uri("some/url")
            .accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .onStatus(ignore404())
            .body(Policy.class);
    }


    public static ResponseErrorHandler ignore404() {
        return new ResponseErrorHandler() {

            @Override
            public boolean hasError(final ClientHttpResponse response) throws IOException {
                return response.getStatusCode() == HttpStatus.NOT_FOUND;
            }

            @Override
            public void handleError(final ClientHttpResponse response) {
                //Do nothing
            }
        };
    }
}

这是我得到的例外:

org.springframework.web.client.RestClientException: Error while extracting response for type [package.Policy] and content type [application/json]
    at org.springframework.web.client.DefaultRestClient.readWithMessageConverters(DefaultRestClient.java:216)
    at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.readBody(DefaultRestClient.java:641)
    at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.body(DefaultRestClient.java:589)
    at package.CmsClient.getPolicy(CmsClient.java:27)
    at package.CmsClientTest.getPolicy_404_ThrowException_Test(CmsClientTest.java:61)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: No content to map due to end-of-input
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:406)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:354)
    at org.springframework.web.client.DefaultRestClient.readWithMessageConverters(DefaultRestClient.java:200)
    ... 7 more
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
 at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 0]
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
    at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1752)
    at com.fasterxml.jackson.databind.ObjectReader._initForReading(ObjectReader.java:360)
    at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2095)
    at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1481)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:395)
    ... 9 more

我预计方法

.body(Policy.class);
应该返回null,而不是由于MismatchedInputException而触发异常。此行为与之前在 WebClient 中观察到的行为一致,我期待在这种情况下采用类似的方法。

目前的解决方案:作为一种临时措施,可以在body方法调用中指定String.class。然而,这种方法会损害代码的流动性和易用性,需要使用 ObjectMapper 和类似工具进行额外处理。

java spring rest httpclient
1个回答
0
投票

我通过注册自定义消息转换器解决了这个问题:

public RestClient create(String baseUrl, Duration timeout) {
        return RestClient.builder()
            .baseUrl("...")
            //addFirst is important
            .messageConverters(converterList -> converterList.addFirst(new JsonMessageConverter()))
            .build();
    }

这是我的自定义消息转换器:

import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

class JsonMessageConverter extends MappingJackson2HttpMessageConverter {

    @Override
    public Object read(@NotNull Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        var bytes = inputMessage.getBody().readAllBytes();
        if (bytes.length == 0) {
            return null;
        }

        return super.read(type, contextClass, new HttpInputMessageWrapper(new ByteArrayInputStream(bytes), inputMessage.getHeaders()));
    }

    private static class HttpInputMessageWrapper implements HttpInputMessage {
        private final InputStream body;
        private final HttpHeaders headers;

        private HttpInputMessageWrapper(InputStream body, HttpHeaders headers) {
            this.body = body;
            this.headers = headers;
        }

        @Override
        public @NotNull InputStream getBody() {
            return this.body;
        }

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

然后,我添加我在问题中发布的错误处理程序以优雅地处理 404:

    Optional<Policy> fetchDataRemotly() {
        var policy = restClient.get()
            .uri("...")
            .accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .onStatus(RestClientFactory.ignore404())
            .body(Policy.class);

        /*
         * Variable 'policy' will be null if we get 404.
         */
        return Optional.ofNullable(policy);
    }
© www.soinside.com 2019 - 2024. All rights reserved.