✔️ 해당 포스트에서는 스프링이 아닌 순수 서블릿 컨테이너가 어떻게 예외를 처리하는지 정리해본다.
Exception
(말그대로 예외 발생, RuntimeException 등..)response.sendError(Http 상태 코드, 오류 메시지)
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
HttpServletResponse
가 제공하는 sendError
메소드를 사용할 수 있다.WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (response.sendError())
✔️ 서블릿은 Exception이 발생해서 WAS까지 전달되거나 response.sendError()가 호출되었을 때, 상황에 맞춘 오류 처리 기능을 제공한다. 즉, 오류에 따라 출력해줄 화면을 설정해줄 수 있는 것이다.
@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);
}
}
✔️ 아래와 같이 해당 ErrorPage의 url 호출 시, 처리해주는 컨트롤러를 만든다.
@Slf4j
@Controller
public class ErrorPageController {
@RequestMapping("/error-page/404")
public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
log.info("errorPage 404");
return "error-page/404";
}
@RequestMapping("/error-page/500")
public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
log.info("errorPage 500");
return "error-page/500";
}
}
✔️ 위에서 정리한 오류 페이지가 어떤식으로 동작하는지 원리를 정리해본다.
- WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
- WAS
/error-page/500
다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/errorpage/
500) -> View
new ErrorPage(RuntimeException.class, "/error-page/500"
정보를 찾는 것이다.)request
의 attribute에 오류 정보를 담아서 넘겨준다.javax.servlet.error.exception
: 예외javax.servlet.error.exception_type
: 예외 타입javax.servlet.error.message
: 오류 메시지javax.servlet.error.request_uri
: 클라이언트 요청 URIjavax.servlet.error.servlet_name
: 오류가 발생한 서블릿 이름javax.servlet.error.status_code
: HTTP 상태 코드DispatcherType
정보를 통해 정상 요청인지 혹은 오류 페이지를 위한 내부 요청인지를 구분할 수 있다.DispatcherType
에 대해 정리한다.public enum DispatcherType {
FORWARD, // 서블릿에서 다른 서블릿이나 JSP를 호출할 때: RequestDispatcher.forward(request, response);
INCLUDE, // 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때: RequestDispatcher.include(request, response);
REQUEST, // 클라이언트 요청
ASYNC, // 서블릿 비동기 호출
ERROR // 오류 요청
}
@Configuration
public class WebConfig {
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
return filterRegistrationBean;
}
}
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
: 이렇게 setDispatcherTypes()
DispatcherType을 적용하면, 적용된 타입인 경우에 필터가 호출된다.DispatcherType.REQUEST
이므로 설정되지 않은 경우 클라이언트 요청인 경우에만 필터가 호출된다.✔️ 그렇다면 인터셉터에서는 DispatchType을 어떻게 사용할 수 있을까?
→ 인터셉터는 스프링이 제공하는 기능이므로 DispatcherType과 무관하게 항상 호출된다.
✔️ 때문에, 인터셉터는 DispatchType 대신에 excludePathPatterns
를 사용해서 에러 페이지에 대한 경로를 제외해주면 된다.
//WebConfig
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "*.ico", "/error", "/error-page/**");
}
"/error", "/error-page/**"
처럼 에러 페이지 경로를 제외해주면 된다.✔️ 위에서 서블릿에서 동작하는 예외 처리 페이지를 학습했는데, 서블릿은 아래와 같은 복잡한 과정을 거친다.
WebServerCustomizer
클래스 생성ErrorPage
추가ErrorPageController
를 생성✔️ 스프링 부트는 위의 과정들을 모두 기본으로 제공한다.
/error
라는 경로로 기본 ErrorPage를 자동으로 등록해둔다.new ErrorPage("/error")
와 같고, 별도로 상태코드와 예외를 설정하지 않으면 이 기본 오류 페이지가 사용된다./error
경로의 에러 페이지를 호출한다.new ErrorPage("/error")
로 등록한 /error
를 매핑해서 처리하기 위해 BasicErrorController
라는 스프링 컨트롤러를 자동으로 등록한다.ErrorMvcAutoConfiguration
라는 클래스가 오류 페이지를 자동으로 등록하는 역할을 한다.✔️ 개발자는 오류 페이지만 등록하면 된다.
BasicErrorController
의 뷰 선택 우선 순위가 있는데 아래와 같다.
✔️ BasicErrorController 는 아래의 정보들을 model에 담아서 뷰에 전달해준다. 뷰 템플릿은 이 값들을 활용해서 출력할 수 있다.
- timestamp: Fri Feb 05 00:00:00 KST 2021
- status: 400
- error: Bad Request
- exception: org.springframework.validation.BindException
- trace: 예외 trace
- message: Validation failed for object='data'. Error count: 1
- errors: Errors(BindingResult)
- path: 클라이언트 요청 경로 (
/hello
)
✔️ 그런데 기본적으로 오류 관련 내부 정보는 클라이언트에게 노출하는 것이 좋지 않기 때문에 기본적으로 출력되지 않는 오류 정보들도 있는데, 이런 정보들을 설정 파일(application.yaml)을 통해 model에 다 포함할지에 대한 여부를 선택할 수 있다.
server.error.include-exception=false
: exception 포함 여부( true , false )server.error.include-message=never
: message 포함 여부server.error.include-stacktrace=never
: trace 포함 여부server.error.include-binding-errors=never
: errors 포함 여부✔️ 위에서 기본 값이never
인 설정들은 아래 3가지 옵션을 사용할 수 있다. 디버그 시 문제확인을 위해 사용할 수 있지만 개발 서버에서만 사용하고 운영 서버에서는 사용하지 않는 것이 좋다.
server.error.whitelabel.enabled=true
: 오류 처리 화면을 못 찾을 시, 스프링 whitelabel 오류 페이지 적용server.error.path=/error
: 오류 페이지 경로, 스프링이 자동 등록하는 서블릿 글로벌 오류 페이지 경로와 BasicErrorController 오류 컨트롤러 경로에 함께 사용된다.ErrorController
인터페이스를 상속 받아서 구현하거나 BasicErrorController
를 상속 받아서 기능을 추가할 수 있다.