簡體   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