Obsługa wyjątków Spring Resttemplate

124

Poniżej znajduje się fragment kodu; zasadniczo próbuję propagować wyjątek, gdy kod błędu jest inny niż 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();
            }

Jednak w przypadku odpowiedzi 500 z serwera otrzymuję wyjątek

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]

Czy naprawdę muszę wypróbować metodę wymiany szablonów reszt? Jaki byłby wówczas cel kodów?

vaibhav
źródło
Uprzejmie udostępnij kod ApplicationException ()
Mudassar

Odpowiedzi:

128

Chcesz utworzyć klasę, która implementuje, ResponseErrorHandlera następnie użyć jej instancji, aby ustawić obsługę błędów w szablonie REST:

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

Ponadto Spring ma klasę DefaultResponseErrorHandler, którą możesz rozszerzyć zamiast implementować interfejs, jeśli chcesz tylko przesłonić handleErrormetodę.

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

Spójrz na jego kod źródłowy, aby dowiedzieć się, jak Spring obsługuje błędy HTTP.

carcaret
źródło
1
Mam 1 wystąpienie RestTemplate, którego używam ponownie do różnych wywołań. Muszę inaczej obsługiwać błędy z różnych wywołań - najwyraźniej nie ma sposobu, aby to zrobić z globalną obsługą - muszę zapewnić procedurę obsługi na żądanie.
mvmn
4
Z tym programem obsługi błędów zawsze otrzymuję, ResourceAccessExceptionponieważ RestTemplate.doExecutełapie IOExceptionsi i rzuca ResourceAccessException. Co ja robię źle?
Federico Bellucci
Udało mi się to rozwiązać, rozszerzając DefaultResponseErrorHandler.
Crenguta S
48

Powinieneś złapać HttpStatusCodeExceptionwyjątek:

try {
    restTemplate.exchange(...);
} catch (HttpStatusCodeException exception) {
    int statusCode = exception.getStatusCode().value();
    ...
}
mekazu
źródło
37
IMO odpowiedź powinna zawsze zawierać odpowiedni kod statusu, w przeciwnym razie jaki jest cel kodów.
vaibhav
5
Nie jestem pewien, czy rozumiem zastrzeżenie @vaibhav: przechwytywanie HttpStatusCodeException nie dotyczy złego kodu, ale dlatego, że w wielu przypadkach wyjątek jest zawsze zgłaszany, więc twoje if (kod == wartość) nigdy nie może zostać wykonane.
Stefano Scarpanti,
1
Wyjątki w Javie są bardzo kosztowne. W przypadku sporadycznych, nieoczekiwanych przyczyn (stąd nazwa) jest OK, ale zamiast tego powinieneś poszukać innych rozwiązań.
Agoston Horvath
11
"Bardzo kosztowne"? Bardziej kosztowne niż, powiedzmy, wykonanie połączenia HTTP?
IcedDante
4
@RaffaelBecharaRameh - HttpStatusCodeException .getResponseBodyAsString () lub HttpStatusCodeException.getResponseBodyAsByteArray ().
Dave
45

Spring sprytnie traktuje kody błędów http jako wyjątki i zakłada, że ​​kod obsługi wyjątków ma kontekst do obsługi błędu. Aby wymiana działała tak, jak byś tego oczekiwał, wykonaj następujące czynności:

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

To zwróci wszystkie oczekiwane wyniki odpowiedzi.

austin cherlo
źródło
2
musisz użyć innego HttpClient niż domyślny zestaw SDK, aby uzyskać treść odpowiedzi dla błędów
razor
26

Innym rozwiązaniem jest to opisane tutaj na końcu tego posta przez „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 
}
Badacz
źródło
4
musisz użyć innego HttpClient niż domyślny SDK, aby uzyskać treść odpowiedzi dla błędów (na przykład apache commons HttpClient)
razor
17

Spring wyrywa cię z bardzo, bardzo dużej listy kodów statusu http. Taka jest idea wyjątków. Przyjrzyj się hierarchii org.springframework.web.client.RestClientException:

