오류의 종류에 따라 서블릿에서 내 서버의 특정 경로로 이동할 수 있게끔 할 수 있도록 하는 기능이 있습니다. 바로 WebServerFactoryCustomizer라는 Interface를 구현함으로써 가능합니다.
@Componentpublic class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> { @Override public void customize(ConfigurableWebServerFactory factory) { ErrorPage errorPage404 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500"); factory.addErrorPages(errorPage500); } }
ErrorPage라는 클래스를 만들고, 이걸 factory에 추가하면 등록했던 오류 상황에 맞는 경로로 오류가 발생했을 때 이동하게 됩니다. 예를들어 NOT_FOUND(404)에러가 발생되면 자동으로 /error-page/404 경로를 실행하게 됩니다. 그럼 error-page/404는 404 에러 상황에 보여줄 에러 페이지를 제공하는 기능을 Controller를 통해 구현하면 됩니다.
@Controllerpublic class ErrorPageController { @RequestMapping("/error-page/500") public String errorPage500() { return "error-page/500"; }}
그럼 결과적으로 resources/templates/error-page/404.html을 보여주게 됩니다.
위 WebServerFactoryCustomizer기능이 다시 사용되어야 하기 때문에 WebServerCustomizer의 @Component주석을 다시 풀어주셔야 합니다.
@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)public ResponseEntity<Map<String, Object>> errorPage500Api(HttpServletRequest request, HttpServletResponse response) { Map<String, Object> result = new HashMap<>(); Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION); result.put("status", request.getAttribute(ERROR_STATUS_CODE)); result.put("message", ex.getMessage()); Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); return new ResponseEntity(result, HttpStatus.valueOf(statusCode)); }
Controller에 RequestMapping을 추가하여 에러 화면을 보여주는 부분에서 조금 더 나아가 produces 옵션을 MediaType.APPLICATION_JSON_VALUE으로 지정하였습니다. 이렇게 하면 /error-page/500경로를 Header의 Accept가 application/json으로 설정되어서 요청했을 때는 해당 함수가 실행되게 됩니다.
2. 스프링 부트 기본 오류 처리 기능(BasicErrorController)
사전 작업
위 WebServerFactoryCustomizer기능이 사용되어 있으면 안되기 때문에, 해당 기능이 Bean에 등록되지 않도록 WebServerCustomizer클래스의 @Component를 주석 처리해줍시다.
WebServerFactoryCustomizer를 따로 설정하지 않으면 스프링 부트가 기본적으로 에러 데이터를 전달합니다.
예시
{ "timestamp": "2021-04-28T00:00:00.000+00:00", "status": 500, "error": "Internal Server Error", "exception": "java.lang.RuntimeException", "trace": "java.lang.RuntimeException: 잘못된 사용자...", "message": "잘못된 사용자", "path": "/api/members/ex"}
더 자세한 오류 정보
application.properties에 해당 옵션을 설정하면 더 자세한 오류 정보를 추가할 수 있습니다.
server.error.include-binding-errors=always
server.error.include-exception=true
server.error.include-message=always
server.error.include-stacktrace=always
always는 무조건 표시, on_param은 파라미터를 추가해야지만, false는 표시 안하는 설정입니다. 보안상 위험할 수 있기 때문에 false로 하고 로그를 통해서 확인하는것을 권장합니다.
3. HandlerExceptionResolver 사용하기
기본적으로 서버에서 오류가 발생하면 500상태코드의 에러가 발생하게 됩니다. 그러나 사용자와 관련된 에러는 400 상태코드를 반환하는 등 여러 상황에 따라 알맞은 400번대의 상태코드로 반환해야하는 상황이 있습니다.
DefaultHandlerExceptionResolver는 스프링 내부에서 발생하는 에러를 해결합니다.
예를 들어 숫자가 들어와야 하는 파라미터에 타입이 맞지 않는 문자열 등이 들어오게 되면 스프링 내부에서 에러가 발생하고, 이 상황은 사용자가 파라미터값을 잘못 넣는 것이니 500번 에러를 발생시키는 것이 아닌 response.sendError(HttpServletResponse.SC_BAD_REQUEST)를 통해 400번 에러를 자동으로 발생시키는 기능을 합니다.
위와 같은 컨트롤러의 함수에 http://localhost:8080/api?data=abc와 같이 data 파라미터를 문자열로 넣게 되면 DefaultHandlerExceptionResolver가 자동으로 발생하여 아래와 같은 결과를 반환합니다.
{ "status": 400, "error": "Bad Request", "exception": "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException", "message": "Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: \"abc\"", "path": "/api" }