[Spring MVC 2편] 8. 예외 처리와 오류 페이지

HJ·2023년 1월 20일
0

Spring MVC 2편

목록 보기
8/13

김영한 님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard


1. 서블릿 예외 처리

WAS ⬅ 필터 ⬅ 서블릿 ⬅ 인터셉터 ⬅ Controller( 예외 발생 )

  • 예외 발생 시 위와 같은 과정으로 전파된다

  • 서블릿은 아래 2가지 방식으로 예외 처리를 함

    • Exception ( 예외 ) : NPE와 같은 예외가 발생한 경우, WAS까지 예외가 전파

    • response.sendError() : 서블릿 컨테이너가 예외가 발생했다고 인지 가능


1-1. Exception

  • 웹 어플리케이션에서는 사용자 요청 별로 Thread가 할당되고 서블릿 컨테이너 안에서 실행

  • 어플리케이션에서 예외를 잡지 못하면 서블릿 밖까지 예외가 전달된다
    ( 서블릿을 넘어서 톰캣 같은 WAS까지 예외가 전달 )

  • WAS는 Exception이 발생하면 서버 내부에서 처리할 수 없는 오류가 발생한 것으로 생각해서 HTTP 상태 코드 500을 반환


1-2. response.sendError()

  • 위 메서드를 를 호출한다고 당장 예외가 발생하는 것은 아니지만, 서블릿 컨테이너에게 오류가 발생했다는 점을 전달할 수 있고 HTTP 상태 코드와 오류 메시지도 추가할 수 있다

    • response.sendError( HTTP 상태 코드 )

    • response.sendError( HTTP 상태 코드, 오류 메세지 )

  • 위 메서드를 호출하면 response 내부에 오류가 발생했다는 상태를 저장

  • 서블릿 컨테이너는 고객에게 응답하기 전에 response에 sendError()가 호출되었는지 확인

    • 호출되었다면 설정한 오류 코드에 맞추어 기본 오류 페이지를 보여준다



2. 서블릿 예외 처리 - 오류 화면 제공

2-1. 과거 xml 방식

<web-app>
 <error-page>
    <error-code>404</error-code>
    <location>/error-page/404.html</location>
 </error-page>
 
 <error-page>
    <exception-type>java.lang.RuntimeException</exception-type>
    <location>/error-page/500.html</location>
 </error-page>
</web-app>
  • 과거에는 web.xml 이라는 파일에 오류 화면을 등록

2-2. 서블릿 오류 페이지 등록

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    @Override
    public void customize(ConfigurableWebServerFactory factory) {

        ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");

        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");
        
        // 오류 페이지 등록
        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);

    }
}
  • WebServerCustomizer : 기본 오류 페이지를 커스텀화하기 위해 사용

  • 위처럼 작성하면 스프링부트가 실행될 때 톰캣에 오류 페이지를 등록

  • Exception을 등록하면 등록한 예외 뿐만 아니라 그 자식 타입의 예외들도 등록한 오류 페이지로 처리된다

  • 오류 페이지를 등록한 후, 오류가 발생했을 때 처리할 수 있는 Controller가 필요

  • 서블릿은 Exception이 발생해서 서블릿 밖으로 전달되거나 sendError()가 호출되었을 때 설정된 오류 페이지를 찾는다


2-3. 오류 페이지 처리 Controller

@Controller
public class ErrorPageController {

    @RequestMapping("/error-page/404")
    public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 404");
        return "error-page/404";
    }
}
  • 2-2에서 등록한 오류 페이지를 처리하는 Controller



3. 서블릿 예외 처리 - 오류 페이지 작동 원리

3-1. 예외 발생과 오류 페이지 처리 흐름

1. WAS ⬅ 필터 ⬅ 서블릿 ⬅ 인터셉터 ⬅ Controller ( 예외 발생 )

