Exception(예외)
애플리케이션에서 예외를 잡지 못하고, 서블릿 밖으로 까지 예외가 전달되면 어떻게 동작할까?
WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
application.properties(스프링 부트가 제공하는 기본 예외 페이지 끄기)
server.error.whitelabel.enabled=false
오류 발생시 tomcat이 기본 제공하는 오류 화면을 볼 수 있다.
HTTP Status 500 – Internal Server Error
response.sendError(HTTP 상태 코드, 오류 메시지)
- 오류가 발생할 때 HttpServletResponse의 sendError 메서드 사용 할 수 있다.
- 호출한다고 당장 예외가 발생하지 않고, 서블릿 컨테이너에게 오류가 발생했다는 점을 전달 가능
response.sendError(HTTP 상태 코드)
response.sendError(HTTP 상태 코드, 오류 메시지)
sendError 흐름
WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러
(response.sendError())
◎ 정리
서블릿 컨테이너가 제공하는 기본 예외 처리 화면은 사용자가 보기에 불편하다.
서블릿 예외 처리 - 오류 페이지 작동 원리
WAS는 해당 예외를 처리하는 오류 페이지 정보를 확인한다.
new ErrorPage(RuntimeException.class, "/error-page/500")
오류 페이지 요청 흐름
WAS `/error-page/500` 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/ 500) -> View
예외 발생과 오류 페이지 요청 흐름
1. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
2. WAS `/error-page/500` 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error- page/500) -> View
중요한 점은 웹 브라우저(클라이언트)는 서버 내부에서 이런 일이 일어나는지 전혀 모른다는 점이다.
오직 서버 내부에서 오류 페이지를 찾기 위해 추가적인 호출을 한다.
◎ 정리
- 예외가 발생해서 WAS까지 전파된다.
- WAS는 오류 페이지 경로를 찾아서 내부에서 오류 페이지를 호출한다.
이때 오류 페이지 경로로 필터, 서블릿, 인터셉터, 컨트롤러가 모두 다시 호출된다.
request.attribute에 서버가 담아준 정보
- javax.servlet.error.exception : 예외
- javax.servlet.error.exception_type : 예외 타입
- javax.servlet.error.message : 오류 메시지
- javax.servlet.error.request_uri : 클라이언트 요청 URI
- javax.servlet.error.servlet_name : 오류가 발생한 서블릿 이름
- javax.servlet.error.status_code : HTTP 상태 코드
서블릿 예외 처리 - 필터
서버 내부에서 오류 페이지를 호출한다고 해서 해당 필터나 인터셉트가 한번 더 호출되는 것은 매우 비효율적
결국 클라이언트로 부터 발생한 정상 요청인지, 아니면 오류 페이지를 출력하기 위한 내부 요청인지 구분할 수 있어야 한다.
서블릿은 DispatcherType 이라는 추가 정보를 제공한다.
log.info("dispatchType={}", request.getDispatcherType())
//오류 페이지 -> dispatchType=ERROR
//고객의 처음 요청 -> dispatcherType=REQUEST
public enum DispatcherType {
FORWARD,
INCLUDE,
REQUEST,
ASYNC,
ERROR
}
DispatcherType
- REQUEST : 클라이언트 요청
- ERROR : 오류 요청
- FORWARD : MVC에서 배웠던 서블릿에서 다른 서블릿이나 JSP를 호출할 때
RequestDispatcher.forward(request, response); - INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때
RequestDispatcher.include(request, response); - ASYNC : 서블릿 비동기 호출
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
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;
}
}
- 두 가지를 모두 넣으면 클라이언트 요청은 물론이고, 오류 페이지 요청에서도 필터가 호출
- default = DispatcherType.REQUEST (클라이언트의 요청이 있는 경우에만 필터 적용)
- 오류 페이지 요청 전용 필터를 적용하고 싶으면 DispatcherType.ERROR 만 지정
서블릿 예외 처리 - 인터셉터
인터셉터 중복 호출 제거
@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/**" //오류 페이지 경로
);
}
//@Bean
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;
}
}
오류 페이지 경로를 excludePathPatterns 를 사용해서 빼주면 된다.
◎ 전체 흐름 정리
/hello 정상 요청
WAS(/hello, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 -> View
/error-ex 오류 요청
1. WAS(/error-ex, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
2. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
3. WAS 오류 페이지 확인
4. WAS(/error-page/500, dispatchType=ERROR) -> 필터(x) -> 서블릿 -> 인터셉터(x) -> 컨트롤러(/error-page/500) -> View
스프링 부트 - 오류 페이지1
◎ 참고: ErrorMvcAutoConfiguration 이라는 클래스가 오류 페이지를 자동으로 등록하는 역할을 한다.
개발자는 오류 페이지만 등록
- BasicErrorController 는 기본적인 로직이 모두 개발되어 있다.
- 개발자는 오류 페이지 화면만 BasicErrorController 가 제공하는 룰과 우선순위에 따라서 등록하면 된다.
- 정적 HTML이면 정적 리소스,
- 동적으로 오류 화면을 만들고 싶으면ㅁㄴ 뷰 템플릿 경로에 오류 페이지 파일 넣으면 된다.
뷰 선택 우선순위: BasicErrorController의 처리 순서
- 뷰템플릿
resources/templates/error/500.html
resources/templates/error/5xx.html - 정적리소스(static,public)
resources/static/error/400.html
resources/static/error/404.html
resources/static/error/4xx.html - 적용 대상이 없을 때 뷰 이름(error)
resources/templates/error.html
구체적인 것이 덜 구체적인 것 보다 우선순위가 높다.
스프링 부트 - 오류 페이지2
<ul>
<li th:text="|timestamp: ${timestamp}|"></li> //Fri Feb 05 00:00:00 KST 2021
<li th:text="|path: ${path}|"></li> //클라이언트 요청 경로 (`/hello`)
<li th:text="|status: ${status}|"></li> //400
<li th:text="|message: ${message}|"></li> //Validation failed for object='data'. Error count: 1
<li th:text="|error: ${error}|"></li> //Bad Request
<li th:text="|exception: ${exception}|"></li> //org.springframework.validation.BindException
<li th:text="|errors: ${errors}|"></li> //Errors(BindingResult)
<li th:text="|trace: ${trace}|"></li> //예외 trace
</ul>
- BasicErrorController 컨트롤러는 위의 정보를 model에 담아서 뷰에 전달한다.
- 뷰 템플릿은 이 값을 활용해서 출력할 수 있다.
application.properties
server.error.include-exception=false : exception 포함 여부( true , false )
server.error.include-message=never : message 포함 여부
server.error.include-stacktrace=never : trace 포함 여부
server.error.include-binding-errors=never : errors 포함 여부
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
- never : 사용하지 않음 (기본 값)
- always :항상 사용
- on_param : 파라미터가 있을 때 사용
이것들을 노출하면 안된다!
사용자에게는고객이 이해할 수 있는 간단한 오류 메시지를 보여주고 오류는 서버에 로그로 남겨서 로그로 확인
스프링 부트 오류 관련 옵션
- server.error.whitelabel.enabled=true : 오류 처리 화면을 못 찾을 시, 스프링 whitelabel 오류 페이지 적용
- server.error.path=/error : 오류 페이지 경로,
스프링이 자동 등록하는 서블릿 글로벌 오류 페이지 경로와 BasicErrorController 오류 컨트롤러 경로에 함께 사용된다.
ex)
- http://localhost:8080/error-404 → 404.html
- http://localhost:8080/error-400 → 4xx.html (400 오류 페이지가 없지만 4xx가 있음)
- http://localhost:8080/error-500 → 500.html
- http://localhost:8080/error-ex → 500.html (예외는 500으로 처리)
◎ 확장 포인트
- 에러 공통 처리 컨트롤러의 기능을 변경하고 싶으면
ErrorController 인터페이스를 상속 받아서 구현하거나
BasicErrorController 상속 받아서 기능을 추가하면 된다.
'Spring > MVC 2편' 카테고리의 다른 글
스프링 타입 컨버터 (0) | 2023.08.08 |
---|---|
API 예외 처리 (0) | 2023.08.08 |
로그인 처리2 - 필터, 인터셉터 (0) | 2023.08.07 |
로그인 처리1 - 쿠키, 세션 (0) | 2023.08.07 |
검증2 - Bean Validation (0) | 2023.08.07 |