=> 하지만, 실제 Spring을 동작하게되면 대부분 WhiteLadel 에러페이지만 줄줄이 나온다.
그럼 어떻게 해야 톰캣(servlet) 에러 페이지를 나올 수 있께 할까?
먼저 스프링 부트가 제공하는 Error Page가 어디있는지 찾아보고자 했다.
@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
// .. 위 생략
factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
}
}
ConfigurableWebServerFactory
에서, addErrorPages()
메소드로 에러 메시지를 추가해줬기 때문에, ConfigurableWebServerFactory
을 주목했다.ConfigurableWebServerFactory
인터페이스AbstractConfigurableWebServerFactory
이며, 내부 코드를 보면Set<>
형태로 중복되지않게 가지고 있음을 알 수 있다.하지만, 해당 구현체에서 자료구조는 만들어 주지만, ErrorPage는 비어 있었다. 그럼 어디서 만들어서 넣어주는 것일까?
ErrorMvcAutoConfiguration
라는 class였다.ErrorPageCustomizer
가 있다.static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;
private final DispatcherServletPath dispatcherServletPath;
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
this.properties = properties;
this.dispatcherServletPath = dispatcherServletPath;
}
/**
* addErrorPages() 부분
**/
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(
this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
}
/**
* 위 부분 까지만
**/
@Override
public int getOrder() {
return 0;
}
}
default String getRelativePath(String path) {
String prefix = getPrefix();
if (!path.startsWith("/")) {
path = "/" + path;
}
return prefix + path;
}
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
// .. 중략
}
private String path = "/error";
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
//..중략
@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 modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
하지만, 이것만으로 TomCat(Servlet) Error Page가 나오고, WhiteLabelError가 나온다고 알 수 없다.
다시 처음으로 돌아가 기본적으로 ErrorMvcAutoConfiguration.class안에 WhiteLabelErrorPage이라는 내부 클래스가 있다.
추가적으로 다른게 있다면, defaultErrorView , 그니까 기본적인 에러메시지를 WhiteLableError 메시지로 설정한다는 것을 알 수 있다.
StaticView()를 보면 WhiteLabel Error Page를 html로 일일이 만들어 준다는 것을 알 수 있다.
private static class StaticView implements View {
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
private static final Log logger = LogFactory.getLog(StaticView.class);
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (response.isCommitted()) {
String message = getMessage(model);
logger.error(message);
return;
}
response.setContentType(TEXT_HTML_UTF8.toString());
StringBuilder builder = new StringBuilder();
Object timestamp = model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>").append(timestamp).append("</div>")
.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
private String htmlEscape(Object input) {
return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
}
private String getMessage(Map<String, ?> model) {
Object path = model.get("path");
String message = "Cannot render error page for request [" + path + "]";
if (model.get("message") != null) {
message += " and exception [" + model.get("message") + "]";
}
message += " as the response has already been committed.";
message += " As a result, the response may have the wrong status code.";
return message;
}
@Override
public String getContentType() {
return "text/html";
}
}
server.error.whitelabel.enabled=false
DefaultErrorWebExceptionHandler
), 코드로 ErrorPage의 우선순위를 정하는 부분도 나오지만 거기까지는 파헤치진 않았다.(Flux
사용하고 난리남..)whiteLabelErrorPage
은 Spring Boot에서 제공하는 페이지이다.
Flux를 사용하는 부분은 1개가 아닌 여러개를 반환페이지를 구성하는 부분이 아닐까 하네요.
깊은 고찰글 잘 보았습니다.
탐킷 튜닝에 대해서도 학습해보시면 더욱 좋을 듯하네요