[MVC2] 8. 예외 처리와 오류 페이지

kiwonkim·2021년 11월 21일
0

[ 이전 포스팅 ]

웹 관련 공통처리를 할 수 있는 필터와 인터셉터에 대해 학습했다. AOP 처럼 특정 Url 패턴에 공통처리를 수행하는데, Request 와 Response 객체를 파라미터로 사용하므로 웹 관련 공통처리에 유리하다. 세 가지 시점에 적용할 수 있고, excludePatterns 로 패턴 적용도 유연해서 스프링에서는 주로 인터셉터를 사용한다.

이번엔 컨트롤러에서 처리 중 발생한 예외를 어떻게 처리할지 알아보자.


[ 예외 상황 ]

Exception

WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)

컨트롤러 예외 상황에서 Exception을 던질 수 있다. 이 예외가 중간에 try-catch 로 처리되면 문제가 없지만 처리되지 못하면 WAS 까지 전달된다. 그러면 해당 스레드는 500 에러(서버 내부 에러)를 발생시키고 종료된다.

response.sendError

response.sendError(HTTP 상태 코드)

컨트롤러 예외 상황에서 response.sendError 를 호출할 수 있다. 응답시 WAS까지 거슬러 올라가면, WAS 는 sendError 기록을 확인하고 기본 오류페이지를 보여준다. 예외는 반드시 500 에러를 발동시키지만 sendError 는 상태코드를 변경할 수 있는 차이점이 있다.


[ 서블릿 오류 페이지 ]

WAS는 예외나 sendError 가 확인되면 그에 해당하는 기본 오류페이지를 넘겨준다. 그러나 기본 오류페이지는 고객 친화적이지 않기에 Exception 이나 오류코드에 Url 을 매핑시킬 수 있다.

상태코드, 예외와 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 를 생성해 등록해야한다. 이 과정이 너무 불편한데 스프링 부트는 이 오류페이지 등록을 기본 제공한다.

스프링부트 오류 페이지 자동등록

  • /error 을 URL 로 ErrorPage 를 자동 등록한다.
    -> 오류나 예외가 발생하면 /error 로 재요청을 수행한다.
  • /error 를 매핑해서 처리하는 컨트롤러인 BasicErrorController 를 자동으로 등록한다. BasicErrorController 는 /error + 상태코드를 확인해서 오류 페이지를 응답해준다.

BasicErrorController 의 처리 순서

BasicErrorController 는 오류의 상태코드를 바탕으로 뷰를 넘겨주는데 우선순위에 따라 넘겨준다.

BasicErrorController 가 넘기는 정보

BasicErrorController 는 $timestamp $status $error $message 등의 정보를 model에 담아 뷰로 전달한다. 뷰 렌더링 시 필요에 따라 해당 정보를 사용할 수 있다. application.properties 에서 server.error.include 옵션으로 해당 정보를 model 에 담아 전달할지 안할지 결정할 수 있다.


결론

exception, response.sendError 시 WAS가 재요청을 수행한다. 스프링부트는 /error를 Url 로 재요청을 수행하며, BasicErrorController /error 를 받아서 처리한다. 이 때 상태 코드를 보고, 해당 상태코드의 이름을 갖는 뷰를 렌더링시킨다. 개발자는 상태코드에 알맞는 뷰를 생성하여 resource/templates/error 에 넣어두면 된다.

0개의 댓글