Gestion des exceptions Spring Resttemplate


124

Voici l'extrait de code; en gros, j'essaye de propager l'exception lorsque le code d'erreur est autre que 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();
            }

Cependant, dans le cas d'une réponse 500 du serveur, j'obtiens l'exception

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]

Ai-je vraiment besoin d'encapsuler la méthode d'échange de modèles rest dans try? Quel serait alors le but des codes?


Veuillez partager le code d'ApplicationException ()
Mudassar

Réponses:


128

Vous souhaitez créer une classe qui implémente ResponseErrorHandler, puis utiliser une instance de celle-ci pour définir la gestion des erreurs de votre modèle de repos:

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());
}

De plus, Spring a la classe DefaultResponseErrorHandler, que vous pouvez étendre au lieu d'implémenter l'interface, au cas où vous ne voudriez que remplacer la handleErrorméthode.

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

Jetez un œil à son code source pour avoir une idée de la façon dont Spring gère les erreurs HTTP.


1
J'ai 1 instance de RestTemplate que je réutilise pour différents appels. J'ai besoin de gérer les erreurs de différents appels différemment - apparemment il n'y a aucun moyen de le faire avec le gestionnaire global - je dois fournir un gestionnaire par requête.
mvmn le

4
Avec ce gestionnaire d'erreurs, j'obtiens toujours un ResourceAccessExceptioncar RestTemplate.doExecuteattrape IOExceptions et jette un ResourceAccessException. Qu'est-ce que je fais mal?
Federico Bellucci

J'ai pu résoudre ce problème en étendant DefaultResponseErrorHandler.
Crenguta S

48

Vous devriez attraper une HttpStatusCodeExceptionexception:

try {
    restTemplate.exchange(...);
} catch (HttpStatusCodeException exception) {
    int statusCode = exception.getStatusCode().value();
    ...
}

37
OMI, la réponse doit toujours être accompagnée d'un code d'état approprié, sinon quel est le but des codes.
vaibhav le

5
Je ne suis pas sûr de comprendre l'objection @vaibhav: attraper HttpStatusCodeException n'est pas pour un code erroné, mais parce que dans de nombreux cas, une exception est toujours levée et donc votre if (code == valeur) ne peut jamais être exécuté.
Stefano Scarpanti

1
Les exceptions sont très coûteuses en Java. C'est OK pour les causes occasionnelles et inattendues (d'où le nom), mais par-dessus cela, vous devriez plutôt chercher d'autres solutions.
Agoston Horvath

11
"Très coûteux"? Plus coûteux que, par exemple, passer un appel HTTP?
IcedDante

4
@RaffaelBecharaRameh - HttpStatusCodeException .getResponseBodyAsString () ou HttpStatusCodeException.getResponseBodyAsByteArray ().
Dave

45

Spring traite intelligemment les codes d'erreur http comme des exceptions et suppose que votre code de gestion des exceptions a le contexte pour gérer l'erreur. Pour que l'échange fonctionne comme vous l'attendiez, procédez comme suit:

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

Cela renverra tous les résultats attendus de la réponse.


2
vous devez utiliser un HttpClient différent du SDK par défaut, pour obtenir le corps de la réponse pour les erreurs
rasoir

26

Une autre solution est celle décrite ici à la fin de cet article par "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 
}

4
vous devez utiliser un HttpClient différent du SDK par défaut, pour obtenir le corps de la réponse pour les erreurs (par exemple apache commons HttpClient)
rasoir

17

Spring vous extrait de la très très très grande liste de codes d'état http. C'est l'idée des exceptions. Jetez un œil à la hiérarchie org.springframework.web.client.RestClientException:

Vous disposez de plusieurs classes pour cartographier les situations les plus courantes lors du traitement des réponses http. La liste des codes http est vraiment grande, vous ne voudrez pas écrire de code pour gérer chaque situation. Mais par exemple, jetez un œil à la sous-hiérarchie HttpClientErrorException. Vous avez une seule exception pour mapper tout type d'erreur 4xx. Si vous avez besoin d'aller en profondeur, vous le pouvez. Mais en attrapant simplement HttpClientErrorException, vous pouvez gérer toute situation où des données incorrectes ont été fournies au service.

