Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

참새의 이야기

[MVC2] MVC 예외 처리 본문

Spring

[MVC2] MVC 예외 처리

참새짹짹! 2023. 8. 12. 16:34

서블릿 예외 처리

스프링이 제공하는 예외 처리 방식을 공부하기 전에 서블릿 컨테이너는 예외를 어떻게 처리하는지 알아볼 필요가 있다.

서블릿을 이용할 경우 두 가지 방식의 예외 처리법이 있다.

  • Exception
  • response.sendError(HTTP_STATUS_CODE, ERROR_MESSAGE)

Exception

웹 애플리케이션은 request마다 thread가 할당되고 서블릿 컨테이너 안에서 실행된다.

@Controller
public class ServletExController {
    @GetMapping("/error-ex")
    public void errorEx() {
        throw new RuntimeException("예외 발생");
    }
}

애플리케이션이 예외를 처리하지 못한다면 예외는 서블릿 밖으로 나와 WAS까지 전달된다.

WAS는 서버 내부 처리가 불가능한 예외가 발생했다고 판단하고 HTTP Status 500 을 반환한다.

response.sendError()

위의 방식 외에도 HttpServletResponse가 제공하는 sendError()를 이용하는 방식도 있다.

@Controller
public class ServletExController {
        @GetMapping("/error-404")
    public void error404(HttpServletResponse response) throws IOException {
        response.sendError(404, "404 error!");
    }

    @GetMapping("/error-500")
    public void error500(HttpServletResponse response) throws IOException {
        response.sendError(500);
    }
}

sendError()를 이용하면 서블릿 컨테이너는 고객에게 응답하기 전에 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);
    }
}

WebServerCustomizer를 통해 서블릿 오류 페이지를 등록할 수 있다.

Http Status Code와 오류 페이지를 매핑하여 등록하고 컨트롤러에서는 아래와 같이 호출한다.

@Controller
public class ErrorPageController {

    @RequestMapping("/error-page/404")
    public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
             return "error-page/404";
      }

    @RequestMapping("/error-page/500")
      public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
             return "error-page/500";
        }
}

오류 페이지 호출 흐름

Exception이든 sendError()든 결국 WAS까지 전파된다.

그럼 결국 예외는 WAS가 처리해야 할 것이다.

예를 들어 NOT_FOUND 404 가 WAS까지 전달되었다면, WAS는 WebServerCustomizer에 등록된 정보를 이용하여 /error-page/400를 요청한다.

이후는 WAS에서 다른 요청을 처리하는 것과 같은 방식으로 진행된다.

WAS '/error-page/404' 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/400) -> View

필터

위의 오류 페이지 호출 흐름대로라면, 예외 발생 시 필터는 두 차례 호출된다.

클라이언트의 첫 요청 때 한 번, 오류 페이지 요청 때 두 번째로 호출된다.

이미 필터를 거친 요청을 두 번 필터링하는 것은 비효율적이다.

이를 막기 위해서는 요청을 구분할 수 있어야 한다.

구분을 위해 DispatcherType이라는 옵션을 활용할 수 있다.

public enum DispatcherType {
    FORWARD,

    INCLUDE,

    REQUEST,

    ASYNC,

    ERROR
}

클라이언트의 요청은 REQUEST 로, WAS의 오류 페이지 요청은 ERROR 로 인식한다.

필터가 어떤 type에 대하여 작동할지는 필터를 등록할 때 설정할 수 있다.

@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() 메서드로 설정할 수 있다.

인터셉터

위의 필터는 서블릿이 제공하는 기능이기 때문에 DispatcherType 을 이용해 적용 여부를 선택할 수 있었다.

그러나 인터셉터는 필터와 달리 스프링이 제공하는 기능이기 때문에 DispatcherType 과 무관하게 항상 호출된다.

대신 인터셉터는 경로에 따라 추가하거나 제거할 수 있다.

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LogInterceptor())
            .order(1)
            .addPathPatterns("/**")
            .excludePathPatterns("/css/**", "*.ico", "/error", "/error-page/**");
}

위의 코드에서는 excludePathPatterns()를 이용해 /error-page/ 경로에 대한 요청에서 인터셉터를 거치지 않도록 했다.

스프링 부트 예외 처리

서블릿을 이용한 예외 처리에서 개발자가 직접 처리해야 했던 부분들을 스프링 부트는 상당 부분 자동으로 처리해 준다.

스프링 부트는 오류가 발생한 경우 /error 를 알아서 요청한다.

개발자는 적절한 경로에 오류 페이지만 등록해 주면 BasicErrorController 가 뷰 템플릿에 알맞은 템플릿이 있는지 최우선으로 확인하고 없다면 정적 리소스를 확인한다.

정적 리소스에도 적절한 템플릿이 없다면 error.html을 활용한다.

BasicErrorController 는 아래의 정보들을 model에 담아 view로 전달할 수 있다.

* timestamp
* status
* error
* exception
* trace
* message
* errors
* path

이 내용들을 노출하는 것은 보안상의 문제가 될 수 있으니 application.properties 에서 model에 포함하지 않도록 설정할 수 있다.

reference

이 글은 김영한님의 '스프링 MVC 2편'을 듣고 작성했습니다.

'Spring' 카테고리의 다른 글

[Spring Security] Spring security Architecture  (0) 2023.11.05
[MVC2] API 예외 처리  (2) 2023.08.15
[MVC2] ArgumentResolver  (0) 2023.08.10
[MVC2] 로그인 처리 - Filter, Interceptor  (0) 2023.08.10
[MVC2] 로그인 처리 - Cookie, Session  (0) 2023.08.10