Manejo de excepciones de Spring Resttemplate

124

A continuación se muestra el fragmento de código; Básicamente, estoy tratando de propagar la excepción cuando el código de error no es 200.

ResponseEntity<Object> response = restTemplate.exchange(url.toString().replace("{version}", version),
                    HttpMethod.POST, entity, Object.class);
            if(response.getStatusCode().value()!= 200){
                logger.debug("Encountered Error while Calling API");
                throw new ApplicationException();
            }

Sin embargo, en el caso de una respuesta 500 del servidor, obtengo la excepción

org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94) ~[spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]

¿Realmente necesito envolver el método de intercambio de plantilla restante en el intento? ¿Cuál sería entonces el propósito de los códigos?

vaibhav
fuente
Por favor comparta el código de ApplicationException ()
Mudassar

Respuestas:

128

Desea crear una clase que implemente ResponseErrorHandlery luego usar una instancia de ella para configurar el manejo de errores de su plantilla de descanso:

public class MyErrorHandler implements ResponseErrorHandler {
  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    // your error handling here
  }

  @Override
  public boolean hasError(ClientHttpResponse response) throws IOException {
     ...
  }
}

[...]

public static void main(String args[]) {
  RestTemplate restTemplate = new RestTemplate();
  restTemplate.setErrorHandler(new MyErrorHandler());
}

Además, Spring tiene la clase DefaultResponseErrorHandler, que puede extender en lugar de implementar la interfaz, en caso de que solo desee anular el handleErrormétodo.

public class MyErrorHandler extends DefaultResponseErrorHandler {
  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    // your error handling here
  }
}

Eche un vistazo a su código fuente para tener una idea de cómo Spring maneja los errores HTTP.

cochera
fuente
1
Tengo 1 instancia de RestTemplate que reutilizo para diferentes llamadas. Necesito manejar los errores de diferentes llamadas de manera diferente; aparentemente, no hay forma de hacerlo con el controlador global; necesito proporcionar un controlador por solicitud.
mvmn
4
Con este controlador de errores siempre obtengo un ResourceAccessExceptionporque RestTemplate.doExecuteatrapa IOExceptionsy lanza un ResourceAccessException. ¿Qué estoy haciendo mal?
Federico Bellucci
Pude resolver esto extendiendo DefaultResponseErrorHandler.
Crenguta S
48

Debería detectar una HttpStatusCodeExceptionexcepción:

try {
    restTemplate.exchange(...);
} catch (HttpStatusCodeException exception) {
    int statusCode = exception.getStatusCode().value();
    ...
}
mekazu
fuente
37
En mi opinión, la respuesta siempre debe venir con un código de estado apropiado, de lo contrario, ¿cuál es el propósito de los códigos?
vaibhav
5
No estoy seguro de entender la objeción de @vaibhav: capturar HttpStatusCodeException no es para un código incorrecto, sino porque en muchos casos siempre se lanza una excepción y, por lo tanto, su if (código == valor) nunca se puede ejecutar.
Stefano Scarpanti
1
Las excepciones son muy costosas en Java. Está bien para las causas ocasionales e inesperadas (de ahí el nombre), pero por encima de eso, debe buscar otras soluciones en su lugar.
Agoston Horvath
11
"Muy costoso"? ¿Más costoso que, digamos, hacer una llamada HTTP?
IcedDante
4
@RaffaelBecharaRameh - HttpStatusCodeException .getResponseBodyAsString () o HttpStatusCodeException.getResponseBodyAsByteArray ().
Dave
45

Spring trata inteligentemente los códigos de error http como excepciones y asume que su código de manejo de excepciones tiene el contexto para manejar el error. Para que el intercambio funcione como lo esperaría, haga esto:

    try {
        return restTemplate.exchange(url, httpMethod, httpEntity, String.class);
    } catch(HttpStatusCodeException e) {
        return ResponseEntity.status(e.getRawStatusCode()).headers(e.getResponseHeaders())
                .body(e.getResponseBodyAsString());
    }

Esto devolverá todos los resultados esperados de la respuesta.

