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