본문 바로가기
Spring/MVC

서블릿 예외 처리

by o3oppp 2024. 7. 20.
Exception
  1. 자바 직접 실행
    •    실행 도중 예외를 잡지 못하고 처음 실행한 main() 메서드를 넘어서 예외가 던져지면, 예외 정보를 남기고 해당 쓰레드는 종료
  2. 웹 애플리케이션
    •    try - catch로 예외를 잡지 못하면 톰캣 같은 WAS까지 예외 전달
    •    WAS(전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외 발생)

response.sendError
response.sendError(Http 상태 코드)
response.sendError(Http 상태 코드, 오류 메시지)
  • HttpServlerResponse가 제공하는 메서드
  • 호출 시 당장 예외가 발생하는 것이 아닌, 서블릿 컨테이너에게 오류가 발생했다는 것을 전달
  • HTTP 상태 코드와 오류 메시지도 추가 가능
  • WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(response.sendError())

사용 예시

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

오류 페이지 작동 원리
  1. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
  2. WAS '/error-page/500' 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/500) -> view
  • 예를 들어 RuntimeException 예외가 발생하여 WAS까지 전달되면, WAS는 오류 페이지 정보를 확인한다. 확인해보니 해당 오류의 오류 페이지로 /error-page/500이 지정되어 있다면, 해당 오류 페이지를 출력하기 위해 /error-page/500을 다시 요청
    • WAS는 오류 페이지를 단순히 다시 요청하는 것이 아닌 오류 정보를 request의 attribute에 추가해서 넘겨줌
    • request.getAttribute("javax.servlet.error.exception");
  • 중요한 점은 클라이언트는 서버 내부에서 이런 일이 일어난지 전혀 모름. 서버 내부에서 오류 페이지를 찾기 위해 추가적인 호출

필터 예외 처리
  • 오류 발생 시 오류 페이지를 출력하기 위해 WAS 내부에서 다시 한번 호출 발생
  • 이때, 필터, 서블릿, 인터셉터도 모두 다시 호출
  • 그런데 로그인 인증 체크 같은 경우 이미 완료하였기에 서버 내부에서 오류 페이지를 호출한다고 해서 해당 필터나 인터셉터가 한번 더 호출되는 문제 발생
  • 결국 클라이언트로부터 발생한 정상 요청인지, 오류 페이지를 출력하기 위한 내부 요청인지 구분 필요
  • 이러한 문제를 해결하기 위해 DispatcherType이라는 추가 정보를 제공

DispatcherTpye
public enum DispatcherType {
     FORWARD, // 서블릿에서 다른 서블릿, JSP 호출
     INCLUDE, // 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때
     REQUEST, // 클라이언트 요청
     ASYNC, // 서블릿 비동기 호출
     ERROR // 에러 요청
}

사용 예시

@Configuration
public class WebConfig implements WebMvcConfigurer {

    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;
    }
}
  • filterRegistrationBean.setDispatcherTypes에 요청을 처리할 타입 적용
  • 아무것도 넣지 않으면 기본 값이 DispatcherType.REQUEST 이므로 클라이언트의 요청이 있는 경우에만 필터가 적용
  • ServletRequest에서 getDispatcherType() 메서드를 통해 타입 확인 가능

인터셉터 예외 처리
  • 필터의 경우 DispatcherType을 통해 필터 적용 유무 선택 가능
  • 그러나 인터셉터의 경우 서블릿이 제공하는 기능이 아닌 스프링이 제공하는 기능으로 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/**");
    }
}
  • excludePathPatterns에/error, /error-page/** 를 추가하여 중복 호출 제거

전체 흐름
  • 정상 요청
    1. WAS(/hello, dispatcherType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 -> View
  • 오류 요청
    1. WAS(/error-ex, dispatcherType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
    2. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외 발생)
    3. WAS 오류 페이지 확인
    4. WAS(/error-page/500, dispatcherType=ERROR) -> 필터(호출x) -> 서블릿 -> 인터셉터(호출x) -> 컨트롤러(/error-page/500) -> View

/error-ex 오류 요청 시 필터는 DispatcherTpye으로 중복 호출 제거, 인터셉터는 경로 정보로 중복 호출 제거


WebServerFactoryCustomizer 인터페이스
  • 해당 인터페이스를 구현하여 사용자가 직접 표출할 에러 페이지 지정 가능
  • new ErrorPage(상태 코드 또는 예외, 오류 처리할 컨트롤러)를 지정하여 factory에 등록
  • 오류를 처리 할 컨트롤러에서 뷰 템플릿을 return

사용 예시

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

스프링 부트 기본 오류 페이지
  • 예외 처리 페이지를 만들기 위해서 아래와 같이 진행
    • WebServerFactoryCustomizer 인터페이스를 구현하여 WebServerCustomizer를 만들고 ErrorPage 추가
    • ErrorPageController를 만듬
  • 이러한 과정을 스프링 부트는 기본으로 제공
  • ErrorPage를 자동으로 등록해줌. 이때 /error라는 경로로 기본 오류 페이지를 설정
    • 상태코드와 예외를 설정하지 않으면 기본 오류 페이지로 사용
    • 서블릿 밖으로 예외가 발생하거나, response.sendError(...)가 호출되면 모든 오류는 /error를 호출
    • BasicErrorController라는 스프링 컨트롤러를 자동으로 등록(ErrorPage에서 등록한 /error를 매핑해서 처리)
    • BasicErrorController는 오류 페이지 등 HTML 페이지를 제공하는 경우 주로 사용

- BasicErrorController에서 View 선택 우선순위 (구체적인 것이 우선순위가 높음)

  1. View 템플릿
    •    resources/templates/error/500.html
    •    resources/templates/error/5xx.html
  2. 정적 리소스
    •    resources/static/error/400.html
    •    resources/static/error/404.html
    •    resources/static/error/4xx.html
  3. 적용 대상이 없을 때 View 이름(error)
    •    resources/templates/error.html

- BasicErrorController가 model에 담아서 View에 전달하는 기본 정보

timestamp //Fri Feb 05 00 :00:00 KST 2021
status // 400
error // Bad Request
exception // org.springframework.validation.BindException
trace // 예외 trace
message // Validation failed for object='data'.Error count : 1
errors // Errors(BindingResult)
path // '/hello'

// 모든 정보를 노출하는 것은 보안 문제 등이 존재하기에 application.properties에서 model에 포함 여부 선택 가능
server.error.include-exception=true // exception 포함 여부
server.error.include-message=on_param // message 포함 여부
server.error.include-stacktrace=on_param // trace 포함 여부
server.error.include-binding-errors=on_param // errors 포함 여부

// 옵션
never // 사용하지 않음
always // 항상 사용
on_param // 파라미터가 있을 때 사용, ex : URL?message=&error=trace=

 

'Spring > MVC' 카테고리의 다른 글

API 예외 처리  (0) 2024.07.21
HttpEntity  (0) 2024.07.21
스프링 인터셉터  (0) 2024.07.11
서블릿 필터  (0) 2024.07.11
Bean Validation  (0) 2024.06.29