Masz kilka klas do mapowania najczęstszych sytuacji, gdy mamy do czynienia z odpowiedziami http. Lista kodów http jest naprawdę duża, nie będziesz chciał pisać kodu do obsługi każdej sytuacji. Ale na przykład spójrz na hierarchię podrzędną HttpClientErrorException. Masz jeden wyjątek do mapowania dowolnego rodzaju błędu 4xx. Jeśli chcesz wejść głęboko, możesz. Ale po prostu przechwytując HttpClientErrorException, możesz obsłużyć każdą sytuację, w której złe dane zostały dostarczone do usługi.

DefaultResponseErrorHandler jest naprawdę prosty i solidny. Jeśli kod statusu odpowiedzi nie pochodzi z rodziny 2xx, zwraca po prostu wartość true dla metody hasError.

Perimosh
źródło
Koleś, dzięki za wyjaśnienie. Jak zbudowałeś to drzewo z hierarchią wyjątków?
samodzielny
1
Hej stary, użyłem Eclipse. Po prostu naciśnij Ctrl + Shift + T, aby otworzyć wyszukiwarkę typów i wpisz RestClientException. Kliknij dwukrotnie RestClientException w wynikach, Eclipse otworzy dla Ciebie tę klasę. Następnie umieść kursor myszy na nazwie klasy (gdzie jest napisane „klasa publiczna RestClientException ...” i naciśnij control + T. Zobaczysz tę hierarchię.
Perimosh
próbowałeś tego
Perimosh
1
Btw w Intellij to: kliknij klasę w drzewie projektu i Ctrl + Alt + U lub kliknij prawym przyciskiem myszy -> Utwórz diagram
samodzielny
3

Jeśli korzystasz z mechanizmu buforowania (fabryka klientów http) lub równoważenia obciążenia (eureka) RestTemplate, nie będziesz mieć luksusu tworzenia new RestTemplateosobnej klasy. Jeśli dzwonisz do więcej niż jednej usługi, nie możesz z niej skorzystać, setErrorHandlerponieważ byłaby używana globalnie dla wszystkich Twoich żądań.

W tym przypadku HttpStatusCodeExceptionlepszym rozwiązaniem wydaje się złapanie .

Jedyną inną opcją, jaką masz, jest zdefiniowanie wielu RestTemplateinstancji za pomocą @Qualifieradnotacji.

Poza tym - ale to mój własny gust - podoba mi się obsługa błędów ściśle dopasowana do moich rozmów.

Hannes
źródło
3

Zrobiłem to jak poniżej:

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
źródło
1

Kod wymiany znajduje się poniżej :

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

Wyjątek RestClientExceptionma HttpClientErrorExceptioni HttpStatusCodeExceptionwyjątek.

Więc RestTempletemoże się zdarzyć HttpClientErrorExceptioni HttpStatusCodeExceptionwyjątek. W obiekcie wyjątku możesz uzyskać dokładny komunikat o błędzie w ten sposób:exception.getResponseBodyAsString()

Oto przykładowy kod :

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

Oto opis kodu :

W tej metodzie musisz przekazać klasę żądania i odpowiedzi. Ta metoda automatycznie przeanalizuje odpowiedź jako żądany obiekt.

Przede wszystkim musisz dodać konwerter wiadomości.

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

Następnie musisz dodać requestHeader . Oto kod:

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

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

Na koniec musisz wywołać metodę wymiany:

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

Do druku prety użyłem biblioteki Gson. oto klasa:compile 'com.google.code.gson:gson:2.4'

Możesz po prostu zadzwonić pod poniższy kod, aby uzyskać odpowiedź:

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

Oto pełny działający kod :

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

Dzięki :)

Md. Sajedul Karim
źródło
2
„org.springframework.web.client.HttpClientErrorException” jest podklasą „org.springframework.web.client.HttpStatusCodeException”. Nie musisz przechwytywać HttpClientErrorException, jeśli już przechwytujesz HttpStatusCodeException i nie robisz nic innego w obsłudze dwóch powyższych wyjątków.
Odrodzenie protonu
0

Bardzo prostym rozwiązaniem może być:

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
źródło
-1

Oto moja metoda POST z HTTPS, która zwraca treść odpowiedzi dla dowolnego typu złych odpowiedzi.

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
źródło
3
musisz użyć innego HttpClient niż domyślny SDK, aby uzyskać treść odpowiedzi dla błędów (na przykład apache commons HttpClient)
razor