-
RestTemplate Error Handling - RestTemplate Hands On 6Spring Framework/RestTemplate 2023. 9. 27. 00:01
개요
이전 글들에서는 RestTemplate의 Get, Post, Delete 등에 대해 알아보았습니다.
이번 글에서는 RestTemplate Error Handling에 대해 알아보고자 합니다.
에러는 매우 다양하게 발생할 수 있습니다.일시적인 네트워크 에러, 호출하는 서버의 다운, 잘못된 요청 등등..
에러를 발생시키는 코드
@RestController class RestTemplateErrorHandling { val baseUrl = "localhost:8080" @GetMapping("/error-handle") fun errorHandle(){ val apiUrl = "/error-handle-test" val restTemplate = RestTemplate() val responseEntity = restTemplate.getForEntity( baseUrl + apiUrl, String::class.java ) println(responseEntity) } @GetMapping("/error-handle-test") fun errorHandleTest(){ throw IllegalArgumentException("호출시 에러가 발생합니디") } }
간단한 get 호출과 이를 호출할 때 Exception이 발생하도록 코드를 구성해 보았습니다.
결과는 어떻게 될까요?
결과
org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : "{"timestamp":"2023-09-04T13:27:44.573+00:00","status":500,"error":"Internal Server Error",
HttpServerErrorException이 발생합니다.
기본적으로 RestTemplate은 HTTP 오류가 발생하면 다음과 같은 예외를 발생시킵니다.
- HttpClientErrorException - 4xx 상태코드
- HttpServerErrorException - 5xx 상태코드
- UnkownHttpStatusCodeException - 알 수 없는 HTTP 상태
위의 예외들은 모두 RestClientResponseException을 상속받고 있습니다.
DefaultResponseErrorHandler
RestTemplate은 에러처리를 위해 기본적으로 DefaultResponseErrorHandler를 활용합니다.
DefaultResponseErrorHandler는 ResponseErrorHandler 인터페이스를 구현하고 있습니다.
public interface ResponseErrorHandler { /** * Indicate whether the given response has any errors. * <p>Implementations will typically inspect the * {@link ClientHttpResponse#getStatusCode() HttpStatus} of the response. * @param response the response to inspect * @return {@code true} if the response indicates an error; {@code false} otherwise * @throws IOException in case of I/O errors */ boolean hasError(ClientHttpResponse response) throws IOException; /** * Handle the error in the given response. * <p>This method is only called when {@link #hasError(ClientHttpResponse)} * has returned {@code true}. * @param response the response with the error * @throws IOException in case of I/O errors */ void handleError(ClientHttpResponse response) throws IOException; /** * Alternative to {@link #handleError(ClientHttpResponse)} with extra * information providing access to the request URL and HTTP method. * @param url the request URL * @param method the HTTP method * @param response the response with the error * @throws IOException in case of I/O errors * @since 5.0 */ default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { handleError(response); } }
JavaDoc을 읽어보면 ClientHttpReponse의 StatusCode를 기반으로 에러를 판단하는 hasError 메서드가 존재하고, 만약 에러라고 판단한다면 에러를 어떻게 처리할지 수행하는 handleError 메서드가 존재합니다.
RestTemplate에서부터 디버깅
doExecute메서드내부를 살펴보면 다음과 같은 코드를 호출합니다.
response = request.execute(); observationContext.setResponse(response); handleResponse(url, method, response);
여기서 handleResponse를 호출합니다.
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { ResponseErrorHandler errorHandler = getErrorHandler(); boolean hasError = errorHandler.hasError(response); if (logger.isDebugEnabled()) { try { HttpStatusCode statusCode = response.getStatusCode(); logger.debug("Response " + statusCode); } catch (IOException ex) { logger.debug("Failed to obtain response status code", ex); } } if (hasError) { errorHandler.handleError(url, method, response); } }
errorHandler를 가져옵니다 (DeafultErrorHandler)
그리고 errorHandler가 에러를 가지고 있다면 에러를 처리하고 그렇지 않은 경우에는 별다른 작업을 수행하지 않습니다.
에러를 판단하는 hasError 로직
public boolean hasError(ClientHttpResponse response) throws IOException { HttpStatusCode statusCode = response.getStatusCode(); return hasError(statusCode); }
statusCode를 가져와서 hasError 메서드를 다시 호출합니다.
더 들어가 보면 HttpStatus Enum에서 isError 메서드를 구현합니다.
@Override public boolean isError() { return (is4xxClientError() || is5xxServerError()); }
400과 500에 대한 에러를 처리합니다.
handleError 메서드를 들어가보면 다음과 같습니다.
protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode) throws IOException { String statusText = response.getStatusText(); HttpHeaders headers = response.getHeaders(); byte[] body = getResponseBody(response); Charset charset = getCharset(response); String message = getErrorMessage(statusCode.value(), statusText, body, charset); RestClientResponseException ex; if (statusCode.is4xxClientError()) { ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset); } else if (statusCode.is5xxServerError()) { ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset); } else { ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset); } if (!CollectionUtils.isEmpty(this.messageConverters)) { ex.setBodyConvertFunction(initBodyConvertFunction(response, body)); } throw ex; }
4xx인 경우에는 HttpClientErrorException을 반환하고
5xx인 경우에는 HttpServerErrorException을 반환합니다.
그 외에는 UnkownHttpStatusCodeException을 반환합니다.
에러를 어떻게 처리하는 게 좋을까?
두 가지 방안이 떠오릅니다.
- try-catch로 잡아서 처리하기
- ResponseErrorHandler를 구현하여 DefaultErrorHandler 대체하기
DeafultErrorHandler를 구현하여 내가 원하는 Exception을 반환하도록 할 수 있습니다.
하지만 조금 더 직관적인 코드를 생각해 보았을 때는 try-catch가 더 좋아 보입니다.
예를 들어 RestTemplate을 잘 모르는 개발자들은 DefaultErrorHandler의 존재를 모를 수 있다고 생각합니다.
이런 상황에서 다른 구현체가 사용되는 것이 어색하고 하나의 허들이 될 수 있습니다.
@GetMapping("/error-handle") fun errorHandle(): String{ val apiUrl = "/error-handle-test" val restTemplate = RestTemplate() try{ val responseEntity = restTemplate.getForEntity( baseUrl + apiUrl, String::class.java ) println("----정상 호출 ----") println(responseEntity) return "성공" } catch (e: HttpClientErrorException){ println("----4xx Http Status 에러가 발생했습니다, 요청값에 문제가 없는지 확인해주세요 ----") return "실패" } catch (e: HttpServerErrorException){ println("----5xx Http Status 에러가 발생했습니다 ----") return "실패" } catch (e: Exception){ println("---- 예상치 못한 에러입니다, 분석 후 대응이 필요합니다----") return "실패" } }
try-catch를 통해 상세하게 400번대 에러인지 500번째 에러인지를 구분할 수 있습니다.
그리고 해당값의 반환값을 통해 성공인지 실패인지를 전달해 준다면 예외의 전파 없이 Client 호출부를 활용하는 Application 로직에서 다음 행위를 판단할 수 있습니다.
예를 들어 다음과 같은 코드가 가능합니다.
if(clientLogicResponse.isError()){ ...에러에 대한 헨들링을 알아서 처리하세요. }
참고자료
https://www.baeldung.com/spring-rest-template-error-handling
'Spring Framework > RestTemplate' 카테고리의 다른 글
RestTemplate Retry 실습 - RestTemplate Hands On 7 (0) 2023.09.29 RestTemplate Timeout - ResetTemplate Hands On 7 (0) 2023.09.28 RestTemplate Post Request - RestTemplate Hands On 4 (0) 2023.09.24 RestTemplate Get Request - RestTemplate Hands On 3 (0) 2023.09.23 RestTemplate Get Request - RestTemplate Hands On 2 (0) 2023.09.21