웹 관련 공통처리를 할 수 있는 필터와 인터셉터에 대해 학습했다. AOP 처럼 특정 Url 패턴에 공통처리를 수행하는데, Request 와 Response 객체를 파라미터로 사용하므로 웹 관련 공통처리에 유리하다. 세 가지 시점에 적용할 수 있고, excludePatterns 로 패턴 적용도 유연해서 스프링에서는 주로 인터셉터를 사용한다.
이번엔 컨트롤러에서 처리 중 발생한 예외를 어떻게 처리할지 알아보자.
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
컨트롤러 예외 상황에서 Exception을 던질 수 있다. 이 예외가 중간에 try-catch 로 처리되면 문제가 없지만 처리되지 못하면 WAS 까지 전달된다. 그러면 해당 스레드는 500 에러(서버 내부 에러)를 발생시키고 종료된다.
response.sendError(HTTP 상태 코드)
컨트롤러 예외 상황에서 response.sendError 를 호출할 수 있다. 응답시 WAS까지 거슬러 올라가면, WAS 는 sendError 기록을 확인하고 기본 오류페이지를 보여준다. 예외는 반드시 500 에러를 발동시키지만 sendError 는 상태코드를 변경할 수 있는 차이점이 있다.
WAS는 예외나 sendError 가 확인되면 그에 해당하는 기본 오류페이지를 넘겨준다. 그러나 기본 오류페이지는 고객 친화적이지 않기에 Exception 이나 오류코드에 Url 을 매핑시킬 수 있다.
@Component
public class WebServerCustomizer
implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
ErrorPage error404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
ErrorPage errorex = new ErrorPage(RuntimeException.class, "/error/500");
factory.addErrorPages(error404, errorex);
}
}
오류 상황에 Url 을 매핑하려면, WebServerFactoryCustomizer를 구현하고, 그 안에 customize 메서드를 오버라이딩하면 된다. 그 안에서 상황에 따른 ErrorPage 객체를 생성하고 factory 에 등록한다. 그러면 오류 상황에 매핑된 에러페이지 Url 로 요청이 수행된다.
@RequestMapping("/error/404")
public String error404() {
return "error-page/404";
}
@RequestMapping("/error/ex")
public String errorEx() {
return "error-page/ex";
}
이제 에러페이지를 생성하고, 컨트롤러에서 오류 Url 에 따른 에러페이지로 응답하면 된다.
오류 페이지 출력까지 과정을 살펴보자. WAS 까지 예외가 전파되면 WAS는 예외와 매핑된 URL 을 찾는다. 그리고 WAS는 오류 정보를 담아 해당 URL로 다시 요청을 수행하고, 에러 페이지를 응답 받는다.
즉 오류 페이지를 얻기 위해 다시 요청을 반복하는 오버헤드를 감수하는 것이다.
오류 발생시 재요청이 들어오는데, 다시 필터를 수행하면 비효율적이다. 이 때 요청의 DispatcherType 이라는 헤더를 사용한다. 이 헤더는 최초 요청에서는 Request 이지만, 오류 페이지를 얻기 위한 재호출 시에는 Error 로 변경된다. 따라서 필터 등록 시 DistapcherType.REQEUST 에만 사용되도록 조건을 추가해주면 된다. 그런데 기본적으로 등록되있기에 오류페이지에 필터를 적용할게 아니면 생략하면 된다.
//기본 등록됨
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQEUST);
인터셉터는 DispatcherType 과 무관하게 항상 호출된다. 따라서 재호출을 방지하려면 등록시 excludePatterns 에서 오류페이지 경로를 빼주면 된다.
서블릿의 오류페이지는 WebServerFactoryCustomizer 를 구현하고 customize 를 오버라이딩하면서 ErrorPage 를 생성해 등록해야한다. 이 과정이 너무 불편한데 스프링 부트는 이 오류페이지 등록을 기본 제공한다.
BasicErrorController 는 오류의 상태코드를 바탕으로 뷰를 넘겨주는데 우선순위에 따라 넘겨준다.
BasicErrorController 는 $timestamp $status $error $message 등의 정보를 model에 담아 뷰로 전달한다. 뷰 렌더링 시 필요에 따라 해당 정보를 사용할 수 있다. application.properties 에서 server.error.include 옵션으로 해당 정보를 model 에 담아 전달할지 안할지 결정할 수 있다.
exception, response.sendError 시 WAS가 재요청을 수행한다. 스프링부트는 /error를 Url 로 재요청을 수행하며, BasicErrorController /error 를 받아서 처리한다. 이 때 상태 코드를 보고, 해당 상태코드의 이름을 갖는 뷰를 렌더링시킨다. 개발자는 상태코드에 알맞는 뷰를 생성하여 resource/templates/error 에 넣어두면 된다.