2 분 소요

Exception이 발생했을 때 애플리케이션에서 해당 에러를 잡지 못하고 서블릿 밖까지 전달된다면 어떻게 될까?
다음 과정을 통해 WAS(톰캣 등)까지 전달된다.

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


오류 발생 시 컨트롤러에서 HttpServletResponse.sendError()로 서블릿 컨테이너에게 오류가 발생했다는 걸
알릴 수 있다. 메소드에 파라미터로 HTTP 상태 코드와 메시지를 전달할 수 있으며, 위에서의 Exception과 같은 과정
으로 WAS까지 전달된다.

  • HttpServletResponse.sendError(HTTP 상태 코드)
  • HttpServletResponse.sendError(HTTP 상태 코드, 오류 메시지)

둘 중 어떤 과정으로든 예외가 발생하면 서블릿은 그에 맞는 설정된 오류 페이지를 찾는다. 그러면 서버 내부에서
설정된 오류 페이지를 사용자에게 뷰로 띄워주기 위해 WAS에서 컨트롤러로 요청을 보내게 된다.

WAS(에러페이지 요청) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(도착)


이런 과정을 거치면 필터 또는 인터셉터가 2번 호출될 수 있어 비효율적이다. 반드시 2번 거쳐야 하는 요청이 아니라면
꼭 2번 거칠 필요가 없으므로 사용자로부터 발생한 요청인지, 오류 페이지 출력을 위한 서버 내부 요청인지 구분할 수
있어야 한다. 이를 위해 서블릿은 DispatcherType라는 정보를 제공한다.

DispatcherType

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

DispatcherType는 5가지 값을 가지며 사용자 요청은 REQUEST, 오류는 ERROR이다. (이 2가지가 중요하다)
DispatcherType에 어떤 값이 담겨있냐에 따라 필터, 인터셉터를 거칠지 여부를 결정하면 된다!

서블릿 필터WebMvcConfigurer에 등록할 때 setDispatcherTypes()DispatcherType 값을 설정해줄 수
있어서 오류 페이지 요청만 하고 싶다면 다음처럼 설정하면 된다.

filterRegistrationBean.setDispatcherTypes(DispatcherType.ERROR);


스프링 인터셉터excludePathPatterns()에서 에러 페이지 경로를 넣어주면 에러 발생 시 인터셉터를 거치지
않도록 할 수 있다. (인터셉터는 서블릿이 아닌 스프링이 제공하는 기능이어서 DispatcherType에 무관하게 항상
호출된다)

@Configuration
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(new LogInterceptor())
              .order(1)
              .addPathPatterns("/**")
              .excludePathPatterns("/error-page/**"); //오류 페이지 경로 포함
  }
}


스프링 부트에서 오류 페이지 처리

위처럼 오류 페이지를 등록하고 처리하는데 과정이 복잡하고 필요할 때마다 반복적인 과정이 필요하므로 스프링 부트
에서는 간단하게 오류 페이지를 처리할 수 있도록 하고있다. 결론만 말하면 정해진 경로(/error)에 오류 페이지를
삽입해주면 끝난다.

스프링 부트에서는 오류 페이지 처리를 위해 자동 등록한 BasicErrorController를 거쳐 /error에 있는
오류 페이지를 가져오는데, 파일 이름에 따라 처리되는 우선순위가 다르다.

  • 파일 이름이 정확할 수록 우선순위가 높음
    • 500.html > 5xx.html
    • 404.html > 4xx.html
  • 뷰 템플릿(/templates) > 정적 리소스(/static) > /templates/error.html
  • 4xx는 400번대 오류를, 5xx는 500번대 오류를 처리

BasicErrorController는 에러가 포함된 많은 정보를 model에 담아 뷰로 전달한다. 하지만 에러에 대한 정보를
사용자에게 노출하면 악용의 여지가 있어 위험하다. 기본값은 노출이 안되도록 설정돼 있으나 application.properties
에서 다음과 같이 수정할 수 있다.

server.error.include-exception=true
server.error.include-message=always
server.error.include-stacktrace=always
server.error.include-binding-errors=always
  • never : 노출X
  • always : 항상 노출
  • on_param : 파라미터가 있을 때만 노출

실무에서는 사용자에게 오류 정보가 반드시 노출되지 않도록 하고 간단한 에러 페이지만 노출되도록 해야한다!
오류 정보는 서버에 로그로 남겨서 확인하자.

추가

  • 스프링 whitelabel 오류 페이지 비활성화: server.error.whitelabel.enabled=false
  • 스프링 글로벌 오류 페이지 경로 설정: server.error.path=/error

카테고리:

업데이트:

댓글남기기