Backend/Spring+Boot

SpringBoot 에러페이지 정의

findmypiece 2021. 4. 19. 13:35
728x90

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 도 함께 적용해야 한다.

 

728x90

'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