예외 - 서블릿

바그다드·2023년 5월 22일
0

예외

목록 보기
1/9

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

1. Exception(예외)

자바의 경우 메인 메서드 실행의 경우 main이라는 이름의 쓰레드가 실행되는데, 예외가 발생하여 이를 잡지 못하면, 예외정보를 남기고 쓰레드는 종료된다.
반면에 웹 어플리케이션의 경우 사용자 요청마다 쓰레드가 할당되고, 서블릿 컨테이너 안에서 실행된다. 그런데 어플리케이션에서 예외가 발생하고, try catch등을 통해 예외를 잡지 못하면 서블릿 바깥으로 예외가 전달된다.

흐름

WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
  • 먼저 스프링에서 제공하는 기본 예외 화면을 확인해보자

    이제 이 화면 대신에 톰캣에서 제공하는 예외 화면을 확인해보자

#application.properties에 추가

#스프링 기본 예외 화면 비활성화
server.error.whitelabel.enabled=false

컨트롤러 추가

@Slf4j
@Controller
public class ServletExceptionController {

    @GetMapping("/error-ex")
    public void errorEx() {
        throw new RuntimeException("예외 발생!");
    }
}

이제 해당 url로 접근해서 확인해보면 아래처럼 톰캣에서 제공하는 기본 예외 화면을 볼 수 있다.

2. response.sendError(Http 상태 코드, 오류 메세지)

HttpServletResponse를 이용해 당장 예외를 발생시키는 것이 아니라 서블릿 컨테이너에게 오류가 발생했다는 것을 전달하는 방법이다.

흐름

WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(response.sendError())
  • response(http 상태 코드, 오류 메세지)
    코드로 확인해보자. 기존 컨트롤러에 아래 코드를 추가해주자
    @GetMapping("/error-404")
    public void error404(HttpServletResponse response) throws IOException {
        response.sendError(404, "404오류!");
    }

    @GetMapping("/error-500")
    public void error500(HttpServletResponse response) throws IOException {
        response.sendError(500, "500오류!");
    }
  • 각 url로 접근해보면 아래처럼 예외 화면과 함께 상태 코드가 넘어가는 것을 확인할 수 있다.

  • 그런데 화면을 보면 알겠지만 사용자 입장에서는 뭐가 문제인지도 모르고 불편함을 끼칠 수 있다. 따라서 좀 더 의미있는 화면을 제공해보자.

오류 화면 제공

과거에는 web.xml에 오류 화면을 등록했다.

레거시(web.xml)

<web-app>
	<!--404에러가 발생했을 때-->
    <error-page>
    <error-code>404</error-code>
    <location>/error-page/404.html</location>
    </error-page>
    <!--500에러가 발생했을 때-->
    <error-page>
    <error-code>500</error-code>
    <location>/error-page/500.html</location>
    </error-page>
    <!--RuntimeException이 발생했을 때-->
    <error-page>
    <exception-type>java.lang.RuntimeException</exception-type>
    <location>/error-page/500.html</location>
    </error-page>
</web-app>

하지만 지금은 스프링 부트가 제공하는 기능을 이용해 에러 페이지를 등록할 수 있다.

스프링 부트

오류 페이지 등록
@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    @Override
    public void customize(ConfigurableWebServerFactory factory) {

        // 상태코드 발생 시
        ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
        ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");

        // 예외 발생 시
        // RuntimeException의 자식 예외 또한 같이 처리해준다.
        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);

    }
}
  • 에러 페이지를 등록하기 위해서는 WebServerFactoryCustomizer를 상속 받아야 한다.
  • new ErrorPage(예외 또는 상태코드, URL패스);
    에러 페이지 생성
  • factory.addErrorPages(에러 페이지);
    에러 페이지 등록
컨트롤러 생성
@Slf4j
@Controller
public class ErrorPageController {

    @RequestMapping("/error-page/404")
    public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 404");
        return "error-page/404";
    }

    @RequestMapping("/error-page/500")
    public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 500");
        return "error-page/500";
    }
}
뷰 생성
  • 500에러에 대한 뷰
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>500 오류 화면</h2>
    </div>
    <div>
        <p>오류 화면 입니다.</p>
    </div>
    <hr class="my-4">
</div> <!-- /container -->
</body>
</html>
  • 아래처럼 우리가 생성한 에러페이지를 띄울 수 있다.

에러 페이지 호출 흐름

에러 발생
1. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
에러 페이지 호출
2. WAS `/error-page/500` 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/errorpage/
500) -> View
  • 예를 들어 RuntimeException이 발생했다고 가정했을 때, 예외가 컨트롤러부터 WAS까지 전달이 될 것이다.
    그런데 현재 우리는 RuntimeException의 오류 페이지를 /error-page/500으로 지정해 놓았기 때문에 WAS는 오류 페이지 출력을 위해 /error-page/500를 다시 호출한다.
  • 이러한 과정은 클라이언트는 전혀 알지 못하고, 오직 서버 내부에서 오류 페이지를 찾이 위한 추가적인 호출을 한다.
  • 이때 오류 페이지를 호출하는 과정에서 필터, 서블릿, 인터셉터, 컨트롤러가 모두 다시 호출된다.

오류 정보 추가

  • 오류 페이지 호출을 위해서 WAS에서 다시 요청을 할 때, 오류 정보를 request의 Attribute에 추가해서 넘겨준다.
    //RequestDispatcher에 아래의 값들이 상수로 정의되어 있음
    public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
    public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
    public static final String ERROR_MESSAGE = "javax.servlet.error.message";
    public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
    public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
    public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
  
      private void printErrorInfo(HttpServletRequest request) {
  		// 예외
        log.info("ERROR_EXCEPTION: {}", request.getAttribute(ERROR_EXCEPTION));
  		// 예외 타입
        log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(ERROR_EXCEPTION_TYPE));
  		// 에러 메세지
        log.info("ERROR_MESSAGE: {}", request.getAttribute(ERROR_MESSAGE));
  		// 클라이언트 요청 URI
        log.info("ERROR_REQUEST_URI: {}", request.getAttribute(ERROR_REQUEST_URI));
  		// 에러가 발생한 서블릿 이름
        log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(ERROR_SERVLET_NAME));
  		// HTTP상태 코드
        log.info("ERROR_STATUS_CODE: {}", request.getAttribute(ERROR_STATUS_CODE));
        log.info("dispatchType={}", request.getDispatcherType());
    }
  • 메서드를 호출하고 콘솔을 확인해보면 다음과 같이 에러에 대한 정보를 확인할 수 있다.
  • 앞서 WAS에서 에러 페이지를 호출할 때 필터나 인터셉터 등도 다시 호출된다고 했는데 이에 대한 것은 다음 포스팅에서 확인하자.

출처 : 김영한 스프링 MVC2편

profile
꾸준히 하자!

0개의 댓글