[Spring] 서블릿 예외 처리와 스프링 부트 예외 처리
서블릿 예외 처리
스프링이 아닌 순수 서블릿 컨테이너는 다음 2가지 방식으로 예외 처리를 지원한다.
- `Exception(예외 던지기)`
- `response.sendError(HTTP 상태 코드, 오류 메시지)`
Exception(예외)
자바 직접 실행 : 자바의 main( ) 메서드를 넘어 예외가 던져지면, 예외 정보를 남기고 해당 쓰레드는 종료
웹 애플리케이션
: 요청별로 별도의 쓰레드가 할당되고, 실행
: 예외가 발생했는데 예외를 잡지 못하면, WAS 까지 예외가 전달
: 컨트롤러(예외 발생) > 인터셉터 > 서블릿 > 필터 > WAS(여기까지 전파)
: 스프링 부트가 제공하는 기본 예외 페이지 (WhiteLable 화면)
: `server.error.whitelabel.enabled=false` 으로 옵션 끄면 tomcat 이 제공하는 기본 예외 페이지
response.sendError(HTTP 상태 코드, 오류 메시지)
HttpServletResponse 가 제공하는 sendError 메서드는 response 내부에 오류가 발생했다는 상태를 저장한다.
그리고 서블릿 컨테이너는 고객에게 응답하기 전 response 에 sendError( ) 가 호출되었는지 확인하고,
설정한 오류 코드에 맞는 예외 페이지를 보여준다.
`response.sendError(HTTP 상태 코드)`
`response.sendError(HTTP 상태 코드, 오류 메시지)`
컨트롤러(response.sendError( )) > 인터셉터 > 서블릿 > 필터 > WAS(sendError 호출 기록 확인)
커스텀 에러 페이지
기본 에러 페이지 대신 커스텀 에러 페이지를 제공해보자.
@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
factory.addErrorPages(errorPage404);
}
}
404 오류가 발생하면, 에러 페이지 정보를 찾아 `/errorpage/404` 컨트롤러가 호출된다.
이때, 해당 오류와 그 자식 타입의 오류를 함께 처리한다.
이렇게 오류가 WAS 까지 전파되면, WAS 가 상태에 맞는 컨트롤러를 다시 호출한다.
@Controller
public class ErrorPageController {
@RequestMapping("/error-page/404")
public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
return "error-page/404";
}
}
컨트롤러가 호출되면, `error-page/404` 뷰 템플릿을 화면에 렌더링하여 보여준다.
동작 흐름
컨트롤러(예외 발생) > 인터셉터 > 서블릿 > 필터 > WAS(/error-page/404 호출)
> 필터 > 서블릿 > 인터셉터 > 컨트롤러(/error-page/404) > View
WAS 까지 오류가 전파되면 서버 내부에서 다시 오류 페이지를 호출한다.
이때, 클라이언트는 서버 내부에서 발생하는 일을 알지 못한다.
이렇게 WAS 가 오류 페이지를 요청할 때, request 에 오류 정보를 넘겨준다.
`getAttribute(jakarta.servlet.error.exception)` : 예외
`getAttribute(jakarta.servlet.error.exception_type)` : 예외 타입
`getAttribute(jakarta.servlet.error.message)` : 오류 메시지
`getAttribute(jakarta.servlet.error.request_uri)` : 클라이언트 요청 URI
`getAttribute(jakarta.servlet.error.servlet_name)` : 오류가 발생한 서블릿 이름
`getAttribute(jakarta.servlet.error.status_code)` : HTTP 상태 코드
`getDispatcherType()` : 요청구분
DispatcherType
서버 내부에서 에러 페이지를 호출할 때 필터나 인터셉터가 한번 더 호출되는 것은 비효율 적이다.
요청을 구분하여 특정 요청에 필터를 호출할 수 있다.
`REQUEST` : 클라이언트 요청
`ERROR` : 오류 요청
`FORWARD` : 다른 서블릿이나 JSP 호출할 때
`INCLUDE` : 다른 서블릿이나 JSP 의 결과를 포함할 때
`ASYNC` : 서블릿 비동기 호출
필터에서 사용법
`filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);`
이렇게 하면 클라이언트 요청과 에러 페이지 요청에서 필터가 호출된다.
DispatcherType.REQUEST 는 기본값으로, 생략 시 적용된다.
인터셉터에서 사용법
`registry.excludePathPatterns("/error-page/**");`
이렇게 하면 에러 페이지 경로일 때 인터셉터가 호출되지 않는다.
스프링 부트 에러 처리
스프링 부트는 에러 페이지를 만드는 과정 대부분을 자동으로 처리해 준다.
(WebServerCustomizer, ErrorPage, ErrorPageController 만들기 등)
ErrorPage 를 자동으로 등록하여 `/error` 라는 경로로 기본 에러 페이지를 설정하여,
예외가 발생하면 모든 오류를 `/error` 를 호출하도록 한다.
또한 BasicErrorController 라는 스프링 컨트롤러를 자동으로 등록하여,
ErrorPage 에서 등록한 `/error` 를 매핑하여 처리하는 `BasicErrorController` 컨트롤러를 자동으로 등록한다.
> 참고 <
에러 페이지를 자동으로 등록하는 역할은 `ErrorMvcAutoConfiguration` 클래스가 한다.
뷰 선택 우선순위
1. 뷰 템플릿
: `resources/templates/error/500.html`
: `resources/templates/error/5xx.html`
2. 정적 리소스 (static, public)
: `resources/static/error/400.html`
: `resources/static/error/4xx.html`
3. 적용 대상 없을 때 뷰 이름 (error)
: `resources/templates/error.html`
해당 경로 위치에 HTTP 상태 코드 이름의 뷰 파일을 넣어두면 된다.
이때, `4xx.html` 이면 400 대 오류를 처리한다.
BasicErrorController
model 에 다음 정보를 담아서 뷰에 전달한다.
`timestamp` : 오류 발생 시간
`status` : HTTP 상태 코드
`error` : 에러 유형
`exception` : 예외 클래스
`trace` : 예외의 상세 스택 트레이드
`message` : 예외 메시지
`errors` : Spring 의 BindingResult 객체에서 발생한 검증 오류 정보
`path` : 예외가 발생한 URL 경로
오류 관련 내부 정보를 노출하는 것은 보안상의 문제가 있다.
따라서 BasicErrorController 에서 다음 정보를 model 에 포함할지 여부를 선택할 수 있다.
개발 서버
`server.error.include-exception=true`
`server.error.include-message=on_param`
`server.error.include-stacktrace=on_param`
`server.error.include-binding-errors=on_param`
운영 서버
`server.error.include-exception=false`
`server.error.include-message=never`
`server.error.include-stacktrace=never`
`server.error.include-binding-errors=never`
`never` : 사용하지 않음
`always` : 항상 사용
`on_param` : 파라미터가 있을 때 사용
(ex. `message=&errors=&trace=`)
`on_param` 은 디버그 시 문제를 확인하기 위해 개발 서버에서 사용할 수 있다. (운영 서버에서는 권장 X)
오류 관련 옵션
`server.error.whitelabel.enabled=true` : 스프링 기본 오류 페이지 사용 여부
`server.error.path=/error` : 글로벌 오류 페이지 경로
확장
ErrorController 인터페이스를 상속받아 구현하거나, BasicErrorController 를 상속받아 기능을 추가하면
에러 공통 처리 컨트롤러의 기능을 변경할 수 있다.
출처 | 스프링 MVC 2(김영한) - 인프런