스프링 부트 - 예외 처리와 오류페이지

SeungTaek·2021년 8월 15일
1
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
틀린 내용이 있을 수 있습니다.

📒서블릿 예외 처리

  • 서블릿은 2가지 방식으로 예외 처리를 지원한다.
    • Exception(예외)
    • response.sendError(HTTP 상태코드, 오류 메시지)

📌Exception

  • 웹 애플리케이션은 사용자 요청별로 별도의 쓰레드가 할당되고, 서블릿 컨테이너 안에서 실행된다.
  • 어딘선가 예외를 처리하지 않고, 서블릿 밖으로까지 예외가 전달되면 결국 WAS까지 에외가 전달된다.

WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)

  • 그럼 tomcat에서는 HTTP 상태 코드 500을 응답해주면서 오류 페이지를 보여준다.
  • 즉, Exception의 경우 서버 내부에서 처리할 수 없는 오류 발생으로 생각해서 상태코드 500을 반환해준다.

cf) 없는 페이지를 호출하면 HTTP 상태코드로 404를 반환해준다.


📌response.sendError

  • response.sendError을 보낸다고 호출한다고 당장 예외가 발생하는건 아니다.

  • WAS까지 가서야 에러구나!를 알 수 있다.

  • 이 메서드를 사용하면 HTTP 상태 코드와 오류 메시지도 추가할 수 있다.

    • response.sendError(HTTP 상태 코드)

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

    @GetMapping("/error-404")
    public void error404(HttpServletResponse response) throws IOException {
     	response.sendError(404, "404 오류!");
    }
  • 아래 흐름은 정상적인 흐름이다. 위에서 말했던 것처럼 WAS까지 와서야 sendError가 있는것을 보고 어? 에러를 보내줬구나를 알고 처리해준다.

WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (response.sendError())

  • 설정한 오류 코드에 맞추어 기본 오류 페이지를 보여준다.

📒서블릿 예외 처리 - 기본

  • Exception이든 response.sendError든 WAS까지 오류가 전달되면, WAS에서는 오류 페이지를 띄우기 위해 오류 페이지 요청을 하게 된다.

  • 오류 화면을 제공하기 위한 html 파일 설정은 web.xml 또는 스프링부트에서 제공하는 기능을 사용해서 설정할 수 있다.

    1. web.xml 사용
     <web-app>
      <error-page>
     	<error-code>404</error-code>
      	<location>/error-page/404.html</location>
      </error-page>
      <error-page>
      	<error-code>500</error-code>
     	<location>/error-page/500.html</location>
      </error-page>
      <error-page>
      	<exception-type>java.lang.RuntimeException</exception-type>
      	<location>/error-page/500.html</location>
      </error-page>
     </web-app>
  1. 스프링 부트가 제공하는 기능 사용
@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
   	@Override
   	public void customize(ConfigurableWebServerFactory factory) {
   		ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/errorpage/404");
   		ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
   		ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/errorpage/500");
       		factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
   	}
}
  • 오류 페이지를 띄우는 순서

WAS /error-page/500 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/500)-> View

  • 요청은 위에서 설정한 경로로 들어온다.
  • 이때 필터, 서블릿, 인터셉터, 컨트롤러가 전부 다시 호출된다.

오류페이지를 보여줄때도 필터와 인터셉터를 실행하지 않게 하는 방법이 있을까? 혹은 오류 페이지를 띄울때 특별히 호출되게 하고 싶은 필터나 인터셉터가 있을까?


📌서블릿 예외 처리 - 필터

@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;
 }
  • 위 코드에서 filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);을 집중하자

  • 평상시 정상적인 호출에서는 DispatcherTypeREQUEST로 넘어온다.

  • 하지만 WAS에서 오류가 발생하고 오류 페이지를 띄우기 위한 호출에서는 DispatcherTypeERROR로 넘어온다.

  • 이를 이용해 오류 페이지 요청 전용 필터를 적용할 수 있는데, DispatcherType.ERROR만 지정하면 된다.

  • 디폴트값으론 DispatcherType.REQUEST만 선언되어 있다.


📌서블릿 예외 처리 - 인터셉터 호출 안하게 하기

  • 인터셉터는 스프링이 제공하는 기능으로서 DispatcherType과 무관하게 항상 호출된다.
  • 대신에 요청 경로에 따라서 추가하거나 제외하기 쉽게 되어 있기 때문에, 이 설정을 이용하여 오류 페이지 경로를 excludePathPatterns 를 사용해서 빼주면 된다.
