예외 처리

강한친구·2022년 6월 21일
0

Spring

목록 보기
24/27

서블릿의 예외 처리

Exception

웹 애플리케이션은 사용자 요청별로 별도의 쓰레드가 할당되고, 서블릿 컨테이너 안에서 실행된다. 만약, 애플리케이션에서 예외를 잡지 못하고, 서블릿 밖으로 까지 예외가 전달되면 어떻게 동작할까?

WAS <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)

WAS까지 예외가 올라온 경우, WAS가 어떤 방식으로 처리하는지 알아봐야한다.

Exception의 경우, 톰캣까지 예외가 전달이 되면 WAS에서는 서버에서 무언가 문제가 발생했다고 판단, 500오류를 뿌려주게 된다.

response.sendError(HTTP Status Code, Message)

WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(response.sendError())
@GetMapping("/error-404")
    public void error404(HttpServletResponse response) throws IOException {
        response.sendError(404, "404 오류!");
    }
    
    @GetMapping("/error-500")
    public void error500(HttpServletResponse response) throws IOException {
        response.sendError(500);
    }

sendError의 경우 StatusCode를 담아서 보내줄 수 있다.

WAS가 sendError기록을 확인 한 후, 오류 기록이 있다면 response.sendError()에 넘어온 코드에 맞춰서 처리해준다.


서블릿 오류 제공 화면

과거에는 xml 파일로 오류파일을 등록했으나, 최근에는 스프링부트로 간편하게 등록할 수 있다.

public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {

    @Override
    public void customize(ConfigurableWebServerFactory factory) {
        ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/400");
        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);
    }
}

sendError가 보내는 SC에 맞춰서 오류페이지로 안내하는 기능을 한다.

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

작동원리

위에 적힌 표 처럼, error가 어떤식으로 발생하여도 WAS까지는 전달이 된다.
이때, WAS는 오류페이지 정보를 확인하고 해당하는 오류페이지를 호출하기 위해 요청을 한다.

RuntimeEx 의 경우

RuntimeEx의 경우 "error-page/500"이 매핑되어 있다. 따라서 WAS는 이 페이지를 요청한다. 따라서 Controller가 이에 응답하여 페이지를 반환해준다.

즉, 유저의 1요청에 컨트롤러가 2번 호출되는것이다.

서블릿에러와 필터

1. WAS <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
2. WAS `/error-page/500` 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/errorpage/500)
-> View

이렇게 되면, 필터가 2회나 호출되게 된다. 이는 비효율적이며 경우에 따라서는 해서는 안될 수도 있다.

DispatcherType

필터는 이런 상황을 대비해서 dispatcherType을 제공한다.
고객요청때는 Type=Request로 해서 작동하지만 에러가 발생해서 반환되는 경우에는 Type=Error로 전달되어서 필터가 작동하지 않는다.

log.info("REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);

기존의 로그필터의 doFilter에 다음과 같은 getDispatcher를 보여주는 log를 추가하고

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LogFilter());
        filterFilterRegistrationBean.setOrder(1);
        filterFilterRegistrationBean.addUrlPatterns("/");
        filterFilterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);

        return filterFilterRegistrationBean;
    }

}

filter등록시 dispatcher type을 지정해주면 해당 타입에서만 작동하게 된다.

위와 같은 경우에서는 타입을 두가지를 잡았기에 오류처리때도 filter가 작동하지만, 아무것도 주지 않으면 filter가 1회만 호출되게 된다.

서블릿필터와 인터셉터

인터셉터의 경우 dispatchertype을 지정할 수는 없지만 excludePath 기능으로 오류페이지를 전부 빼버리면 인터셉터 호출 없이 작동하게 된다 .

1. WAS(/error-ex, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
2. WAS <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
3. WAS 오류 페이지 확인
4. WAS(/error-page/500, dispatchType=ERROR) -> 필터(x) -> 서블릿 -> 인터셉터(x) -> 컨트롤러(/error-page/500) -> View

스프링부트의 오류페이지

지금까지 했던 모든 과정을 스프링부트를 이용해서 간편하게 해결 할 수 있다.
BasicErrorController를 등록하고 ErrorPage에서 등록한 /error을 매칭해서 처리하는 컨트롤러이다.

컨트롤러의 처리순서는 다음과 같다.

뷰 선택 우선순위
BasicErrorController 의 처리 순서
1. 뷰 템플릿
resources/templates/error/500.html
resources/templates/error/5xx.html
2. 정적 리소스( static , public )
resources/static/error/400.htmlresources/static/error/404.html
resources/static/error/4xx.html
3. 적용 대상이 없을 때 뷰 이름( error )
resources/templates/error.html

구체적인 파일이 덜 구체적인 파일보다 우선순위가 높게 적용된다 .

오류 정보 제공

오류 정보는 사실 고객에게 최대한 감추는게 좋다. trace 정보들을 전부 보여주면 취약점이 노출 될 수도 있다.

application.properties에

server.error.include-exception=true
server.error.include-message=on_param
server.error.include-stacktrace=on_param
server.error.include-binding-errors=on_param

를 추가하면 정보가 노출이 된다. (on_param은 특정 param이 있을때만 작동)

결론

스프링 제공 오류처리가 제일 간편한다

0개의 댓글