2. WAS에서 /error-page/404 다시 요청 ➜ 필터 ➜ 서블릿 ➜ 인터셉터
➜ Controller ( /error-page/404 ) ➜ View

  • 서블릿은 Exception이 발생해서 서블릿 밖으로 전달되거나 sendError()가 호출되었을 때 설정된 오류 페이지를 찾는다

  • 예외가 발생해서 WAS까지 전달된 후, 오류를 확인해서 ErrorPage 에 등록한 경로가 호출 ➜ WAS에서 다시 필터, 서블릿 등을 거쳐 ErrorPage 에 등록한 경로에 대해 처리하는 Controller 까지 다시 호출

    1. 예외 발생 ( ex> 404 ) ➜ WAS까지 전달

    2. WAS에서 2-2에서 등록한 오류 페이지를 보고 발생한 예외에 맞는 경로를 요청
      ( ex> /error-page/404 )

    3. WAS부터 다시 필터, 서블릿 등을 거쳐서 2-3의 Controller가 호출
      ( ex> @RequestMapping("/error-page/404") )

  • 웹 브라우저(클라이언트)는 서버 내부에서 이런 일이 일어나는지 알지 못하고 오직 서버 내부에서 오류 페이지를 찾기 위해 추가적인 호출을 한다

3-2. 오류 정보 추가

  • WAS 는 오류 페이지를 다시 요청할 뿐만 아니라 request.setAttribute()를 통해 오류에 대한 정보를 담아 Controller 에 넘겨준다

  • 필요한 경우, 오류 페이지에 전달된 오류 정보를 사용할 수 있다

  • 정보들은 RequestDispatcher에 상수로 정의되어 있다

    • ex> public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
  • 서버가 request.attribute 에 담는 오류 정보

    • javax.servlet.error.exception : 예외

    • javax.servlet.error.exception_type : 예외 타입

    • javax.servlet.error.message : 오류 메시지

    • javax.servlet.error.request_uri : 클라이언트 요청 URI

    • javax.servlet.error.servlet_name : 오류가 발생한 서블릿 이름

    • javax.servlet.error.status_code : HTTP 상태 코드




4. 서블릿 예외 처리 - DispatcherType

4-1. DispatcherType

  • 오류 페이지를 출력하기 위해 WAS 내부에서 다시 한번 호출이 발생하면 필터, 서블릿, 인터셉터도 모두 다시 호출된다

  • but> 로그인 인증 체크의 경우, 이미 필터나, 인터셉터에서 로그인 체크를 완료했기 때문에 서버 내부에서 오류 페이지를 호출할 때 해당 필터나 인터셉트가 한번 더 호출되는 것은 매우 비효율적

  • 그러므로 클라이언가 요청한 것인지, 아니면 오류 페이지를 출력하기 위한 서버 내부 요청인지 구분할 수 있어야 한다

  • ➡️ 서블릿은 요청 구분을 위해 WAS에서 넘겨줄 때 DispatcherType라는 추가 정보 제공

  • DispatcherType 종류

    • REQUEST : 클라이언트 요청

    • ERROR : 오류 요청 ( 오류 페이지 요청을 위해 WAS가 다시 요청하는 경우 )

    • FORWARD : 서블릿에서 다른 서블릿이나 JSP를 호출할 때
      ( RequestDispatcher.forward(request, response); )

    • INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때
      ( RequestDispatcher.include(request, response);)

    • ASYNC : 서블릿 비동기 호출


4-2. 필터와 DispatcherType

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean logFilter() {
        ...
        // 해당 필터는 아래 두 경우에 호출된다
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
        return filterRegistrationBean;
    }
}
  • 필터는 DispatcherType을 지정해서 중복 호출을 제거할 수 있다

  • setDispatcherTypes()에 지정된 DispatcherType의 경우에만 필터가 호출된다

  • 위의 예시의 경우 클라이언트 요청과 오류 페이지 요청 둘 다 필터가 호출

  • 아무것도 설정하지 않으면 DispatcherType.REQUEST가 기본값


4-3. 인터셉터와 DispatcherType

