[英]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.