서블릿 예외처리
스프링이 아닌 순수 서블릿 컨테이너의 예외 처리
서블릿 예외 처리 기본 흐름
- 서블릿은 2가지 방식[
Exception
,response.sendError()
] 로 예외처리 가능 Exception
- In JAVA : main 이라는 이름의 쓰레드로 실행되며 예외를 따로 잡지(try, catch) 못하여 main() 메서드를 넘어서 예외가 던져지면, 예외 정보를 남기고 쓰레드 종료
- WAS
- 사용자 요청별로 별도의 쓰레드가 활성되며 서블릿 컨테이너 안에서 실행됨. 어플리케이션안에서 예외를 잡지 못하고, 서블릿 밖으로 까지 예외가 전달되면 WAS까지 예외가 전달됨
- 예외 전파 흐름 :
WAS <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
- WAS의 예외 처리
-
예외가 발생하여 WAS까지 전파되면 tomcat이 기본으로 제공하는 오류 화면을 보여줌 (
HTTP Status 500 – Internal Server Error
) - WAS에서 발생한 Exception의 경우, 서버 내부에서 처리할 수 없는 오류가 발생한 것으로 생각해서 항상 HTTP 상태 코드 500을 반환
-
지정되어 있지 않은 URL일 경우는
HTTP Status 404 – Not Found
(서버 내부에서 발생한 Exception이 아니므로 404 반환)
-
response.sendError(HTTP status code, error msg)
HttpServletResponse
가 제공하는sendError
메서드 (오류를 끼워서 response를 보내는 것)- 호출하자마자 오류가 처리되는 것은 아니고 WAS까지 response가 전달 된 후 해당 error를 띄워주는 것 (즉, 모든 흐름은 정상적일 때와 동일하게 흘러감)
- 해당 메서드를 통해 직접 HTTP 상태 코드와 오류 메시지를 추가할 수 있음 (Exception과 다른 점)
- sendError 전파 흐름 :
WAS(response의 sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(response.sendError() 실행)
- 500 에러 코드 발생 →
response.sendError(500, "500 오류!");
(서블릿 기본 500 오류 화면 발생) - 404 에러 코드 발생 →
response.sendError(404, "404 오류!");
(서블릿 기본 404 오류 화면 발생)
오류 화면 제공
- Exception이 발생하여 서블릿 밖으로 전달되거나 response.sendError() 가 호출되었을 때, 각각의 상황에 맞춘 오류처리( → 오류 화면 제공)
- 과거에는 xml 파일을 통해 오류화면을 등록
- 여기서는 스프링부트가 제공하는 기능을 통해 서블릿 오류 페이지를 등록
- 서블릿 오류 페이지 등록 (
WebServerFactoryCustomizer
interface 이용)WebServerFactoryCustomizer<ConfigurableWebServerFactory>
: web 오류에 따른 오류 화면 처리 기능을 이용할 떄 사용하는 설정같은 느낌 (Spring Boot에서 지정한 설정 Interface).customize
method overriding 필요
@Component public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> { @Override public void customize(ConfigurableWebServerFactory factory) { ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404"); // 오류가 발생하면 path에 Mapping된 Controller를 호출하는 것 ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500"); ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500"); // runtime 자식 예외까지 처리 factory.addErrorPages(errorPage404, errorPage500, errorPageEx); // 등록 } }
new ErrorPage(Http Status Code, path)
: 오류 처리 Controller를 호출하는 것. path가 Controller(오류 페이지 View를 연결해주는)에 매핑된 주소. 즉, 해당 오류코드가 발생하면 path에 매핑된 Controller가 호출됨.factory.addErrorPages
: 설정한 오류 처리 class들 추가하여 Spring이 알 수 있도록 해주는 것Exception
,response.sendError
에 따른 호출response.sendError(404)
: errorPage404 호출 (HttpStatus.NOT_FOUND) → “/error-page/404” path에 매핑된 Controller 호출response.sendError(500)
: errorPage500 호출 (HttpStatus.INTERNAL_SERVER_ERROR) → “/error-page/500” path에 매핑된 Controller 호출RuntimeException
(Exception) 또는 그 자식 타입의 예외: errorPageEx 호출 (RuntimeException.class) → “/error-page/500” path에 매핑된 Controller 호출
-
해당 오류 화면 설정을 처리해주는 Controller (
ErrorPageController
)@RequestMapping("/error-page/404") public String errorPage404() { return "error-page/404"; } @RequestMapping("/error-page/500") public String errorPage500() { return "error-page/500"; }
- 해당 veiw path(resources/templates/error-page/404, …)에 맞는 custom html을 만들어서 사용자 친화적인 오류 화면을 생성하면 됨!
오류 페이지 작동 원리
- 오류 페이지가 어떤 흐름을 가지고 호출되는지에 대해 알아보기
- 예외, sendError 발생 흐름
Exception
(서버 내부 예외 발생) :WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
response.sendError()
:WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(response.sendError())
- 둘 다 WAS까지 전달되는 것을 알 수 있음
- 즉, 오류 발생 후 WAS가 그 오류를 확인한 후 해당 예외를 처리하는 오류 페이지 정보를 확인(오류 페이지 Config 부분(new ErrorPage())을 통해). 이제 이 정보를 통해 다시 요청!
- 오류 페이지 요청 흐름
WAS "/error-page/500(404)" 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/500(404)) -> View
- ErrorPage의 path의 URI로 다시 요청! → 해당 URI를 가진 Controller가 동작!
- 즉, 서버 내부에서 오류 페이지를 찾기 위해 추가적인 호출이 일어나는 것! (서버 외부_Client 는 알 수 없음!)
- 오류 정보 확인
- WAS는 오류 페이지를 단순히 다시 요청만 하는 것이 아니라, 오류 정보를 request 의 attribute 에 추가해서 넘겨줌
- 해당 정보를 Controller에서 request를 통해 사용할 수 있는 것! (필요하다면)
request.getAttribute(정보 이름)
예외 처리 - 필터, 인터셉터
- 서블릿이 제공하는
DispatchType
을 통해 예외 처리에 따른 필터, 인터셉터 이해하기 - Exception with Servlet Filter
- DispatcherType
- 필터의 쓸데없는 중복 적용을 막기 위한 기능
- 요청에 따른 Type을 구분할 수 있게 해주는 것 (사용자의 처음 요청 →
dispatcherType=REQUEST
, 오류로 인한 서버 내부 요청 →dispatchType=ERROR
) - 즉, 실제 고객이 요청한 것인지, 서버가 내부에서 오류 페이지를 요청하는 것인지
DispatcherType
으로 구분 가능! - Type
- REQUEST : 클라이언트 요청
- ERROR : 오류 요청
- FORWARD : MVC에서 배웠던 서블릿에서 다른 서블릿이나 JSP를 호출할 때 [RequestDispatcher.forward(request, response);]
- INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때 [RequestDispatcher.include(request, response);]
- ASYNC : 서블릿 비동기 호출
- 기존의 필터 설정(
WebConfig
의logFilter
)에서 Dispatcher에 대한 설정을 다루면 됨 →filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
setDispatcherTypes
- default는 REQUEST. 즉, 기본으로 사용자의 요청에 대해서만 필터가 적용되도록 자동 설정되어 있음
- 만약, ERROR에만 필터를 적용하고 싶다. 혹은 REQUEST에 더해 ERROR에도 필터를 적용하고 싶다면 해당 기능 이용!!
- DispatcherType
- Exception with Spring Interceptor
- Interceptor는 Filter와는 다르게 DispatcherType Setting (Type에 따른 Filter 동작) 같은 역할을 하는 기능이 없음! → 제외 경로 설정(
.excludePathPatterns(...)
)를 통해 해결! - 또한 setDispatcherType 과 같이 default가 없으므로 꼭 제외 경로에 추가해줘야됨!
.excludePathPatterns(...)
- 제외 요청 경로 설정
- 여기다 해당
new ErrorPage(Status, path)
에서 설정한 path에 대해서excludePathPatterns
에 추가해주면 됨 → 해당 경로에 대한 요청(예외 처리 요청)에 대해서는 인터셉터가 동작하지 않게 됨! .excludePathPatterns(..., "/error-page/**");
- 예외 처리 요청에 대한 URI 패턴을 동일하게 설정해줘야 적용하기 편함 (ex_
/error-page/**
→ /error-page 하위에 대해선 모두 예외처리 요청으로 설정한 것)
- 예외 처리 요청에 대한 URI 패턴을 동일하게 설정해줘야 적용하기 편함 (ex_
- Interceptor는 Filter와는 다르게 DispatcherType Setting (Type에 따른 Filter 동작) 같은 역할을 하는 기능이 없음! → 제외 경로 설정(
- 정리
- 정상 요청
- 오류가 발생하지 않는 “/hello” URI로 요청이 들어온 경우
WAS(/hello, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 -> View
dispatchType=REQUEST
로 들어오고 해당URI
는 exclude에 포함되지 않기 떄문에 필터, 인터셉터 모두 정상 동작
- 오류 요청
- 오류가 발생하는 “/error-ex” URI로 요청이 들어온 경우
- 흐름
WAS(/error-ex, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
WAS 오류 페이지 확인
(new ErrorPage(HTTP Status, path)
를 통해 )WAS(/error-page/500, dispatchType=ERROR) -> 필터(x) -> 서블릿 -> 인터셉터(x) -> 컨트롤러(/error-page/500) -> View
dispatchType=ERROR
,/error-page/**
로 인해 필터, 인터셉터가 모두 한번만 동작하게 됨
- 정상 요청
스프링 예외 처리
- 하지만 스프링 부트를 사용하여 예외 처리 페이지를 적용하면 위와 같은 과정을 모두 자동으로 처리해주기 때문에 아주 간단히 처리가 가능!! (규칙에 따라 View만 만들어주면 됨!)
오류 페이지 처리 with Spring Boot
- Spring Boot가 자동으로 처리하는 기능들
ErrorPage
자동으로 등록 ( “/error” 라는 경로로 기본 오류 페이지를 설정. path = “/error”) → 서블릿 밖으로 예외가 발생(Exception
)하거나,response.sendError(...)
가 호출되면 모든 오류는 “/error” 라는 path를 가진 Controller 를 호출하게 됨BasicErrorController
라는 예외 처리용 Controller 자동 등록 (ErrorPage
를 통해 등록한 “/error” 를 매핑해서 처리하는 컨트롤러)
- 즉, 개발자는 오류 페이지만 등록 하면 됨!! (Spring Boot에서 지정한 경로 규칙을 따라서)
- 경로 규칙
- View Template (
resources/templates/error/…
)- resources/templates/error/500.html : 500 처리
- resources/templates/error/5xx.html : 505,503,… 등을 모두 처리
- 정적 html (
resources/static/error/…
)- resources/static/error/400.html : 400 처리
- resources/static/error/404.html : 404 처리
- resources/static/error/4xx.html : 405, 440, … 등을 모두 처리
- 적용 대상이 없을 때
- resources/templates/error.html
- 발생된 오류 Status에 대한 html이 설정되어 있지 않을 때
- 정해진 경로, 정해진 이름 규칙을 따라야 함. 추가로 더 상세하게 설정되어 있으면 그 상세 html이 우선순위를 갖게 됨
- 해당 규칙에 따라서 html 파일만 만들어주면 알아서 예외 오류 페이지 처리를 해주는 것!
- View Template (
- 오류 정보 렌더링
BasicErrorController
는 다음 정보를Model
에 담아서View
에 전달- View Template은 이를 이용하여
View
에 렌더링 가능 (but, 자주 쓰이는 건 아님 → 오류 정보를 사용자에게 보여주는 건 고려해봐야될 상황이므로) - 오류 정보
- 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
)
- 물론 해당 정보들은 보안상의 문제가 있으므로
BasicErrorController
에서 다음 오류 정보를model
에 포함할지 여부 선택할 수 있음 (Spring Boot 설정 - application.properties에서 가능!) -
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
- true, false
- never, always, on_param(개발 서버에서 디버그 시 이용 가능)