1. 서블릿 예외 처리
서블릿은 2가지 방식으로 예외를 처리한다.
1.1. Exception
웹 애플리케이션은 사용자 요청별로 스레드가 할당되고, 서블릿 컨테이너 안에서 실행된다. 애플리케이션에서 예외가 발생했는데 해당 예외를 잡지 못한 경우는 어떻게 될까? 기본적으로 어떠한 요청이 들어올 경우 다음과 같이 요청이 전파된다.
- WAS → 필터 → 서블릿 → 인터셉터 → 컨트롤러
만약 컨트롤러에서 예외가 발생하면 요청의 전파 방향과 반대로 전파된다.
- WAS ← 필터 ← 서블릿 ← 인터셉터 ← 컨트롤러
예외 발생시 서블릿 컨테이너에서 그에 맞는 오류 페이지를 보여준다.
1.2. response.sendError() 메서드
오류가 발생하면 HttpServletResponse가 제공하는 sendError 메서드를 사용해도 된다. sendError 메서드의 사용법은 다음과 같다.
- response.sendError(HTTP 상태 코드)
- response.sendError(HTTP 상태 코드, 오류 메시지)
위 메서드를 호출하면 예외가 발생하진 않고 서블릿 컨테이너에 오류가 발생했다는 사실을 알릴 수 있다. 즉, response 내부에 오류가 발생했다는 상태만 저장한다. 서블릿 컨테이너는 응답 전에 response.sendError()가 호출되었는지 확인하고 호출된 경우 설정한 오류 코드에 맞게 오류 페이지를 보여준다.
2. 오류 화면 제공
서블릿이 제공하는 기본 오류 페이지가 아닌 원하는 오류 페이지를 제공하는 방법도 있다. 서블릿은 Exception이 발생하거나 response.sendError()가 호출된 경우 각 상황에 맞게 오류를 처리할 수 있는 기능을 제공한다. 이전에는 아래와 같이 web.xml이라는 파일에 오류 화면을 등록했다.
<web-app>
<error-page>
<error-code>404</error-code>
<location>/error-page/404.html</location>
</error-page>
<error-page>
...
</error-page>
</web-app>
최근에는 스프링 부트를 통해 서블릿 컨테이너를 실행하기 때문에 스프링 부트가 제공하는 기능을 통해 오류 페이지를 등록하면 된다.
@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);
}
}
- 서버에서 404, 500 등의 에러가 발생하면 ErrorPage로 등록한 컨트롤러를 호출한다. 즉, 404 에러가 발생한 경우 "error-page/404" 컨트롤러를 호출한다.
기존에 sendError를 통해 오류를 전달하거나 예외가 발생하는 경우 WAS까지 오류가 전파된다고 했다. WAS는 오류를 확인하면 해당 오류의 오류 페이지 정보를 확인한다. 즉, 404 에러가 발생하면, WAS는 404 에러의 오류 페이지가 error-page/404임을 확인하고 해당 오류 페이지를 요청한다. 즉, 전체적인 흐름은 다음과 같다.
- 컨트롤러 (예외 발생) → 인터셉터 → 서블릿 → 필터 → WAS (예외의 오류 페이지 확인, 요청) → 필터 → 서블릿 → 인터셉터 → 컨트롤러
이때 중요한 점은 서버 내부에서 오류 페이지를 추가 호출한다는 점이다. (클라이언트는 이 사실을 모른다.) WAS에서 오류 페이지 요청 시 HttpServletRequest에 추가된 에러 정보를 컨트롤러에서 아래와 같이 출력해볼 수도 있다.
@Slf4j
@Controller
public class ErrorPageController {
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";
@RequestMapping("/error-page/404")
public String errorPage400(HttpServletResponse response, HttpServletRequest request) {
log.info("errorPage 404");
printErrorInfo(request);
return "error-page/404";
}
...
public void printErrorInfo(HttpServletRequest request) {
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());
}
}
404에러 발생시 다음과 같이 로그가 찍히는 것을 확인할 수 있다.
3. 서블릿 예외 처리 - 필터
에러가 발생하여 WAS가 오류 페이지를 호출할 때 다시 필터와 인터셉터가 호출된다. 하지만 처음 요청에서 거친 필터 혹은 인터셉터를 다시 호출하는 것은 매우 비효율적이다. 따라서 클라이언트의 처음 요청인지, 오류 페이지에 대한 요청인지를 구분할 수 있어야 하는데 이를 위해서 서블릿은 DispatcherType이라는 정보를 제공한다. 필터와 인터셉터에 대해 잘 모른다면 아래 포스터를 참고하자.
3.1. DispatcherType
필터는 이런 상황을 대비해 DispatcherType이라는 정보를 제공한다. DispatcherType은 enum 타입으로 구체적 내용은 다음과 같다.
public enum DispatcherType {
FORWARD, // 다른 서블릿 혹은 JSP를 호출
INCLUDE, // 다른 서블릿 혹은 JSP의 결과를 포함하는 경우
REQUEST, // 클라이언트의 요청
ASYNC, // 서블릿 비동기 호출
ERROR // 오류 요청
}
필터에 원하는 DispatcherType을 다음과 같이 설정하면 된다.
@Configuration
public class WebConfig {
@Bean
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;
}
}
- setDispatcherTypes를 통해 원하는 DistpatcherType들을 넘겨주면 된다.
만약 오류 페이지 요청 시 필터를 거치고 싶지 않다면 setDispatcherTypes에서 DispatcherType.ERROR를 빼주면 된다.
4. 서블릿 예외 처리 - 인터셉터
인터셉터는 DispatcherType을 제공하지 않고, 이전에 사용했던 .excludePathPatterns를 사용하며 된다. 즉, 오류 페이지 요청과 관련된 경로를 대상에서 빼주면 된다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "*.ico", "/error", "error-page/**");
}
}
5. 스프링 부트의 오류 페이지
위에서는 다음과 같이 에러의 타입에 따라 ErrorPage를 추가하고 해당 에러 처리를 위한 컨트롤러를 만들어야 했다. 스프링 부트는 이 과정을 기본으로 제공한다.
- ErrorPage를 자동으로 등록하되, 기본 경로는 "/error"이다. 어떤 예외가 발생하거나 response.sendError()가 호출되면 "/error"가 호출된다.
- BasicErrorController라는 스프링 컨트롤러를 자동으로 등록한다. "/error"를 매핑해 처리하는 컨트롤러다.
위와 같이 BasicErrorController라는 처리 컨트롤러가 만들어져 있기 때문에 우리는 오류 페이지 화면만 등록하면 된다. 오퓨 페이지 화면에도 우선 순위가 정해져있는데 순위는 다음과 같다.
- 뷰 템플릿 - resources/templates/error/500.html, resources/templates/error/5xx.html
- 정적 리소스 - resources/static/error/500.html , resources/static/error/5xx.html
- 적용 대상이 없는 경우 - resources/templates/error.html
추가적으로 BaiscErrorController는 timestamp, status, error, exception, message, path 등의 정보들을 model에 담아 뷰에 전달하기 때문에 해당 정보들을 활용해 화면에 출력할 수 있다. 이러한 정보를 model에 포함하지 않으려면 다음과 같은 설정을 application.properties에 추가하면 된다.
server.error.include-exception=true
server.error.include-message=always
server.error.include-statcktrace=always
server.error.include-binding-errors=always
- always: 항상 사용 / never: 사용하지 않음 / on_param: 파라미터 있을 때 사용
하지만 실무에서 이러한 것들을 노출해서는 안되고 서버 로그로 남겨서 확인해야 한다. 이외에도 스프링 부트는 다음과 같은 오류 관련 옵션을 제공한다.
- server.error.whitelabel.enabled=true : 오류 처리 화면을 못 찾을 시 스프링 whitelabel 오류 페이지를 적용한다.
- server.error.path=/error : 오류 페이지 경로를 정한다.
만약 에러 공통 처리 컨트롤러의 기능을 변경하고 싶다면 BasicErrorController를 상속해 구현하거나 ErrorController 인터페이스를 상속받으면 된다. 하지만 변경하는 경우가 크게 많지는 않다.
출처: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard
'BackEnd > Spring' 카테고리의 다른 글
JWT란? (0) | 2024.07.31 |
---|---|
[Spring MVC-2] 로그인 - 필터, 인터셉터 (0) | 2024.05.27 |
[Spring MVC-2] 로그인 - 쿠키, 세션 (0) | 2024.05.27 |
[Spring MVC-2] Bean Validation (0) | 2024.05.24 |
[Spring MVC-2] Validation (0) | 2024.05.23 |