Spring ์์ธ์ฒ๋ฆฌ - ์ค๋ฅํ์ด์ง(404,500)
by JiwonDev์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ ์ด๋ค ๋ฐฉ๋ฒ์ผ๋ก ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋์ง ๋ฐฐ์๋ณด๊ณ Spring์์์ ์์ธ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์.
# ์๋ธ๋ฆฟ ์์ธ ์ฒ๋ฆฌ
์๋ธ๋ฆฟ์ WAS์์ ๋์ํ๋ ์๋ฐ ๊ฐ์ฒด์ด๋ค. ์๋ธ๋ฆฟ์ ๋ค์ 2๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
- Java Exception (์์ธ)
- response.sendError (HTTP ์ํ์ฝ๋, ์ค๋ฅ ๋ฉ์์ง ์ ์ก)
@ Java Exception
์๋ฐ์์ ์์ธ์ฒ๋ฆฌ๋ฅผ ํ์ง์์ผ๋ฉด ์ฑ์ ์ง์ ์ ์ธ Main๊น์ง ์ ๋ฌ๋๊ณ , Main์์ ์ฒ๋ฆฌ๋ฅผ ํ์ง ์์ผ๋ฉด JVM์ผ๋ก ๋๊ฒจ์ง๋ฉฐ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์์ฑํ๊ณ ํด๋น ์ฐ๋ ๋๋ฅผ ์ข ๋ฃํ๊ฒ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ํฐ์บฃ ๊ฐ์ WAS์์ ์คํ๋๋ ์๋ธ๋ฆฟ์ ์ด๋ป๊ฒ ์์ธ๊ฐ ์ฒ๋ฆฌ๋ ๊น?
@Slf4j
@Controller
public class ServletExController {
@GetMapping("/error-ex")
public void errorEx() { // ๊ถ๊ธํ๋ ์ง์ ์์ธ๋ฅผ ๋ง๋ค์ด์ ๋์ ธ๋ณด์.
throw new RuntimeException("์์ธ ๋ฐ์!");
}
}
์ฆ ์๋ฒ ๋ด๋ถ์์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ์ง ๋ชปํ๋ค๋ฉด WAS๋ ๋ฐํํ Response๊ฐ ์๋ค.
- HTTP 500 ์ฝ๋์ ํจ๊ป ๊ธฐ๋ณธ ์ค๋ฅํ์ด์ง๋ฅผ ๋ฐํํ๊ฒ ๋๋ค.
- ์์ธ๊ฐ ํฐ์ง๊ฑด ์๋์ง๋ง, ํด๋น URL์ ์ฒ๋ฆฌํ ์ปจํธ๋กค๋ฌ๊ฐ ์๋ค๋ฉด HTTP 404 ์ฝ๋๋ฅผ ์ ์กํ๋ค.
@ response.sendError (HTTP ์ํ์ฝ๋, ์ค๋ฅ ๋ฉ์์ง)
์์ธ์ฒ๋ฆฌ๋ฅผ ํ์ง์๊ณ WAS๊น์ง ๋์ ธ๋ฒ๋ฆฌ๋ ๊ฑด ์ข์ ๋ฐฉ๋ฒ์ด ์๋๋ค. ๊ทธ๋์ ์๋ธ๋ฆฟ response ๊ฐ์ฒด์๋ Exception ๋์ ์ฌ์ฉํ ์ ์๋ sendError() ๋ผ๋ ๋ฉ์๋๊ฐ ์กด์ฌํ๋ค.
โก response.senError()๊ฐ ํธ์ถ๋๋ฉด, WAS๋ ํด๋ผ์ด์ธํธ ์์ฒญ์ ํด๋น ์ค๋ฅ์ฝ๋์ ๋ฉ์์ง๋ฅผ ์ ์กํ๋ค.
@Slf4j
@Controller
public class ServletExController {
@GetMapping("/error-404")
public void error404(HttpServletResponse response) throws IOException {
response.sendError(404, "404 ์ค๋ฅ!"); // ์ค๋ฅ์ฝ๋์ ๋ฉ์์ง๋ฅผ ๋ด๊ฐ ์ง์ ํ ์ ์๋ค.
}
}
# ์๋ธ๋ฆฟ ์ค๋ฅ ํ์ด์ง ๋ง๋ค๊ธฐ
์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ ์คํ๋ง๋ถํธ๊ฐ ์ ๊ณตํ๋ ์ค๋ฅํ๋ฉด์ ๊ทธ๋๋ก ๊ณ ๊ฐ์๊ฒ ์ ์กํ๋ฉด ์๋๋ค. ์ด๋ค ์๋ฐ ์ฝ๋์์ ์์ธ๊ฐ ๋ฐ์ํ๋์ง๋ฅผ ๊ณ ๊ฐ์๊ฒ ๊ตณ์ด ๋ณด์ฌ์ค ํ์๋ ์๋ค.
๊ทธ๋์ ์๋ธ๋ฆฟ์์๋ ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ๋ค์ ํธ์ถํ URL์ ์ง์ ํ ์ ์๋ค.
โก ์๋ฅผ ๋ค์ด WAS์ HTTP 404 ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด https://myhome/error-page/404 ๋ก ๋ค์ ์์ฒญํ๋ค.
โก ๋ง์น ๋ฆฌ๋ค์ด๋ ์
์ฒ๋ผ ๋์ํ์ง๋ง, HTTP ์์ฒญ์ ๋ค์ ๋ณด๋ด๋๊ฑด์๋๋ค. ํด๋ผ์ด์ธํธ๋ ์ด ์ฌ์ค์ ๋ชจ๋ฅธ๋ค.
@Component
public class WebServerCustomizer
implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
// 200, 300 ์ด๋ ๊ฒ ์ซ์๋ฅผ ์ง์ ์จ๋ ๋์ง๋ง ์์ ๊ฐ์ ์ฌ์ฉํ๋๊ฒ ๋ชจ๋ ๋ฐฉ๋ฉด์์ ์ข๋ค.
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
// ๊ทธ ์ธ RuntimeException์ ๋ฐ์ธ๋ฉ ๋๋ ์์ธ๊ฐ ๋ฐ์ํ์ ๋ ์ฌ์ฉํ ํ์ด์ง
ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");
factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
}
}
์ฆ ์์์ ๋ง๋ ์ค๋ฅ URL ๊ฒฝ๋ก๋ค์ ์ปจํธ๋กค๋ฌ์์ ๊ตฌํํด์ฃผ๋ฉด ๋๋ค.
@Slf4j
@Controller
public class ErrorPageController {
// ์ฐธ๊ณ ๋ก ์๋ ๊ฐ๋ค์ RequestDispatcher ์ ์์๋ก ์ ์๋์ด ์์
public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
public static final String ERROR_MESSAGE = "javax.servlet.error.message";
public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
@RequestMapping("/error-page/404")
public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
log.info("errorPage 404");
this.printErrorInfo(request);
return "error-page/404"; // 404.html ํ์ด์ง ๊ฒฝ๋ก
}
@RequestMapping("/error-page/500")
public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
log.info("errorPage 500");
this.printErrorInfo(request);
return "error-page/500"; // 500.html ํ์ด์ง ๊ฒฝ๋ก
}
private void printErrorInfo(HttpServletRequest request) {
//RequestDispatcher ์ ์๋ ์์๊ฐ์ผ๋ก ์ด๋ ๊ฒ request์์ ์๋ฌ์ ๋ณด๋ฅผ ๋ฐ์์ฌ ์ ์๋ค.
log.info("ERROR_EXCEPTION: {}", request.getAttribute(ERROR_EXCEPTION));
log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(ERROR_EXCEPTION_TYPE));
log.info("ERROR_MESSAGE: {}", request.getAttribute(ERROR_MESSAGE));
log.info("ERROR_REQUEST_URI: {}", request.getAttribute(ERROR_REQUEST_URI));
log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(ERROR_SERVLET_NAME));
log.info("ERROR_STATUS_CODE: {}", request.getAttribute(ERROR_STATUS_CODE));
log.info("dispatchType={}", request.getDispatcherType());
}
}
# ์ค๋ฅ๊ฐ ์ํ์ ๋ ํํฐ, ์ธํฐ์ ํฐ
์ด์ฒ๋ผ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด, WAS์์๋ ๋ง์น ๋ฆฌ๋ค์ด๋ ์ ๋ ๊ฒ์ฒ๋ผ ์ค๋ฅํ์ด์ง์ฉ ์ปจํธ๋กค๋ฌ๋ฅผ ๋์ํ๋ค. ํ์ง๋ง ์ด ๊ฒฝ์ฐ์๋ ํํฐ์ ์ธํฐ์ ํฐ๋ ํ๋ฒ ๋ ์คํ๋๋๋ฐ ์ด๋ ์๋ฏธ์๋ ๋ถํ์ํ ์์ ์ด๋ค.
๊ทธ๋์ ์๋ธ๋ฆฟ์ ์์ธ๊ฐ ๋ฐ์ํ์ ๋ ํํฐ์์ ๋์์ ๊ฒฐ์ ํ ์ ์๊ฒ DispatcherTypes๋ผ๋ Enum๊ฐ์ ์ ๊ณตํ๋ค.
@ํํฐ์ DispatcherType
ํํฐ ๊ฐ์ฒด์๋ setDispatcherTypes( dispatcherType ) ๋ผ๋ ๋ฉ์๋๊ฐ ์๋ค.
ํด๋น ์์ฒญ ํ์ ์ผ ๋๋ง ํํฐ๊ฐ ์คํ๋๋๋ก ์ค์ ํ๋ ๊ธฐ๋ฅ์ผ๋ก, ๊ธฐ๋ณธ๊ฐ์ REQUEST๋ง ๋ฑ๋ก๋์ด์๋ค.
โก ๊ทธ๋์ ์ฌ์ค์ ์ค๋ฅํ์ด์ง ์์ฒญ์๋ ํํฐ๊ฐ ์คํ๋์ง ์์๋ค. ๊ธฐ๋ณธ๊ฐ์ REQUEST ํ๋ ๋ฐ์ ์์ด์ ๊ทธ๋ ๋ค.
public enum DispatcherType {
FORWARD, // RequestDispatcher.forward(request, response); ํฌ์๋ ์์ฒญ
INCLUDE, // RequestDispatcher.include(request, response); ๋ค๋ฅธ ์๋ธ๋ฆฟ์ ๊ฒฐ๊ณผ๋ฅผ ํฌํจ
REQUEST, // ํด๋ผ์ด์ธํธ ํธ์ถ
ASYNC, // ์๋ธ๋ฆฟ ๋น๋๊ธฐ ํธ์ถ
ERROR //์ค๋ฅ
}
@ ์ธํฐ์ ํฐ์ DispatcherType
์ธํฐ์ ํฐ๋ ์คํ๋ง MVC์ DipatcherServlet์ด ์คํ ๋ ์ดํ์ ์๋ํ๋ ์คํ๋ง์ ์ถ๊ฐ๊ธฐ๋ฅ์ด๋ค. ์ธํฐ์ ํฐ์์๋ request.DispatcherType ์ ๋ณด๋ฅผ ๋ฐ์์ฌ ์๋ ์์ง๋ง ํด๋น ํ์ ์ ์ข ๋ฅ์ ์๊ด์์ด ์ธํฐ์ ํฐ๋ ํญ์ ํธ์ถ๋๋ค.
โก ํ์ง๋ง ๊ฑฑ์ ๋ง๋ผ. ์ธํฐ์ ํฐ์๋ excludePathPatterns(...)๋ฅผ ์ถ๊ฐํด์ ์๋ํ์ง ์๋๋ก ๋ง๋ค ์ ์๋ค.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**") // ์ฌ์ค ๋ฒ๊ฑฐ๋กญ๊ฒ DispatcherType์ ์ง์ ํ ํ์๊ฐ ์๋ค.
.excludePathPatterns("/css/**", "*.ico", "/error", "/error-page/**");//์ค๋ฅ ํ์ด์ง ๊ฒฝ๋ก
}
}
# ์คํ๋ง ๋ถํธ์ ์ค๋ฅํ์ด์ง ๊ธฐ๋ฅ
์์ธ์ฒ๋ฆฌ๋ฅผ ๋ง๋ค๊ธฐ์ํด ์ฐ๋ฆฌ๋ WebServerCustomizer, ErrorPage, ErroController๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด์ผ ํ๋ค.
์ฌ์ค ๋ฒ๊ฑฐ๋กญ๊ฒ ๋ง๋ค์ง ์์๋ ์คํ๋ง ๋ถํธ๋ ์ด๋ฐ ๊ณผ์ ์ ๋ชจ๋ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ค.
๐์คํ๋ง๋ถํธ์ ๋์
- /error/์ค๋ฅ์ฝ๋.html ์ ๊ธฐ๋ณธ ์ค๋ฅํ์ด์ง ๊ฒฝ๋ก๋ก ์ธ์ํ๋ค.
- ์คํ๋ง ๋ถํธ์์ BasicErrorController๋ผ๋ ์ปจํธ๋กค๋ฌ๋ฅผ ์๋์ผ๋ก ๋ฑ๋กํ๋ค.
โก ๊ธฐ๋ณธ ๊ฒฝ๋ก(/error/์ค๋ฅ์ฝ๋.html)๋ฅผ ๋งคํํ๋ค.
- ์คํ๋ง ๋ถํธ์ ErrorMvcAutoConfiguration๋ผ๋ ํด๋์ค๊ฐ ์ฌ์ฉํ ์ค๋ฅํ์ด์ง๋ฅผ WAS์ ๋ฑ๋กํ๋ค.
โก ์ด์ ์๋ธ๋ฆฟ ์์ธ๋ response.sendError(...)๊ฐ ํธ์ถ๋๋ฉด ๋ชจ๋ ์ค๋ฅ๋ฅผ /error/... URL์ ํธ์ถํ๋ค.
# ์ค์ ๋ก ๊ฐ๋ฐ์๊ฐ ํ ์ผ
์ด์ ์ค๋ฅํ์ด์ง๋ฅผ ๋ง๋ค๋ ค๊ณ WebServerCustomizer๋ฅผ ์ค์ ํ๊ณ , ์ปจํธ๋กค๋ฌ๋ฅผ ๋ง๋คํ์๊ฐ ์๋ค.
์คํ๋ง๋ถํธ๋ฅผ ์ด์ฉํด์ /error/... ๊ฒฝ๋ก์ 404.html ๊ฐ์ ์๋ฌํ์ด์ง๋ฅผ ๋ง๋ค๋ฉด ์๋์ผ๋ก ๋ฑ๋ก๋๋ค.
- BasicErrorController ์์๋ ์ค๋ฅ์ ๋ณด๋ค์ ๋ชจ๋ธ์ ๋ด์ ๋ทฐ์ ์ ๋ฌํ๋ค. ํ์ํ๋ค๋ฉด ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="py-5 text-center">
<h2>500 ์ค๋ฅ ํ๋ฉด ์คํ๋ง ๋ถํธ ์ ๊ณต</h2>
</div>
<h1> ์ค๋ฅํ๋ฉด์
๋๋ค </h1>
<ul>
<li>์ค๋ฅ ์ ๋ณด</li>
<ul> <!-- ํ์๋ฆฌํ์ ๋ฌธ์์ด ์์ฑ๊ธฐ๋ฅ. ์๋ฐ์คํฌ๋ฆฝํธ์ `string ${value}s`์ ๋น์ท -->
<li th:text="|timestamp: ${timestamp}|"></li>
<li th:text="|path: ${path}|"></li>
<li th:text="|status: ${status}|"></li>
<li th:text="|message: ${message}|"></li>
<li th:text="|error: ${error}|"></li>
<li th:text="|exception: ${exception}|"></li>
<li th:text="|errors: ${errors}|"></li>
<li th:text="|trace: ${trace}|"></li>
</ul>
</li>
</ul>
</div> <!-- /container -->
</body>
</html>
ํ์ง๋ง ์ด๋ฌํ ์ ๋ณด๋ค์ ์ค๋ฅํ์ด์ง์ ๋์ฐ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ&์๋ฒ ๋ณด์์ ์ข์ง ์์ ๋ฐฉ๋ฒ์ด๋ค. ๊ทธ๋์ application.properties๋ฅผ ์ด์ฉํด์ BasicErrorController์์ ์ด๋ฌํ ์ ๋ณด๋ฅผ ์ ๋ฌํ์ง ์๋๋ก ๋ง๋ค ์ ์๋ค.
//application.properties
server.error.path=/error // ๊ธฐ๋ณธ ์์ธ ํ์ด์ง ๊ฒฝ๋ก
server.error.whitelabel.enabled=false // ์คํ๋ง ๊ธฐ๋ณธ ์ค๋ฅํ์ด์ง[whitelabel.html]์ ์ฌ์ฉ์ฌ๋ถ
server.error.include-exception= true // exception ์ด๋ฆ ์ ๋ฌ์ฌ๋ถ
server.error.include-message= always // ์๋ฌ ๋ฉ์์ง๋ฅผ ํญ์ ์ ๋ฌ
server.error.include-stacktrace= on_param // [URL ์์ฒญ ํ๋ผ๋ฉํ๊ฐ ์์ ๋๋ง] ์ ๋ฌ
server.error.include-binding-errors= never // [์ ๋ฌ์ํจ]
- on_param์ ์๋์ ๊ฐ์ด URL ์์ฒญ์ด ์์ ๋๋ง ๋ณด์ด๊ฒํ๋ ๊ธฐ๋ฅ์ด๋ค.
โก https://jiwondev.com/error-page?message=&trace= (message์ trace ์ ๋ณด ์์ฒญ)
- ์ ์ฌ์ฉํ ์ผ์ ์์ง๋ง, ๋๋ง์ ์ค๋ฅ ์ปจํธ๋กค๋ฌ๊ฐ ํ์ํ๋ค๋ฉด BasicErrorController๋ฅผ ์์๋ฐ๊ฑฐ๋ ErrorController ์ธํฐํ์ด์ค๋ฅผ ์ด์ฉํ์ฌ ์ฝ๋๋ฅผ ํ์ฅํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
'๐ฑ Spring Framework > Spring MVC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring ํ์ ์ปจ๋ฒํฐ (Converter, Formatter) (0) | 2021.08.31 |
---|---|
Spring ์์ธ์ฒ๋ฆฌ - API, @ExceptionHandler (0) | 2021.08.30 |
Spring Login#2 ํํฐ, ์ธํฐ์ ํฐ, ArugmentResolver (0) | 2021.08.30 |
Spring Login#1 ์ฟ ํค์ ์ธ์ (0) | 2021.08.29 |
Spring Bean Validation#2 ์คํ๋ง ๋น ๊ฒ์ฆ (0) | 2021.08.29 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev