스프링부트 기본 에러 페이지 변경하기 - Customize Whitelabel Error Page

@Frank Oh · June 03, 2020 · 7 min read

1. 들어가며

존재하지 않는 API를 접속하게 되면 아래와 같은 Whitelabel Error Page를 자주 접하게 됩니다. 별도 설정을 하지 않았다면 스프링부트에서는 기본적으로 Whitelabel Error Page를 보여줍니다.

image 20200905170917741

오류 처리 관련해서 어떤 처리가 기본적으로 되어 있는지 어떻게 변경을 할 수 있는지 알아보죠.

1.1 BasicErrorController - 기본 오류처리 컨트롤러

스프링부트에서 BasicErrorController가 이런 기본적인 오류처리를 담당합니다. application.properties에서 server.error.path를 설정하지 않았다면 /error가 기본 오류처리 PATH 주소로 지정됩니다.

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
...

1.1.1 Whitelabel error page

브라우져에서 접속하면 Whitelabel Error Page 보여줍니다.

GET http://localhost:8080/notfound
Accept: text/html

...생략...

<html>
<body><h1>Whitelabel Error Page</h1>
<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>
<div id='created'>Sat Sep 05 16:54:38 KST 2020</div>
<div>There was an unexpected error (type=Not Found, status=404).</div>
<div></div>
</body>
</html>

Response code: 404; Time: 212ms; Content length: 286 bytes

Request 해더에 Accept 속성 값이 text/html인 경우에는 아래 코드가 실행되면서 오류 페이지를 뷰로 반환해주고 있습니다.

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

1.1.2 Json 응답

Accept 값이 application/json인 경우에는 응답 값을 JSON 형태로도 내려줍니다.

GET http://localhost:8080/notfound
Accept: application/json

...생략...

{
  "timestamp": "2020-09-05T07:58:28.016+00:00",
  "status": 404,
  "error": "Not Found",
  "message": "",
  "path": "/notfound"
}

Response code: 404; Time: 221ms; Content length: 110 bytes

Json 응답 값은 error(HttpServletRequest request) 메서드에서 처리하여 응답 값을 내려줍니다.

@RequestMapping
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, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
}

응답 값은 getErrorAttributes() 메서드에서 채워주고 있습니다.

public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

  ...생략...
  public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
      Map<String, Object> errorAttributes = new LinkedHashMap();
      errorAttributes.put("timestamp", new Date());
      this.addStatus(errorAttributes, webRequest);
      this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
      this.addPath(errorAttributes, webRequest);
      return errorAttributes;
  }
}

2. Custom Error 페이지에 대한 처리

2.1 Error 관련 Properties

서버 오류 관련 설정은 아래와 같습니다.

키 값 기본 값 설명
server.error.include-binding-errors never 바인딩 오류를 포함시킬 때
server.error.include-exception false 예외 내용을 포함시킬 때
server.error.include-message never 오류 메시지를 포함시킬 때
server.error.include-stacktrace never stacktrace를 포함시킬 떄
server.error.path /error 오류 처리할 컨트롤러 패스
server.error.whitelabel.enabled true 브라우져에서 오류 페이지를 보여줄 지 결정한다.
false로 지정하면 tomcat의 오류 페이지로 로딩이 된다

2.2 특정 응답코드에 대한 Custom Error 페이지 만들기

Custom Error 페이지 만들어서 사용하는 건 간단합니다. 아래 폴더중에 한 곳에 error/{응답코드}.<확장명> 형식으로 파일을 생성하면, 스프링 부트에서 Http 상태 값에 다라서 해당 파일을 로딩해줍니다.

  • 폴더

    • /templates/error
    • /static/error
  • 파일

    • 4xx.<확장명>

      • 400번 대의 모드 상태 코드 발생시 이 파일로 로딩이 된다
    • 404.<확장명>

      • Http 상태 코드가 404인 경우에 이 파일이 로딩이 된다

본 포스팅에서는 Mustache를 View Template Engine으로 사용하였고 templates/error 폴더에 404와 5xx에 해당하는 파일을 생성했습니다.

% tree .
.
├── application.properties
├── static
└── templates
    ├── error
    │   ├── 404.mustache
    │   └── 5xx.mustache
    └── index.mustache

404.mustache 파일을 작성합니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>404</title>
</head>
<body>
404 error page
<p>timestamp : {{timestamp}}</p>
<p>status : {{status}}</p>
<p>error : {{error}}</p>
<p>message : {{message}}</p>
<p>path : {{path}}</p>
</body>
</html>

브라우져에서 존재하지 않는 path로 접속을 하면 404 응답 오류가 발생하여 위 view 파일이 응답으로 처리됩니다.

image 20200907230948266

2.3 별도 ErrorController 를 생성하기

위와 같이 특정 응답 코드에 대해서 뷰 파일을 생성하는 방식은 특정 로직을 수행할 수 없는 단점이 있습니다. 이런 경우에 Custom Error Controller를 생성하여 /error PATH에 대한 호출은 이 컨트롤러에서 처리하도록 할 수 있습니다.

@Slf4j
@Controller
public class CustomErrorController implements ErrorController {

    @GetMapping("/error")
    public String handleError(HttpServletRequest request) {
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        if (status != null) {
            int statusCode = Integer.valueOf(status.toString());
            if (statusCode == HttpStatus.NOT_FOUND.value()) {
                return "errors/404-custom";
            }
        }
        return "error";
    }

    /**
    * 이 메서드는 스프링 부트 2.3.x부터 deprecated 됨
    * - 이 메서드 대신 custom path를 지정하려면 server.error.path 속성으로 지정해야 한다
    */
    @Override
    public String getErrorPath() {
        return null;
    }
}

handleError()에서는 errors/404-custom 뷰를 반환합니다. 404 오류가 발생하면, 별도 뷰를 보여줍니다.

4. 마치며

Whitelabel Error Page가 어떻게 로딩이 되는지 스프링부트의 내부 코드를 간단하게 살펴보았고 어떻게 오류 처리를 다르게 변경할 수 있는지도 알아보았습니다.

전체 소스 코드는 github를 참고해주세요.

5. 참고

@Frank Oh
안녕하세요. 방문해주셔서 감사합니다. 컴퓨터 관련 스터디한 내용 기록하는 블로그입니다.