繁体   English   中英

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

[英]How to parse the response body in Java, when the HTTP request has return status 401

我正在使用 Spring 的RestTemplate和 Jackson 来使用 RESTful JSON API。 在某些情况下,我们可能会收到带有自定义 JSON 主体的Status 401 (未授权)响应,该主体由 API 制造商定义,如下所示:

{
    "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);
        }
    }
}

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

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

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

restTemplate.postForObject(url, pojoInstance, responseClass);

如果使用普通的老式休息客户端程序(如 Postman)执行相同的请求,则会收到预期的 JSON 响应。 因此,我认为问题可能在于 Spring 的ClientHttpResponse实现以某种方式不允许访问响应正文,以防出现 401 状态。

真的可以解析响应体吗?

更新

根据我的调查, RestTemplate类使用ClientHttpResponse ,后者又创建了一个提供输入流的sun.net.www.protocol.http.HttpURLConnection 在那里,输入流被忽略并抛出IOException

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

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

有可能避免这个问题吗? 也许我们应该使用一种替代实现,在出现错误状态代码时不会忽略响应主体? 你能推荐任何替代品吗?

无需自定义处理程序即可尝试以下方法。 这个想法是从 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());

我是这样做的:

@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);
}
}

其中RestRuntimeExceptionData是我的自定义 WebFault 对象。 它重用了 RestTemplate 的 HttpConverters。

我只将clientHttpResponse与映射器一起使用。 ClientHttpResponse.getBody()是类似于流HttpStatusCodeException.getResponseBodyAsString

@Component
public class xhandler extends RestTemplateErrorHandler{


    @Override
    protected boolean handleServiceSpecificError(ClientHttpResponse response) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            CustomError error =mapper.readValue(response.getBody(), CustomError.class);
            throw new RestClientException(“x Service returned an error response with error-code: "+response.getStatusCode().toString() + error.getErrorMessage());
        } catch (IOException e) {
            LOG.error(create(this.getClass(), "002"), “x Service returned an error, but the error response could not be parsed: {}", e.toString(), e);
        }
        return false;
    }

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM