Estoy consumiendo una RESTful JSON API usando Spring’s RestTemplate
y Jackson. En algunos casos, es posible que recibamos una respuesta del Status 401
(no autorizado) con un cuerpo JSON personalizado, definido por el fabricante de la API, y se parece a esto:
{ "code": 123, "message": "Reason for the error" }
Necesitamos analizar el cuerpo y usar la propiedad del code
en nuestra lógica de negocios.
Este es el objeto Java de respuesta de error que necesitamos analizar para:
public class CustomError { @JsonProperty private Integer code; @JsonProperty private String message; public Integer getCode() { return code; } public String getMessage() { return message; } }
Y un controlador de errores personalizado para hacer esto:
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); } } }
El controlador de errores falla con una HttpMessageNotReadableException
en el bloque try:
“No se pudo leer JSON: no se puede reintentar debido a la autenticación del servidor, en modo de transmisión”
Así es como estoy enviando solicitudes:
restTemplate.postForObject(url, pojoInstance, responseClass);
Si se ejecuta la misma solicitud con un progtwig de cliente de descanso anterior, como Postman, se recibe la respuesta JSON esperada. Entonces, asumo que el problema podría estar en la implementación ClientHttpResponse
de Spring que de alguna manera no permite el acceso al cuerpo de respuesta, en caso del estado 401.
¿Es realmente posible analizar el cuerpo de respuesta?
Actualizar
Por lo que he investigado, la clase RestTemplate
utiliza ClientHttpResponse
que a su vez crea un sun.net.www.protocol.http.HttpURLConnection
que proporciona el flujo de entrada. Es allí, donde se descuida el flujo de entrada y se lanza una IOException
:
no se puede volver a intentar debido a la autenticación del servidor, en modo de transmisión
Por lo tanto, la implementación de HttpURLConnection
está causando el problema.
¿Será posible evitar este problema? ¿Quizás deberíamos usar una implementación alternativa que no ignore el cuerpo de la respuesta en caso de un código de estado de error? ¿Puedes recomendar alguna alternativa?
Pruebe el siguiente enfoque sin necesidad de un controlador personalizado. La idea es obtener la respuesta como una cadena de la excepción HttpStatusCodeException, y luego puede convertirla en su objeto. Para la conversión utilicé el ObjectMapper de Jackson:
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); } }
Actualización: el uso de una fábrica diferente también puede ayudar, ya que hay un error en la predeterminada relacionado con su problema (vea el comentario a continuación):
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());