본문 바로가기

쓸만한 글

스프링부트 3 내장 톰캣에서 400 오류 사용자 정의 화면 보여주기

반응형

클로드, 제미나이 등에서 실패하고, 열심히 검색질해서 해결했다.

https://sh970901.tistory.com/135

 

Valve(ErrorReportVavle)를 이용한 내장 톰캣 스프링 톰캣 버전 감추기

이슈는 간단했지만 해결책은 결국 돌고 돌았던 내용을 정리해보려고 한다. (최종 코드는 맨 하단을 참고)일반적인 url path에 대한 400이나 404 등의 에러 핸들링은 성공적으로 진행되었고 문제가

sh970901.tistory.com

 

위 블로그에서 해결했는데, 여기도 조금 복잡하게 쓰여 있어서 나같은 자바 알못은 헤맬 수 있다.

딱 이렇게만 하면 된다.

1. CustomErrorReportValve 생성

위 블로그에서는 텍스트를 뿌리는 화면인데, 클로드한테 물어서 정적 오류 화면이 나오도록 했다.

오류 화면 위치는 코드에 있는 위치로 해야 한다.

public class CustomErrorReportValve extends ErrorReportValve {
  private static final Logger logger = LoggerFactory.getLogger(CustomErrorReportValve.class);
  private static final String ERROR_PAGE_PATH = "/static/error.html";

  @Override
  protected void report(Request request, Response response, Throwable throwable) {
    try {
      // 상태 코드 로깅
      int statusCode = response.getStatus();
      logger.error("Error occurred. Status Code: {}", statusCode);

      // 에러 HTML 페이지 로드
      String errorPageContent = loadErrorPageContent(statusCode, throwable);

      // 응답 설정
      response.setContentType("text/html");
      response.setCharacterEncoding("UTF-8");

      // 에러 페이지 쓰기
      Writer writer = response.getReporter();
      writer.write(errorPageContent);

      // 응답 완료
      response.finishResponse();
    } catch (IOException e) {
      logger.error("Error reporting error", e);
    }
  }

  // 에러 페이지 내용 로드 메서드
  private String loadErrorPageContent(int statusCode, Throwable throwable) {
    try {
      // 클래스패스에서 정적 에러 페이지 로드
      Resource resource = new ClassPathResource(ERROR_PAGE_PATH);
      String content = Files.readString(Paths.get(resource.getURI()));

      // 에러 정보 동적 치환
      return content
          .replace("${statusCode}", String.valueOf(statusCode))
          .replace("${errorMessage}", getErrorMessage(throwable));
    } catch (IOException e) {
      logger.error("Error loading error page", e);
      return generateDefaultErrorPage(statusCode);
    }
  }

  // 기본 에러 메시지 생성
  private String getErrorMessage(Throwable throwable) {
    return throwable != null
        ? throwable.getMessage()
        : "알 수 없는 오류가 발생했습니다.";
  }

  // 대체 에러 페이지 생성
  private String generateDefaultErrorPage(int statusCode) {
    return String.format(
        "<html><body>" +
            "<h1>오류 %d</h1>" +
            "<p>서버에서 오류가 발생했습니다.</p>" +
            "</body></html>",
        statusCode
    );
  }
}

 

2. TomcatCustomizer 생성

@Component
public class TomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

  @Bean
  public TomcatServletWebServerFactory tomcatFactory() {
    return new TomcatServletWebServerFactory() {
      @Override
      protected void postProcessContext(Context context) {
        //((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
      }
    };
  }
  @Override
  public void customize(TomcatServletWebServerFactory factory) {
    factory.addContextCustomizers((context) -> {
      if (context.getParent() instanceof StandardHost parent) {
        parent.setErrorReportValveClass(CustomErrorReportValve.class.getName());
        parent.addValve(new CustomErrorReportValve());
      }
    });

    factory.addConnectorCustomizers(connector -> {
      if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol<?> protocolHandler) {
        protocolHandler.setKeepAliveTimeout(80000);
        protocolHandler.setMaxKeepAliveRequests(50);
        protocolHandler.setUseKeepAliveResponseHeader(true);
        protocolHandler.setRelaxedPathChars("<>[\\\\]^`{|}");
        protocolHandler.setRelaxedQueryChars("<>[\\\\]^`{|}");
      }
    });

  }

}

 

3. error.html 생성

이건 적당히 사용하고자 하는 화면을 만들면 된다.

경로는 1번 코드에 있는 위치로 하거나, 다른 위치에 두고 코드를 바꾸면 된다.

 

이상 끝.

반응형