HTTP 요청이 들어오면 위 그림과 같은 순서로 처리가 된다. 만약 여기서 예외가 발생하면 어떻게 될까?
try-catch문으로 예외를 처리하면 상관없지만 서블릿 밖으로 예외가 전파되면 예외는 WAS까지 올라간다. WAS는 오류페이지를 클라이언트에 응답하는 방식으로 예외를 처리한다.
Controller
@GetMapping("/error-ex")
public void errorEx(){
throw new RuntimeException("예외 발생");
}
/error-ex 경로로 요청이 들어오면 RuntimeException을 던져보겠다.
application.properties
server.error.whitelabel.enabled=false
SpringBoot환경의 경우, SpringBoot가 제공하는 오류페이지를 잠시 꺼두자.
그럼 위와 같이, 에러 페이지가 화면에 뜬다.
이처럼 서블릿 선에서 처리되지 못하는 에러는 WAS까지 전파되고 WAS 이를 오류페이지를 보여주는 방식으로 처리한다. 위 사진을 보면 알겠지만 WAS가 제공하는 오류페이지는 클라이언트 친화적이지 못하다. 이번 포스팅에서는 WAS까지 에러가 전파되는 경우 클라이언트 친화적으로 커스터마이징된 오류페이지를 띄우는 방법에 대해서 다루어 보겠다.
오류페이지 띄우기
커스터마이징 된 오류페이지는 html 파일로 존재한다. 예외가 발생한 경우 WAS가 오류페이지 경로를 요청할 수 있도록 등록해야 한다.
web.xml
<web-app>
<error-page>
<error-code>404</error-code>
<location>/error-page/404.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error-page/500.html</location>
</error-page>
<error-page>
<exception-type>java.lang.RuntimeException</exception-type>
<location>/error-page/500.html</location>
</error-page>
</web-app>
과거에는 web.xml 형식으로 에러코드와 매핑되는 에러페이지 경로를 설정해주었다. SpringBoot는 JAVA 코드로 에러페이지를 등록할 수 있도록 지원한다.
WebServerFactoryCustomizer
@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");
factory.addErrorPages(errorPage404,errorPage500,errorPageEx);
}
}
WebServerFactoryCustomizer 인터페이싀 구현체 클래스를 생성해준다. 인터페이스의 customize 메소드를 재정의한다. customize 메소드는 ConfigurableWebServerFactory 객체를 파라미터로 받을 수 있다. ConfigurableWebServerFactory는 에러페이지를 추가할 수 있다. 에러의 종류와 에러페이지 경로를 토대로 에러페이지 객체를 생성하고 factory 객체에 추가하면 URL 경로 설정이 마무리된다.
에러페이지에 설정한 경로는 에러페이지 파일이 위치한 경로가 아닌 URL 경로이다.
예외가 발생하면 WAS까지 전파되었다가 설정된 URL 경로로 다시 요청을 하는 것이다. 이때의 요청은 에러페이지를 클라이언트에게 응답하기 위한 요청이다. 그럼 에러페이지 요청을 위한 Controller를 만들어 보자.
ErrorPageController
@Slf4j
@Controller
public class ErrorPageController {
// 404 에러
@RequestMapping("/error-page/404")
public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
return "error-page/404"; // 404 에러페이지 경로
}
// 500 에러
@RequestMapping("/error-page/500")
public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
return "error-page/500"; // 500 에러페이지 경로
}
}
에러페이지를 호출하는 컨트롤러는 에러페이지 경로를 뷰템플릿으로 반환하여 에러페이지가 클라이언트에게 응답될 수 있도록 한다.
http://localhost:8080/error-ex 로 요청이 들어오면 RuntimeException이 발생한다. 에러가 발생하면 WAS까지 에러가 전파된다. RuntimeException 에러가 발생한 경우, /error-page/500 경로로 재요청하기로 설정했었다. 그럼 WAS는 /error-page/500 경로로 재요청한다. 서블릿은 /error-page/500와 매핑되는 컨트롤러의 메소드를 실행한다. ErrorPageController의 errorPage500 메소드가 실행되어 500 에러 페이지(html)가 위치한 error-page/500 경로를 뷰템플릿에 return하면, 뷰템플릿은 오류페이지를 렌더링하고 클라이언트에게 응답한다.
추가로,
HttpServletRequest 객체에는 많은 에러 관련 데이터가 들어있다. 이런 데이터에 접근하여 공통관심사를 처리하는 필터나 인터셉터에서 공통로그를 찍을 수도 있고 컨트롤러에서도 관련해서 유의미한 로직을 만들수 있다.
ErrorPageController
@Slf4j
@Controller
public class ErrorPageController {
// 에러 정보 key 값
public static final String ERROR_EXCEPTION = "jakarta.servlet.error.exception";
public static final String ERROR_EXCEPTION_TYPE = "jakarta.servlet.error.exception_type";
public static final String ERROR_MESSAGE = "jakarta.servlet.error.message";
public static final String ERROR_REQUEST_URI = "jakarta.servlet.error.request_uri";
public static final String ERROR_SERVLET_NAME = "jakarta.servlet.error.servlet_name";
public static final String ERROR_STATUS_CODE = "jakarta.servlet.error.status_code";
// 404 에러
@RequestMapping("/error-page/404")
public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
printErrorInfo(request);
return "error-page/404";
}
// 500 에러
@RequestMapping("/error-page/500")
public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
printErrorInfo(request);
return "error-page/500";
}
// 에러 정보 로그로 출력
private void printErrorInfo(HttpServletRequest request) {
log.info("ERROR_EXCEPTION {} ", request.getAttribute(ERROR_EXCEPTION));
log.info("ERROR_EXCEPTION_TYPE {} ", request.getAttribute(ERROR_EXCEPTION_TYPE));
log.info("ERROR_MESSAGE {} ", request.getAttribute(ERROR_MESSAGE));
log.info("ERROR_REQUEST_URI {} ", request.getAttribute(ERROR_REQUEST_URI));
log.info("ERROR_SERVLET_NAME {} ", request.getAttribute(ERROR_SERVLET_NAME));
log.info("ERROR_STATUS_CODE {} ", request.getAttribute(ERROR_STATUS_CODE));
log.info("dispatchType {} ", request.getDispatcherType());
}
}
그럼 에러가 발생했을때, HttpServletRequest 객체에는 어떤 데이터들이 저장되는지 확인해보자. 다시 http://localhost:8080/error-ex 를 요청하여 에러를 발생시켜 보겠다.
Error Exception Type, Error Status Code 등 현재 발생한 에러와 관련 많은 데이터가 Request 객체에 저장되어 있다. 그러므로 이를 활용하여 에러 관련한 다양한 작업을 처리할 수 있게 된다.
참고자료
'SPRING > Spring MVC' 카테고리의 다른 글
[SpringMVC] SpringBoot에서 오류 페이지 띄우기 (0) | 2023.09.01 |
---|---|
[SpringMVC] 예외처리 ( 필터, 인터셉터 ) (0) | 2023.09.01 |
[SpringMVC] ArgumentResolver 활용하기 (0) | 2023.08.31 |
[SpringMVC] 스프링 인터셉터(Interceptor)란? (0) | 2023.08.31 |
[SpringMVC] 서블릿 필터(Filter)란? (0) | 2023.08.31 |