Austin Cherlo
fuente
2
necesita usar un HttpClient diferente al SDK predeterminado, para obtener el cuerpo de respuesta para los errores
razor
26

Otra solución es la que se describe aquí al final de esta publicación por "enlian": http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

try{
     restTemplate.exchange(...)
} catch(HttpStatusCodeException e){
     String errorpayload = e.getResponseBodyAsString();
     //do whatever you want
} catch(RestClientException e){
     //no response payload, tell the user sth else 
}
Investigador
fuente
4
necesita usar un HttpClient diferente al SDK predeterminado, para obtener el cuerpo de respuesta para los errores (por ejemplo, apache commons HttpClient)
razor
17

Spring lo abstrae de la lista muy grande de código de estado http. Esa es la idea de las excepciones. Eche un vistazo a la jerarquía de org.springframework.web.client.RestClientException:

Tiene un montón de clases para mapear las situaciones más comunes cuando se trata de respuestas http. La lista de códigos http es muy grande, no querrá escribir código para manejar cada situación. Pero, por ejemplo, eche un vistazo a la subjerarquía HttpClientErrorException. Tiene una única excepción para asignar cualquier tipo de error 4xx. Si necesita profundizar, puede hacerlo. Pero con solo detectar HttpClientErrorException, puede manejar cualquier situación en la que se proporcionaron datos incorrectos al servicio.

DefaultResponseErrorHandler es realmente simple y sólido. Si el código de estado de respuesta no es de la familia 2xx, solo devuelve verdadero para el método hasError.

Perimosh
fuente
Amigo, gracias por la explicación. ¿Cómo construiste este árbol con jerarquía de excepciones?
independiente
1
Oye, usé Eclipse. Simplemente presione control + shift + T para abrir el buscador de tipos y escriba RestClientException. Haga doble clic en RestClientException de los resultados, Eclipse abrirá esa clase para usted. Luego, coloque el cursor del mouse sobre el nombre de la clase (donde dice "clase pública RestClientException ...", y presione control + T. Verá esa jerarquía.
Perimosh
¿Lo intentaste?
Perimosh
1
Por cierto en Intellij es: haga clic en la clase en el árbol del proyecto y Ctrl + Alt + U, o haga clic con el botón derecho del mouse -> Crear diagrama
independiente
3

Si utiliza un mecanismo de agrupación (fábrica de clientes http) o de equilibrio de carga (eureka) con su RestTemplate, no podrá darse el lujo de crear un new RestTemplatepor clase. Si llama a más de un servicio, no puede usarlo setErrorHandlerporque se usaría globalmente para todas sus solicitudes.

En este caso, la captura HttpStatusCodeExceptionparece ser la mejor opción.

La única otra opción que tiene es definir varias RestTemplateinstancias usando la @Qualifieranotación.

Además, pero este es mi propio gusto, me gusta que mi manejo de errores se adapte perfectamente a mis llamadas.

Hannes
fuente
3

He manejado esto de la siguiente manera:

try {
  response = restTemplate.postForEntity(requestUrl, new HttpEntity<>(requestBody, headers), String.class);
} catch (HttpStatusCodeException ex) {
  response = new ResponseEntity<String>(ex.getResponseBodyAsString(), ex.getResponseHeaders(), ex.getStatusCode());
}
Saikat
fuente
1

El código de intercambio está a continuación :

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
            HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException

Excepción RestClientExceptiontiene HttpClientErrorExceptiony HttpStatusCodeExceptionexcepción.

Así pues, en RestTempleteque puede aparecer dentro HttpClientErrorExceptiony HttpStatusCodeExceptionexcepción. En el objeto de excepción, puede obtener un mensaje de error exacto de esta manera:exception.getResponseBodyAsString()

Aquí está el código de ejemplo :

public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {

        printLog( "Url : " + url);
        printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));

        try {

            RestTemplate restTemplate = new RestTemplate();
            restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());


            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

            long start = System.currentTimeMillis();

            ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

            printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());


            printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));

            long elapsedTime = System.currentTimeMillis() - start;
            printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");

            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                return responseEntity.getBody();
            }

        } catch (HttpClientErrorException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }catch (HttpStatusCodeException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }
        return null;
    }

