如何在 Java 中解析响应正文,当 HTTP 请求具有返回状态 401 时

2022-09-01 03:14:07

我正在使用Spring's和Jackson使用RESTful JSON API。在某些情况下,我们可能会收到一个(未经授权的)响应,其中包含由 API 制造商定义的自定义 JSON 正文,如下所示:RestTemplateStatus 401

{
    "code": 123,
    "message": "Reason for the error"
}

我们需要解析正文,并在业务逻辑中使用该属性。code

这是我们需要解析的 Java 对象的错误响应:

public class CustomError {

    @JsonProperty
    private Integer code;
    @JsonProperty
    private String message;

    public Integer getCode() {
       return code;
    }
    public String getMessage() {
        return message;
    }
}

以及一个自定义错误处理程序来执行此操作:

public class CustomErrorHandler extends DefaultResponseErrorHandler {
    private RestTemplate restTemplate;
    private ObjectMapper objectMapper;
    private MappingJacksonHttpMessageConverter messageConverter;


    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        return super.hasError(response);
    }

    @Override
    public void handleError(final ClientHttpResponse response) throws IOException {

        try {
            CustomError error = 
                (CustomError) messageConverter.read(CustomError.class, response);
            throw new CustomErrorIOException(error, error.getMessage());
        } catch (Exception e) {
            // parsing failed, resort to default behavior
            super.handleError(response);
        }
    }
}

错误处理程序失败,在 try 块中出现 :HttpMessageNotReadableException

“无法读取 JSON:在流模式下,由于服务器身份验证而无法重试”

这就是我发送请求的方式:

restTemplate.postForObject(url, pojoInstance, responseClass);

如果使用普通的旧 rest 客户端程序(如 Postman)执行相同的请求,则会收到预期的 JSON 响应。因此,我认为问题可能出在Spring的实现中,在401状态的情况下,它以某种方式不允许访问响应机构。ClientHttpResponse

是否确实可以解析响应正文?

更新

根据我的调查,该类使用它反过来创建一个提供输入流的。它就在那里,输入流被忽略并抛出:RestTemplateClientHttpResponsesun.net.www.protocol.http.HttpURLConnectionIOException

在流式处理模式下,由于服务器身份验证而无法重试

因此,的实现导致了此问题。HttpURLConnection

是否有可能避免此问题?也许我们应该使用一种替代实现,在出现错误状态代码时不会忽略响应正文?您能推荐任何替代方案吗?


答案 1

请尝试以下方法,而无需自定义处理程序。这个想法是从HttpStatusCodeException获取字符串形式的响应,然后你可以将其转换为你的对象。对于转换,我使用了Jackson的ObjectMapper:

        try {

            restTemplate.postForObject(url, pojoInstance, responseClass);

        } catch (HttpStatusCodeException e) {

            if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {

                String responseString = e.getResponseBodyAsString();

                ObjectMapper mapper = new ObjectMapper();

                CustomError result = mapper.readValue(responseString,
                        CustomError.class);
            }
        }

更新:使用不同的工厂也可能有所帮助,因为默认工厂中存在与您的问题相关的错误(请参阅下面的评论):

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

答案 2

我是这样做的:

@Component
public class RestTemplateFactory {
public enum Type {
    JSON, XML
}

public RestTemplate create(Type type) {
    RestTemplate restTemplate = new RestTemplate();
    if (type == Type.XML) {
        Jaxb2RootElementHttpMessageConverter jaxbMessageConverter = new Jaxb2RootElementHttpMessageConverter();
        jaxbMessageConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.TEXT_HTML, MediaType.APPLICATION_XML));
        restTemplate.setMessageConverters(Lists.newArrayList(jaxbMessageConverter));
    }
    restTemplate.setErrorHandler(new BpmRestErrorHandler(restTemplate.getMessageConverters()));
    return restTemplate;
}

public HttpHeaders contentHeaders(Type type) {
    HttpHeaders headers = new HttpHeaders();
    if (type == Type.XML) {
        headers.setContentType(MediaType.APPLICATION_XML);
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
    } else {
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
    }
    return HttpHeaders.readOnlyHttpHeaders(headers);
}
}

和处理程序:

public class BpmRestErrorHandler extends DefaultResponseErrorHandler {

private final List<HttpMessageConverter<?>> messageConverters;

public BpmRestErrorHandler(List<HttpMessageConverter<?>> messageConverters) {
    this.messageConverters = messageConverters;
}

@Override
public void handleError(ClientHttpResponse response) throws IOException {
    for (HttpMessageConverter messageConverter : messageConverters) {
        if (messageConverter.canRead(RestRuntimeException.class, response.getHeaders().getContentType())) {
            RestRuntimeExceptionData exceptionData =
                    (RestRuntimeExceptionData)messageConverter.read(RestRuntimeException.class, response);
            throw new BpmRestException(exceptionData);
        }
    }
    super.handleError(response);
}
}

我的自定义 WebFault 对象在哪里。它重用 RestTemplate 的 HttpConverters。RestRuntimeExceptionData


推荐