@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/**" //오류 페이지 경로
		 );
	 }
 }

📒전체 흐름 정리

  • 가정
    • /hello에서 예외가 터진다.
    • 필터는 DispatchType 으로 중복 호출 제거 ( dispatchType=REQUEST만 설정 ) (기본값이므로 따로 설정해주지 않아도 된다.)
    • 인터셉터는 경로 정보로 중복 호출 제거( excludePathPatterns("/error-page/**") )
1. WAS(/hello, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
2. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
3. WAS 오류 페이지 확인
4. WAS(/error-page/500, dispatchType=ERROR) -> 서블릿 -> 컨트롤러(/error-page/500) -> View

📒스프링 부트가 제공하는 오류 페이지 기능

  • 스프링 부트는 오류 페이지 기본 경로를 /error로 설정해준다.

  • 즉, 서블릿 밖으로 예외가 발생하거나, response.sendError(...)가 호출되면 모든 오류는 /error를 호출하게 된다.

  • 스프링 부트는BasicErrorController라는 스프링 컨트롤러를 자동으로 등록해주는데, 이 컨트롤러는 /error를 매핑해서 어느 페이지를 보여줄지 처리하는 컨트롤러다.

  • BasicErrorController에 기본적인 로직이 다 개발되어 있으므로, 개발자는 오류 페이지를 만든 후 뷰 템플릿 경로에 넣어두기만 하면 된다.

🎈BasicErrorController의 처리 순서

  1. 뷰 템플릿
  • resources/templates/error/500.html
  • resources/templates/error/5xx.html
  1. 정적 리소스( static , public)
  • resources/static/error/400.html
  • resources/static/error/404.html
  • resources/static/error/4xx.html
  1. 적용 대상이 없을 때 뷰 이름( error )
  • resources/templates/error.html
  • 우선순위
    • 정적 리소스< 뷰 템플릿
    • 5xx 덜 구체적 < 404, 500 구체적인것
    • 5xx, 4xx라고 하면 500대, 400대 오류를 처리해준다.
  • 예를 들어보자
    • 등록한 오류 페이지
    • resources/templates/error/4xx.html
    • resources/templates/error/404.html
    • resources/templates/error/500.html
    • 발생한 오류와 보여지는 오류 페이지
    • 404오류 -> 404.html
    • 400 오류 -> 4xx.html
    • 500 오류 -> 500.html
    • 예외 발생 오류 -> 500.html (예외는 500으로 처리한다.)

🎈더 나아가서..

  • BasicErrorController는 뷰를 호출할 때 model에 다음 정보를 담아서 전달해준다. 뷰 템플릿이 이 값을 활용해 출력할 수 있다.
* 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`)
  • but.. 오류 관련 내부 정보들을 고객에게 노출하는 것은 좋지 않다. 고객이 해당 정보 를 읽어도 혼란만 더해지고, 보안상 문제가 될 수도 있다.

  • 그래서 BasicErrorController 오류 컨트롤러에서 다음 오류 정보를 model 에 포함할지 여부 선택할 수 있다.

    • application.properties에다가

    • server.error.include-exception=true #exception 포함 여부(t, f)
      server.error.include-message=on_param #message 포함 여부
      server.error.include-stacktrace=on_param #trace 포함 여부
      server.error.include-binding-errors=on_param #errors 포함 여부
    • 3가지 옵션을 사용할 수 있다. (기본 값은 never이다.)

      • never : 사용하지 않음
      • always :항상 사용
      • on_param : 파라미터가 있을 때 사용

실무에서는 이것들을 노출하면 안된다.

고객에게는 이쁜 오류 화면을, 오류는 서버에 로그로 남겨야 된다.

cf) 스프링 부트 오류 관련 옵션

  • server.error.whitelabel.enabled=true
    • 오류 처리 화면을 못 찾을 시, 스프링 whitelabel 오류 페이지 적용
  • server.error.path=/error
    • 오류 페이지 경로, 스프링이 자동 등록하는 서블릿 글로벌 오류 페이지
      경로와 BasicErrorController 오류 컨트롤러 경로에 함께 사용된다.

인프런의 '스프링 MVC 2편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요.

profile
I Think So!

0개의 댓글