Ниже приведен фрагмент кода; в основном, я пытаюсь распространить исключение, когда код ошибки отличается от 200.

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

Однако в случае ответа 500 от сервера я получаю исключение

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]

Мне действительно нужно оборачивать остальные методы обмена шаблонами в try? Для чего тогда нужны коды?

Ответы (14)

Вы хотите создать класс, реализующий ResponseErrorHandler, а затем использовать его экземпляр для установки обработки ошибок вашего остального шаблона:

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

Also, Spring has the class DefaultResponseErrorHandler, which you can extend instead of implementing the interface, in case you only want to override the handleError method.

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

Take a look at its source code to have an idea of how Spring handles HTTP errors.

Если вы используете механизм объединения (фабрика http-клиента) или балансировки нагрузки (eureka) с вашим RestTemplate, у вас не будет роскоши создания нового RestTemplate для каждого класса. Если вы вызываете более одной службы, вы не можете использовать setErrorHandler, потому что if будет использоваться глобально для всех ваших запросов.

In this case, catching the HttpStatusCodeException seems to be the better option.

The only other option you have is to define multiple RestTemplate instances using the @Qualifier annotation.

Also - but this is my own taste - I like my error handling snuggled tightly to my calls.

Я обработал это следующим образом:

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

Spring абстрагирует вас от очень-очень-очень большого списка кодов состояния http. В этом суть исключений. Взгляните на иерархию org.springframework.web.client.RestClientException:

You have a bunch of classes to map the most common situations when dealing with http responses. The http codes list is really large, you won't want write code to handle each situation. But for example, take a look into the HttpClientErrorException sub-hierarchy. You have a single exception to map any 4xx kind of error. If you need to go deep, then you can. But with just catching HttpClientErrorException, you can handle any situation where bad data was provided to the service.

The DefaultResponseErrorHandler is really simple and solid. If the response status code is not from the family of 2xx, it just returns true for the hasError method.

Попробуйте использовать @ ControllerAdvice. Это позволяет обрабатывать исключение только один раз и хранить все «настраиваемые» обработанные исключения в одном месте.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html

example

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler{

    @ExceptionHandler(MyException.class)
    protected ResponseEntity handleMyException(){
      MyException exception,
      WebRequest webRequest) {
    return handleExceptionInternal(
        exception,
        exception.getMessage(),
        exception.getResponseHeaders(),
        exception.getStatusCode(),
        webRequest);
}

Для расширения @carcaret ответьте немного ....

Consider your response errors are returned by json message. For example the API may return 204 as status code error and a json message as error list. In this case you need to define which messages should spring consider as error and how to consume them.

As a sample your API may return some thing like this, if error happens:

 { "errorCode":"TSC100" , "errorMessage":"The foo bar error happend" , "requestTime" : "202112827733" .... } 

To consume above json and throw a custom exception, you can do as below:

First define a class for mapping error ro object

//just to map the json to object
public class ServiceErrorResponse implements Serializable {

    //setter and getters
    private Object errorMessage;
    private String errorCode;
    private String requestTime;
   
}

Now define the error handler:

public class ServiceResponseErrorHandler implements ResponseErrorHandler {

    private List> messageConverters;

    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        
        return (response.getStatusCode().is4xxClientError() ||
                response.getStatusCode().is5xxServerError());
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        
        @SuppressWarnings({ "unchecked", "rawtypes" })
        HttpMessageConverterExtractor errorMessageExtractor = 
                new HttpMessageConverterExtractor(ServiceErrorResponse.class, messageConverters);
        
        ServiceErrorResponse errorObject = errorMessageExtractor.extractData(response);
        
       throw new ResponseEntityErrorException(
               ResponseEntity.status(response.getRawStatusCode())
                                .headers(response.getHeaders())
                                .body(errorObject)
               );
        
    }

    public void setMessageConverters(List> messageConverters) {
        this.messageConverters = messageConverters;
    }
}

The custom Exception will be:

public class ResponseEntityErrorException extends RuntimeException  {
    
    private ResponseEntity serviceErrorResponseResponse;

    public ResponseEntityErrorException(ResponseEntity serviceErrorResponseResponse) {
        this.serviceErrorResponseResponse = serviceErrorResponseResponse;
    }
    
    public ResponseEntity getServiceErrorResponseResponse() {
        return serviceErrorResponseResponse;
    }
}

To use it:

RestTemplateResponseErrorHandler errorHandler = new 
RestTemplateResponseErrorHandler();
//pass the messageConverters to errror handler and let it convert json to object
        errorHandler.setMessageConverters(restTemplate.getMessageConverters());
        restTemplate.setErrorHandler(errorHandler);

Вы должны поймать исключение HttpStatusCodeException:

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

Прочтите о глобальной обработке исключений в глобальном обработчике исключений, добавьте метод ниже. это будет работать.

@ExceptionHandler( {HttpClientErrorException.class, HttpStatusCodeException.class, HttpServerErrorException.class})
@ResponseBody
public ResponseEntity httpClientErrorException(HttpStatusCodeException e) throws IOException {
    BodyBuilder bodyBuilder = ResponseEntity.status(e.getRawStatusCode()).header("X-Backend-Status", String.valueOf(e.getRawStatusCode()));
    if (e.getResponseHeaders().getContentType() != null) {
        bodyBuilder.contentType(e.getResponseHeaders().getContentType());
    }
    return bodyBuilder.body(e.getResponseBodyAsString());
}

Другое решение - это то, что описано здесь, в конце поста, пользователем "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 
}

Очень простым решением может быть:

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

Вот мой метод POST с HTTPS, который возвращает тело ответа для любого типа неверных ответов.

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 entity = new HttpEntity(requestJson,headers);
    ResponseEntity response;
    String stringResponse = "";
    try {
        response = restTemplate.postForEntity(url, entity, String.class);
        stringResponse = response.getBody();
    }
    catch (HttpClientErrorException e)
    {
        stringResponse = e.getResponseBodyAsString();
    }
    return stringResponse;
}

Spring ловко обрабатывает коды ошибок http как исключения и предполагает, что ваш код обработки исключений имеет контекст для обработки ошибки. Чтобы обмен работал так, как вы ожидали, сделайте следующее:

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

This will return all the expected results from the response.

Я исправил это, переопределив метод hasError из класса DefaultResponseErrorHandler:

public class BadRequestSafeRestTemplateErrorHandler extends DefaultResponseErrorHandler
{
    @Override
    protected boolean hasError(HttpStatus statusCode)
    {
        if(statusCode == HttpStatus.BAD_REQUEST)
        {
            return false;
        }
        return statusCode.isError();
    }
}

And you need to set this handler for restemplate bean:

@Bean
    protected RestTemplate restTemplate(RestTemplateBuilder builder)
    {
        return builder.errorHandler(new BadRequestSafeRestTemplateErrorHandler()).build();
    }

Код обмена ниже:

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

Exception RestClientException has HttpClientErrorException and HttpStatusCodeException exception.

So in RestTemplete there may occure HttpClientErrorException and HttpStatusCodeException exception. In exception object you can get exact error message using this way: exception.getResponseBodyAsString()

Here is the example 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 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;
    }

Here is the code description:

In this method you have to pass request and response class. This method will automatically parse response as requested object.

First of All you have to add message converter.

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

Then you have to add requestHeader. Here is the code:

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

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

Finally, you have to call exchange method:

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

For prety printing i used Gson library. here is the gradle : compile 'com.google.code.gson:gson:2.4'

You can just call the bellow code to get response:

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

Here is the full working code:

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

Thanks :)

2022 WebDevInsider