@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/**");
    }
}
  • 인터셉터는 필터처럼 DispatcherType을 지정할 수 없기 때문에 경로 정보로 중복 호출을 제거한다

  • 등록할 때 setDispatcherTypes()같은 메서드가 없기 때문에 excludePathPatterns() 에서 지정해야함

    • 오류 페이지 경로를 excludePathPatterns() 에 추가하면 오류 발생 시의 로그를 출력하지 않는다



5. 스프링부트 - 오류 페이지

5-1. 이전까지 오류 페이지 만드는 과정

  • WebServerCustomizer 생성

  • 예외 종류에 따라 ErrorPage 추가

  • 예외 처리용 Controller 생성

  • 스프링부트는 위의 과정을 모두 기본으로 제공


5-2. 스프링부트의 오류 페이지

  • ErrorPage 자동 등록

    • /error 경로로 기본 디폴트 오류 페이지를 설정

    • new ErrorPage("/error") 가 자동으로 등록

    • 상태 코드와 예외를 설정하지 않으면 기본 오류 페이지로 사용

    • 서블릿 밖으로 예외가 전달되거나, response.sendError()이 호출되면 모든 오류는 /error를 호출

  • BasicErrorController라는 스프링 컨트롤러 자동 등록

    • ErrorPage에서 등록한 /error을 매핑해서 처리하는 Controller
  • 즉, 아무것도 설정하지 않았을 때 오류가 발생하면 오류 페이지로 /error를 요청하고 스프링부트가 자동으로 등록한 BasicErrorController는 /error 경로를 기본으로 받는다


5-3. 오류 페이지 처리 규칙

  • BasicErrorController가 처리하는 순서

    • 뷰 템플릿 : resources/templates에 있는 html

    • 정적 리소스 : resources/static에 있는 html

    • 적용 대상이 없는 경우 : resources/templates/error.html

  • 같은 우선순위라면 구체적인 파일명이 우선순위가 더 높다

    • ex> 4XX.html보다 404.html이 우선순위가 더 높다

    • ex> 404.html와 4XX.html이 존재 ➜ 404 오류 발생 ➜ 404.html을 오류 페이지로 사용

  • 400번대 오류가 발생하면 4XX.html을 실행하도록 컨트롤러가 작성되어 있다

    • ex> 404.html와 4XX.html이 존재 ➜ 400 오류 발생 ➜ 4XX.html을 오류 페이지로 사용

    • ex> 404.html이 없고 4XX.html이 존재 ➜ 404 오류 발생 ➜ 4XX.html을 오류 페이지로 사용

  • 오류 페이지를 등록하고 싶으면 BasicErrorController가 제공하는 룰과 우선순위에 따라 오류 페이지를 등록할 수 있다

5-4. BasicErrorController가 제공하는 정보

  • BasicErrorController는 몇몇 정보들을 model에 담아서 view에 전달하기 때문에 view template에서 이 값을 활용해 동적 HTML 오류페이지를 만들 수 있다

    • timestamp, path, message, trace, status 등 여러 정보들을 제공
  • but> 오류 관련 내부 정보들을 고객에게 노출하는 것은 보안에 문제가 될 수도 있다

  • application.properties에서 BasicErrorController가 오류 정보를 model 에 포함할지 여부 선택할 수 있다

    • on_param : url에 특정 (쿼리)파라미터가 있을 때만 정보를 제공 ( ?message= 가 있으면 message에 관련된 정보를 제공 )

5-5. 스프링부트 오류 참고

  • server.error.whitelabel.enabled=true : 오류 처리 화면을 찾지 못했을 경우, 스프링 whitelabel 오류 페이지 적용

  • server.error.path=/error : 오류 페이지 경로, 스프링부트가 자동 등록하는 서블릿 글로벌 오류 페이지 경로와 BasicErrorController 오류 컨트롤러 경로에 함께 사용

  • 오류를 공통으로 처리하는 컨트롤러의 기능을 변경하고 싶다면 ErrorController 인터페이스를 상속받아 구현하거나 BasicErrorController를 상속받아 기능을 추가하면 된다

profile
공부한 내용을 정리해서 기록하고 다시 보기 위한 공간

0개의 댓글