Le DefaultResponseErrorHandler est vraiment simple et solide. Si le code d'état de la réponse ne fait pas partie de la famille 2xx, il renvoie simplement true pour la méthode hasError.


Mec, merci pour l'explication. Comment avez-vous construit cet arbre avec une hiérarchie d'exceptions?
autonome le

1
Hé mec, j'ai utilisé Eclipse. Appuyez simplement sur Ctrl + Maj + T pour ouvrir le chercheur de type et tapez RestClientException. Double-cliquez sur RestClientException à partir des résultats, Eclipse ouvrira cette classe pour vous. Ensuite, placez le curseur de la souris sur le nom de la classe (où il est dit "classe publique RestClientException ...", et appuyez sur Ctrl + T. Vous verrez cette hiérarchie.
Perimosh

est-ce que tu l'as essayé?
Perimosh

1
Btw dans Intellij c'est: cliquez sur la classe dans l'arborescence du projet et Ctrl + Alt + U, ou cliquez avec le bouton droit de la souris -> Construire un diagramme
autonome

3

Si vous utilisez un mécanisme de pooling (usine client http) ou d'équilibrage de charge (eureka) avec votre RestTemplate, vous n'aurez pas le luxe de créer un new RestTemplatepar classe. Si vous appelez plus d'un service, vous ne pouvez pas utiliser setErrorHandlercar if serait globalement utilisé pour toutes vos demandes.

Dans ce cas, attraper le HttpStatusCodeExceptionsemble être la meilleure option.

La seule autre option dont vous disposez est de définir plusieurs RestTemplateinstances à l'aide de l' @Qualifierannotation.

De plus, mais c'est mon goût personnel, j'aime que ma gestion des erreurs soit étroitement liée à mes appels.


3

J'ai géré cela comme ci-dessous:

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

1

Le code d'échange est ci-dessous :

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

L'exception RestClientExceptiona HttpClientErrorExceptionet l' HttpStatusCodeExceptionexception.

Donc, RestTempletelà-dedans peut se produire HttpClientErrorExceptionet HttpStatusCodeExceptionexception. Dans l'objet d'exception, vous pouvez obtenir un message d'erreur exact de cette manière:exception.getResponseBodyAsString()

Voici l'exemple de code :

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;
    }

Voici la description du code :

Dans cette méthode, vous devez passer la classe de demande et de réponse. Cette méthode analysera automatiquement la réponse en tant qu'objet demandé.

Tout d'abord, vous devez ajouter un convertisseur de message.

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

Ensuite, vous devez ajouter requestHeader. Voici le code:

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

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

Enfin, vous devez appeler la méthode d'échange:

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

Pour l'impression prety, j'ai utilisé la bibliothèque Gson. voici le gradle:compile 'com.google.code.gson:gson:2.4'

Vous pouvez simplement appeler le code ci-dessous pour obtenir une réponse:

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

Voici le code de travail complet :

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);
    }
}

Merci :)


2
'org.springframework.web.client.HttpClientErrorException' est une sous-classe de 'org.springframework.web.client.HttpStatusCodeException'. Vous n'avez pas besoin d'attraper HttpClientErrorException si vous interceptez déjà HttpStatusCodeException et ne faites rien de différent pour gérer les deux exceptions ci-dessus.
The-Proton-Resurgence

0

Une solution très simple peut être:

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());
}

-1

Voici ma méthode POST avec HTTPS qui renvoie un corps de réponse pour tout type de mauvaises réponses.

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;
}

3
vous devez utiliser un HttpClient différent du SDK par défaut, pour obtenir le corps de la réponse pour les erreurs (par exemple apache commons HttpClient)
rasoir
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.