Aquí está la descripción del código :

En este método, debe pasar la clase de solicitud y respuesta. Este método analizará automáticamente la respuesta como objeto solicitado.

En primer lugar, debe agregar el convertidor de mensajes.

restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

Entonces tienes que agregar requestHeader. Aquí está el código:

HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

Finalmente, debes llamar al método de intercambio:

ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

Para la impresión prety utilicé la biblioteca Gson. aquí está el gradle:compile 'com.google.code.gson:gson:2.4'

Puede simplemente llamar al código de abajo para obtener respuesta:

ResponseObject response=new RestExample().callToRestService(HttpMethod.POST,"URL_HERE",new RequestObject(),ResponseObject.class);

Aquí está el código de trabajo completo :

import com.google.gson.GsonBuilder;
import org.springframework.http.*;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;


public class RestExample {

    public RestExample() {

    }

    public Object callToRestService(HttpMethod httpMethod, String url, Object requestObject, Class<?> responseObject) {

        printLog( "Url : " + url);
        printLog( "callToRestService Request : " + new GsonBuilder().setPrettyPrinting().create().toJson(requestObject));

        try {

            RestTemplate restTemplate = new RestTemplate();
            restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            restTemplate.getMessageConverters().add(new StringHttpMessageConverter());


            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<Object> entity = new HttpEntity<>(requestObject, requestHeaders);

            long start = System.currentTimeMillis();

            ResponseEntity<?> responseEntity = restTemplate.exchange(url, httpMethod, entity, responseObject);

            printLog( "callToRestService Status : " + responseEntity.getStatusCodeValue());


            printLog( "callToRestService Body : " + new GsonBuilder().setPrettyPrinting().create().toJson(responseEntity.getBody()));

            long elapsedTime = System.currentTimeMillis() - start;
            printLog( "callToRestService Execution time: " + elapsedTime + " Milliseconds)");

            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                return responseEntity.getBody();
            }

        } catch (HttpClientErrorException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }catch (HttpStatusCodeException exception) {
            printLog( "callToRestService Error :" + exception.getResponseBodyAsString());
            //Handle exception here
        }
        return null;
    }

    private void printLog(String message){
        System.out.println(message);
    }
}

Gracias :)

Md. Sajedul Karim
fuente
2
'org.springframework.web.client.HttpClientErrorException' es una subclase de 'org.springframework.web.client.HttpStatusCodeException'. No necesita detectar HttpClientErrorException si ya está detectando HttpStatusCodeException y no está haciendo nada diferente al manejar las dos excepciones anteriores.
The-Proton-Resurgence
0

Una solución muy sencilla puede ser:

try {
     requestEntity = RequestEntity
     .get(new URI("user String"));
    
    return restTemplate.exchange(requestEntity, String.class);
} catch (RestClientResponseException e) {
        return ResponseEntity.status(e.getRawStatusCode()).body(e.getResponseBodyAsString());
}
Vaibhav Sharma
fuente
-1

Aquí está mi método POST con HTTPS que devuelve un cuerpo de respuesta para cualquier tipo de respuesta incorrecta.

public String postHTTPSRequest(String url,String requestJson)
{
    //SSL Context
    CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    //Initiate REST Template
    RestTemplate restTemplate = new RestTemplate(requestFactory);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    //Send the Request and get the response.
    HttpEntity<String> entity = new HttpEntity<String>(requestJson,headers);
    ResponseEntity<String> response;
    String stringResponse = "";
    try {
        response = restTemplate.postForEntity(url, entity, String.class);
        stringResponse = response.getBody();
    }
    catch (HttpClientErrorException e)
    {
        stringResponse = e.getResponseBodyAsString();
    }
    return stringResponse;
}
vkrams
fuente
3
necesita usar un HttpClient diferente al SDK predeterminado, para obtener el cuerpo de respuesta para los errores (por ejemplo, apache commons HttpClient)
razor