SpringBoot 는 아무것도 하지 않더라도 아래와 같은 기본 에러페이지를 제공한다.
또한 view 자원 root 디렉토리에 error 디렉토리를 생성하고 그 하위에 에러상태코드에 해당하는 파일을 만들어 놓으면 각 에러 발생시 자동으로 해당 페이지가 연결된다. view자원의 root 디렉토리는 어떤 라이브러리를 사용하느냐에 따라 다르고 입맛에 맞게 얼마든지 커스텀이 가능한데 일반적인 경우을 살펴보면 아래와 같다.
기본 html: src/main/resources/public
Thymeleaf: src/main/resources/templates
jsp: src/main/webapp
즉, 각 라이브러리의 error 페이지는 아래 경로에 만들면 된다.
기본 html: src/main/resources/public/error/404.html
Thymeleaf: src/main/resources/templates/error/404.html
jsp: src/main/webapp/error/404.jsp
이런 처리가 가능한 이유는 Spring 자체에서 에러 발생시 /error 로 포워딩하는 ErrorPageFilter 필터가 등록되어 있기 때문이고 이를 처리하는 RequestMapping 이 정의된 BasicErrorController 가 등록되어 있기 때문이다. BasicErrorController 코드를 보면 알겠지만 /error 경로는 application.yml or applicaiton.properties 에서 server.error.path or error.path 로 재정의 가능하다.
또 한가지 중요한 것은 ErrorPageFilter 에서 처리하는 것은 Controller에서 처리되지 못한 예외라는 점이다. @ControllerAdvice, @ExceptionHandler 조합으로 Controller 단에서 예외가 처리되었다면 ErrorPageFilter 까지 처리가 위임되진 않는다.
아무튼 이러한 ErrorPageFilter 의 동작방식을 바꾸고 싶다면 ErrorPageFilter, BasicErrorController 클래스들을 커스터마이징 하면 된다. 그런데 둘 다 수정할 필요는 없고 일반적으로 BasicErrorController 확장만으로 해결된다. 예를 들어 에러페이지 자체를 커스텀하게 사용하고 싶은데 401, 403, 404, 500, 503 에 대한 페이지만 각각 만들고 나머지 에러는 공통 에러페이지로 일괄 처리하고 싶은 경우를 생각해볼 수 있다.
ErrorPageFilter 에서 에러발생시 BasicErrorController 로 포워딩 되도록 되어 있기 때문에 BasicErrorController 를 상속한 @Controller 클래스를 만들어서 html에 대한 요청을 처리할 errorHtml() 과 json에 대한 요청을 처리할 error()와 메소드 정도를 재정의하면 된다. 각 메소드 에서는 HttpServletRequest 을 통해 HttpStatus 를 구한 뒤 각 에러코드에 맞게 ModelAndView 를 명시하면 된다. 예를 들어 아래와 같이 구현하면 된다.
@Controller
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes,
ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
}
@Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus hs = getStatus(request);
ModelAndView mv = new ModelAndView("500.html");
switch (hs){
case UNAUTHORIZED:
mv.setViewName("400.html");
break;
case NOT_FOUND:
mv.setViewName("401.html");
break;
case INTERNAL_SERVER_ERROR:
mv.setViewName("404.html");
break;
case SERVICE_UNAVAILABLE:
mv.setViewName("500.html");
break;
}
return mv;
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, ErrorAttributeOptions.defaults());
return new ResponseEntity<>(body, status);
}
}
그런데 이러한 에러페이지는 uri만 알면 누구나 직접적으로 접근할수 있게 되는데 이건 좀 문제가 있어 보인다. 이걸 제한할 방법이 있는지 더 살펴볼 필요가 있다.
그리고 또 한가지. 예외처리는 위와 같이 ErrorController 를 확장하는 방법과 @ControllerAdvice, @ExceptionHandler 조합을 활용하는 방법이 있는데 두가지 모두 적용해야 할까? 기본적으로 MVC 개발이라면 둘 다 정의해야 한다.
존재하지 않는 자원으로 접근할 경우 404에러가 발생할텐데 이 경우 ApplicationContext 진입조차 하지 못한 ServletContext 상태이기 때문에 @ControllerAdvice 로는 처리될 수 없다. 이런 경우 때문에 ErrorController 도 함께 적용해야 한다.
'Backend > Spring+Boot' 카테고리의 다른 글
SpringBoot 정적자원 (0) | 2021.04.20 |
---|---|
Spring 어노테이션 정리 (0) | 2021.04.20 |
SpringBoot 2.4 Config file processing (0) | 2021.04.16 |
h2 DB 초기 데이터 생성 (0) | 2021.03.30 |
SpringBoot Interceptor 구현 (0) | 2021.03.25 |