ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring MVC - 서블릿의 예외(Exception)에 따른 오류 페이지 등록(오류 페이지 작동 원리, 오류 페이지에서 오류 정보 이용하기)
    Spring/Spring MVC 2022. 2. 10. 15:07
    728x90
    반응형

     

     

     

     

     

     

     

     

     

     

     

     스프링 부트를 사용하면 간단하게 개발자가 만든 예외에 따른 오류 페이지를 클라이언트에게 보여줄 수 있다. 스프링 부트가 오류 페이지를 보여주는 복잡한 과정을 처리해주기 때문에 우리는 간단하게 오류 페이지를 등록할 수 있다.

     

     그럼 내부에서 어떻게 오류 페이지를 호출하는 것일까? 서블릿에서 예외에 따른 오류 페이지를 호출하는 과정을 한번 알아보자.

     

     

     

    서블릿의 예외(Exception)에 따른 오류 페이지 등록

     일반적인 클라이언트의 요청에 따른 흐름은 다음과 같다.

    HTTP 요청 → WAS → 필터 → 서블릿 → 스프링 인터셉터 → 컨트롤러

     

     웹 애플리케이션은 사용자 요청 별로 별도의 쓰레드가 할당되고, 서블릿 컨테이너 안에서 실행된다. 애플리케이션에서 로직 처리 중 예외가 발생했을 경우에 try-catch로 예외에 따른 처리를 해주면 되지만 만약 웹 애플리케이션 내에서 예외를 잡지 못하면 어떻게 될까?

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

     

    Tomcat이 제공하는 기본 오류 페이지

     

     WAS에게 예외 발생 여부를 알려주는 방법 1

     

      throw 키워드를 이용하여 RuntimeException 예외에 발생하면 WAS에게 예외 발생 여부를 알려줄 수 있다. WAS는 해당 예외를 받아 tomcat에 기본으로 내장되어있는 기본 오류 페이지들 중 예외에 맞는 오류 페이지를 보여준다.

     

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

     

     위 이미지처럼 서버에서 발생한 예외이기 때문에 상태 코드가 500인 오류 페이지가 호출된다. 만약 컨트롤러에 mapping 되지 않은 URL 경로를 요청하면 상태 코드가 404(Not Found)인 오류 페이지가 호출될 것이다.

     

    더보기

    위 오류 페이지는 tomcat에서 제공하는 기본 오류 페이지이다.
    application.properties에 다음 설정을 넣어주면 tomcat이 제공하는 기본 예외 페이지를 볼 수 있다.
    server.error.whitelabel.enabled=false

     

    해당 설정을 넣어주지 않으면 스프링 부트에서 제공하는 기본 오류 페이지는 다음과 같다.


     

     WAS에게 예외 발생 여부를 알려주는 방법 2 - response.sendError(HTTP 상태 코드, 오류 메시지)

     

    오류가 발생했을 때 HttpServletResponse 가 제공하는 sendError 라는 메서드를 사용해도 된다. 이 메서드는 예외를 발생시키는 것이 아닌 WAS에게 예외를 발생시키는 오류가 있다는 내용(HTTP 상태 코드, 오류 메시지)을 전달해 줄 수 있다(주의 : 예외가 발생한게 아니다!). 전달받은 WAS는 responsesendError()가 호출되었는지 확인하고, 호출되었다면 상태 코드에 따른 tomcat에서 제공하는 기본 오류 페이지를 보여준다.

     

        @GetMapping("/error-404")
        public void error404(HttpServletResponse response) throws IOException {
            response.sendError(404, "404에러 발생");
        }


     

     

    오류 페이지 등록 

     위에서 보여준 오류 페이지는 tomcat이 기본으로 제공하는 페이지이다. 고객 입장에서는 상당히 불친절하다. 오류 페이지가 무슨 의미인지 어떤 문제로 오류 페이지가 나타났는지 알 수가 없기 때문이다. 따라서 예외에 따른 오류 페이지를 개발자가 직접 오류가 발생한 상태 코드에 따른 페이지를 준비하여 고객에게 친절하게 알려줘야 한다.

     

     

    오류 페이지 등록

     

    • Exception이 발생하거나 response.sendError()가 호출되었을 때
      각각의 상황에 맞는 오류 페이지 등록
    • 개발자가 직접 작성한 오류 페이지를 고객에게 보여줄 수 있다.
    • 개발자가 직접 404.html, 500.html 을 만들었다고 가정

     

    @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(HttpStatus.NOT_FOUND, "/error-page/404")
              - ErrorPage([ HTTP상태 코드 or 예외 ], "오류 페이지 경로")
              - HTTP 상태 코드가 발생 시 WAS /error-page/404 로 URL 요청
              - 404 상태 코드일 때 /error-page/404를 보여준다.

    factory.addErrorPages(errorPage404, errorPage500, errorPageEx)

              - 오류 페이지 등록
              - ex) response.sendError(404) : errorPage404 호출


     

    오류 페이지 요청을 처리하는 컨트롤러 구현

     

    • WAS에서는 등록된 ErrorPage의 상태 코드가 오면 해당 Path로 
      WAS에서 직접 URL 요청을 한다.
    • 따라서 ErrorPage에 등록된 상태 코드에 따른 처리를 할 수 있는 컨트롤러가 필요하다.

     

    @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";
        }
    }

     

    오류 페이지 예시

     

    URL 요청 : localhost:8080/error-404

     


     

    오류 페이지 작동 원리

     

     

    • 예외가 발생하면 WAS까지 예외가 넘어간다
    • WAS는 ErrorPage에 저장된 오류 페이지 경로로 다시 재요청한다.

     


     

     

    오류 페이지에서 오류 정보 이용하기

     오류가 발생하여 해당 오류가 WAS까지 전달이 되면 WAS는 RequestDispatcher 인터페이스에 정의된 상수를 key값으로 오류 정보들을 request에 담아서 다시 재요청을 한다. 즉,  WAS는 오류 페이지를 단순히 재요청만 하는 것이 아니라, 오류 정보를 request영역에 저장해준다. 따라서 오류 페이지를 만들 때 request에 저장되어있는 오류 정보들을 이용할 수 있다.

     

    • ERROR_EXCEPTION = "javax.servlet.error.exception"               : 예외
    • ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type" : 예외 타입
    • ERROR_MESSAGE = "javax.servlet.error.message"                   : 오류 메시지
    • ERROR_REQUEST_URI = "javax.servlet.error.request_uri"           : 클라이언트 요청 URI
    • ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name"      : 오류가 발생한 서블릿 이름
    • ERROR_STATUS_CODE = "javax.servlet.error.status_code"          : HTTP 상태 코드

     

     

    @Slf4j
    @Controller
    public class ErrorPageController {
        //RequestDispatcher 상수로 정의되어 있음
        public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
        public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
        public static final String ERROR_MESSAGE = "javax.servlet.error.message";
        public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
        public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
        public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
    
        @RequestMapping("/error-page/404")
        public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
            log.info("errorPage 404");
            printErrorInfo(request);
            return "error-page/404";
        }
        @RequestMapping("/error-page/500")
        public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
            log.info("errorPage 500");
            printErrorInfo(request);
            return "error-page/500";
        }
        private void printErrorInfo(HttpServletRequest request) {
            log.info("ERROR_EXCEPTION: ex=", request.getAttribute(ERROR_EXCEPTION));
            log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(ERROR_EXCEPTION_TYPE));
            log.info("ERROR_MESSAGE: {}", request.getAttribute(ERROR_MESSAGE)); //ex의 경우 NestedServletException 스프링이 한번 감싸서 반환
            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());
        }
    }

     

    URL 요청 : localhost:8080/error-ex

     

     

    URL 요청 : localhost:8080/error-404

     

     

     

     

     

     

    728x90
    반응형

    댓글

Designed by Tistory.