Tratamento de exceções Spring Resttemplate

124

Abaixo está o trecho de código; basicamente, estou tentando propagar a exceção quando o código de erro é diferente de 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();
            }

No entanto, no caso de uma resposta 500 do servidor, estou recebendo a exceção

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]

Eu realmente preciso envolver o método de troca de modelo restante na tentativa? Qual seria então o propósito dos códigos?

Vaibhav
fonte
Compartilhe o código de ApplicationException ()
Mudassar

Respostas:

128

Você deseja criar uma classe que implemente ResponseErrorHandlere, em seguida, use uma instância dela para definir o tratamento de erros do seu modelo 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());
}

Além disso, o Spring tem a classe DefaultResponseErrorHandler, que você pode estender em vez de implementar a interface, caso queira apenas substituir o handleErrormétodo.

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

Dê uma olhada em seu código-fonte para ter uma ideia de como o Spring trata os erros HTTP.

tapete
fonte
1
Tenho 1 instância de RestTemplate que reutilizo para chamadas diferentes. Preciso lidar com erros de chamadas diferentes de forma diferente - aparentemente, não há como fazer isso com o manipulador global - preciso fornecer um manipulador por solicitação.
mvmn
4
Com esse manipulador de erros, sempre recebo um ResourceAccessExceptionporque RestTemplate.doExecutepega IOExceptions e lança a ResourceAccessException. O que estou fazendo de errado?
Federico Bellucci
Consegui resolver isso estendendo DefaultResponseErrorHandler.
Crenguta S
48

Você deve pegar uma HttpStatusCodeExceptionexceção:

try {
    restTemplate.exchange(...);
} catch (HttpStatusCodeException exception) {
    int statusCode = exception.getStatusCode().value();
    ...
}
mekazu
fonte
37
IMO a resposta deve sempre vir com um código de status apropriado, caso contrário, qual é o propósito dos códigos.
vaibhav
5
Não tenho certeza de entender a objeção @vaibhav: capturar HttpStatusCodeException não é para um código errado, mas porque em muitos casos uma exceção é sempre lançada e então seu if (code == value) nunca pode ser executado.
Stefano Scarpanti
1
As exceções são muito caras em Java. Tudo bem para as causas ocasionais e inesperadas (daí o nome), mas, acima disso, você deve procurar outras soluções.
Agoston Horvath
11
"Muito caro"? Mais caro do que, digamos, fazer uma chamada HTTP?
IcedDante
4
@RaffaelBecharaRameh - HttpStatusCodeException .getResponseBodyAsString () ou HttpStatusCodeException.getResponseBodyAsByteArray ().
Dave
45

O Spring trata habilmente os códigos de erro http como exceções e assume que o seu código de tratamento de exceção tem o contexto para tratar o erro. Para que o Exchange funcione como você espera, faça o seguinte:

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

Isso retornará todos os resultados esperados da resposta.

Austin Cherlo
fonte
2
você precisa usar um HttpClient diferente do SDK padrão para obter o corpo de resposta para erros
razor
26

Outra solução é a descrita aqui no final desta postagem 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
fonte
4
você precisa usar um HttpClient diferente do SDK padrão, para obter o corpo da resposta para erros (por exemplo, apache commons HttpClient)
razor
17

O Spring o abstrai da lista muito grande de códigos de status http. Essa é a ideia das exceções. Dê uma olhada na hierarquia org.springframework.web.client.RestClientException:

Você tem um monte de classes para mapear as situações mais comuns ao lidar com respostas http. A lista de códigos http é muito grande, você não vai querer escrever código para lidar com cada situação. Mas, por exemplo, dê uma olhada na sub-hierarquia HttpClientErrorException. Você tem uma única exceção para mapear qualquer tipo de erro 4xx. Se você precisa ir fundo, você pode. Mas, apenas capturando HttpClientErrorException, você pode lidar com qualquer situação em que dados inválidos tenham sido fornecidos ao serviço.

O DefaultResponseErrorHandler é realmente simples e sólido. Se o código de status de resposta não for da família 2xx, ele apenas retornará verdadeiro para o método hasError.

Perimosh
fonte
Cara, obrigado pela explicação. Como você construiu esta árvore com hierarquia de exceção?
independente em
1
Ei cara, eu usei Eclipse. Basta pressionar control + shift + T para abrir o buscador de tipo e digitar RestClientException. Clique duas vezes em RestClientException nos resultados e o Eclipse abrirá essa classe para você. Em seguida, coloque o cursor do mouse sobre o nome da classe (onde está escrito "public class RestClientException ..." e pressione control + T. Você verá essa hierarquia.
Perimosh
você experimentou?
Perimosh
1
A propósito, no Intellij é: clique na classe na árvore do projeto e Ctrl + Alt + U, ou clique com o botão direito do mouse -> Construir diagrama
autônomo
3

Se você usar o mecanismo de pooling (http client factory) ou load balancing (eureka) RestTemplate, não terá o luxo de criar um new RestTemplatepor classe. Se você estiver ligando para mais de um serviço, não poderá usar setErrorHandlerporque ele será usado globalmente para todas as suas solicitações.

Nesse caso, pegar o HttpStatusCodeExceptionparece ser a melhor opção.

A única outra opção que você tem é definir várias RestTemplateinstâncias usando a @Qualifieranotação.

Além disso - mas este é o meu gosto - gosto de meu tratamento de erros aninhado às minhas chamadas.

Hannes
fonte
3

Eu tratei disso como a seguir:

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
fonte
1

O código de troca está abaixo :

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

Exceção RestClientExceptiontem HttpClientErrorExceptione HttpStatusCodeExceptionexceção.

Assim, em RestTempleteque pode occure HttpClientErrorExceptione HttpStatusCodeExceptionexceção. No objeto de exceção, você pode obter a mensagem de erro exata da seguinte maneira:exception.getResponseBodyAsString()

Aqui está o código de exemplo :

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

Aqui está a descrição do código :

Neste método, você deve passar a classe de solicitação e resposta. Este método analisará automaticamente a resposta como objeto solicitado.

Primeiro de tudo você tem que adicionar conversor de mensagem.

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

Então você tem que adicionar requestHeader . Aqui está o código:

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

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

Finalmente, você deve chamar o método de troca:

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

Para impressão bonita, usei a biblioteca Gson. aqui está o gradle:compile 'com.google.code.gson:gson:2.4'

Você pode apenas chamar o código abaixo para obter a resposta:

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

Aqui está o código de trabalho 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);
    }
}

Obrigado :)

Md. Sajedul Karim
fonte
2
'org.springframework.web.client.HttpClientErrorException' é uma subclasse de 'org.springframework.web.client.HttpStatusCodeException'. Você não precisa capturar HttpClientErrorException se já estiver capturando HttpStatusCodeException e não fizer nada diferente no tratamento das duas exceções acima.
The-Proton-Resurgence de
0

Uma solução muito simples pode 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
fonte
-1

Aqui está meu método POST com HTTPS, que retorna um corpo de resposta para qualquer tipo de resposta incorreta.

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
fonte
3
você precisa usar um HttpClient diferente do SDK padrão, para obter o corpo da resposta para erros (por exemplo, apache commons HttpClient)
razor