728x90
1. 서버 예외 처리
- 서버를 다루면서 다양한 에러를 만나게 된다. 특히나 서버단에서 프로그래밍을 잘못하면 404, 500 등등 엄청나게 다양한 오류를 클라이언트는 마주할 수 밖에 없다.
- 이때 이런 오류에 맞춰서 오류에 대한 예외처리 - 404 페이지, 500 오류 처리 등 - 을 할 수 있는 방법이 있다.
- 예외처리는 서블릿으로 예외 처리하는 방법과 스프링 부트를 통해 예외 처리를 하는 방법 2가지가 있다.
2. 웹 어플리케이션의 예외처리
- 웹 어플리케이션은 사용자 요청별로 별도의 쓰레드가 할당되고 서블릿 컨테이너 안에서 실행된다
- 이때 에플리케이션 안에서 예외가 발생하고 이를 try ~ catch 로 잡아서 처리하면 아무런 문제가 없다.
2) WAS 가 자체적으로 처리하도록 만들기 : 정확히는 Spring 의 오류 기본 페이지 출력하기!
- 컨트롤러에서 예외가 발생한 경우 WAS 까지 올라가게 되고 이에 맞는 페이지를 반환한다.
- 즉 컨트롤러의 예외가 거슬러 올라가서 최종적으로 WAS 까지 전파되고 이에 맞는 예외 페이지가 보여진다 —> 500에러 발생
WAS(여기까지 전파) ← 필터 ← 서블릿 ← 인터셉터 ← 컨트롤러(예외발생)
2) HttpServletResponse 의 sendError 메서드 사용하기
- sendError 메서드를 사용하면 당장 예외가 발생하는 것은 아니지만, 예외가 발생했을 때 즉 error 이 발생했을 때 서블릿 컨테이너에게 에러 상태 코드와 메시지를 담아서 전달할 수 있다.
- 아래와 같은 순서로 컨트롤러에서 WAS 로 전달된다.
WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(response.sendError())
- response.sendError() 를 호출하면 response 내부에는 오류가 발생했다는 상태를 저장해둔다. 그리고 서블릿 컨테이너는 고객의 응답전에 response 에 sendError() 가 호출되었는지 확인한다. 만약 sendError 이 호출되었다면 설정한 오류 코드에 맞추어 기본 오류 페이지를 보여준다.
3. 서블릿 예외 처리 : 오류 화면 제공
1) WebServerCustomizer : WebServerFactoryCustomizer 구현
package HJproject.Hellospring.ServletException;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
// WebServerFactoryCustomizer<ConfigurableWebServerFactory> 인터페이스를 구현하여 커스텀 에러 페이지를 설정할 수 있다
// 만약 404, 500, runtimeException 등이 발생한다면
// WAS 에서는 ErrorPage 에서 설정한 HttpStatus 코드와 함께 Path 위치로 가도록 다시 http 요청을 실행한다.
// 따라서 path 에 해당하는 요청을 처리할 컨트롤러가 필요하다
@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
// 404 error
ErrorPage error404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
// 500 error
ErrorPage error500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
// runtime Exception 및 runtime Exception 의 자식 예외도 포함
ErrorPage exPage = new ErrorPage(RuntimeException.class, "/error-page/500");
factory.addErrorPages(error404, error500, exPage);
}
}
2) ErrorPage : errorPage 컨트롤러
package HJproject.Hellospring.ServletException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Controller
public class ErrorPageController {
@RequestMapping("/error-page/404")
public String error404(HttpServletRequest req, HttpServletResponse resp) {
log.info("errorPage 404");
printErrorInfo(req);
return "error-page/404";
}
@RequestMapping("/error-page/500")
public String error500(HttpServletRequest req, HttpServletResponse resp) {
log.info("errorPage 500");
printErrorInfo(req);
return "error-page/500";
}
private void printErrorInfo(HttpServletRequest req) {
log.info("dispatchTypes= {}", req.getDispatcherType());
}
}
코드 확인 : 커스텀 에러 페이지
내가 만든 커스텀 에러 페이지가 똭!!
4. 서블릿 예외 처리에 따른 중복 호출 해결하기
- 서블릿 예외 처리에 따라서 필터를 사용하게 되면 중복 호출이라는 문제가 발생한다.
- 예를 들어 로그인 인증 체크의 경우 유저가 서버에 접소해서 로그인 인층 체크 페이지로 들어가는 순간 필터나 인터셉터를 통해 한번 로그인 체크를 완료한다.그러나 서버 내부에서 오류가 발생하면 이런 오류 페이지를 호출했을 때 다시 필터나 인터셉터가 호출되기 때문이다.
- 대표적으로 아래와 같은 경우
1. WAS(에러 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예약 발생) <- 에러 발생
2. 전파된 에러를 받고 WAS 가 '/error-page/500' 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/500) -> view
- 이러한 필터나 인터셉터의 중복 호출 문제를 해결하기 위해서는 클라이언트로부터 발생한 정상 요청인지 아니면 오류 페이지를 출력하기 위한 내부 요청인지 구분할 수 있어야 한다.
1) DispatcherType
- 필터는 이러한 중복 호출을 해결하기 위해 DispatcherType 이라는 옵션을 제공한다.
- 정확히는 request.getDispatcherType() 을 사용해서 가져올 수 있다.
- 아래는 DispathcerType enum
public enum DispatcherType {
FORWARD, // MVC 에서 서블릿에서 다른 서블릿이나 jsp 를 호출할 때??
INCLUDE, // 서블릿에서 다른 서블릿이나 jsp 결과를 포함할 때 사용됨
REQUEST, // 클라이언트의 요청
ASYNC, // 서블릿 비동기 호출
ERROR // 오류 요청
}
2) 중복 호출 확인하기 : ServletFilter
먼저 실제로 Filter 가 중복 호출이 되는지 확인해보자
- SpringConfig
@Bean
public FilterRegistrationBean logFilterDispatcher(){
FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
filter.setFilter(new LogFilterException());
filter.setOrder(1);
filter.addUrlPatterns("/*");
// 이 필터는 DispatcherType 이 REQUEST 와 ERROR 인 경우에만 호출된다!!
filter.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
return filter;
}
- ServletFilter
package HJproject.Hellospring.ServletException;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
@Slf4j
// DispatcherType 을 확인하기 위한 logFilter
public class LogFilterException implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("log filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("log filter doFilter");
HttpServletRequest req = (HttpServletRequest) request;
String uri = req.getRequestURI();
String uuid = UUID.randomUUID().toString();
try {
// Dispatcher 을 확인하기 위해 req.getDispatcherType 으로 가져온다
log.info("REQUEST [{}][{}][{}]", uuid, uri, req.getDispatcherType());
chain.doFilter(request, response);
} catch (Exception e) {
throw e;
}finally {
log.info("RESPONSE [{}][{}]", uuid, uri);
}
}
@Override
public void destroy() {
log.info("log filter destory");
}
}
코드 확인 : 필터 중복 호출
이는 아까 이야기했듯이 처음에 클라이언트의 요청에 따라서 서블릿 필터를 거쳐서 컨트롤러까지 도착한다. 그 후 컨트롤러에서는 에러를 뱉어내고, 에러를 들고! WAS 까지 진행한다.
WAS 는 에러를 확인하고 다시 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러까지 도착한다. 컨트롤러는 해당 오류를 확인 후 /error-page/500 으로 응답을 보내게 된다.
3) 중복 호출 제외하기
- 중복 호출을 제거하는 방법은 사실 아주 간단하다. SpringConfig 에서 setDispatcherType 부분만 조정해주면된다.
- 정확히는 이 옵션을 완전히 제거해버리면 된다. 이렇게 하면 서블릿 필터의 기본값은 DispatcherType.REQUEST 인 상태, 즉 클라이언트의 정상 요청일 때만 불러오기 때문에 DispatcherType.ERROR 인 상태에서는 필터가 호출되지 않기 때문이다.
@Bean
public FilterRegistrationBean logFilterDispatcher(){
FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
filter.setFilter(new LogFilterException());
filter.setOrder(1);
filter.addUrlPatterns("/*");
// 아래 설정을 안하면 기본값!! DispatcherType.REQUEST 인 경우만 필터가 호출된다
// filter.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
return filter;
}
- Reference
HTML 404 Error Page Templates - DevBeep
'Java - Spring &&n SpringBoot' 카테고리의 다른 글
SpringSecurity 와 소셜 로그인 : OAuth2, 카카오 로그인, 네이버 로그인 (4) | 2022.09.15 |
---|---|
웹 네트워크 기본 공부 2) HTTP 알아보기 (0) | 2022.08.17 |
Spring - ArgumentResolver (feat.커스텀 어노테이션, 세션) (0) | 2022.08.17 |
Spring - 스프링 인터셉터(3) 스프링 인터셉터 개념과 로그 남기기 (0) | 2022.08.13 |
Spring - 서블릿 필터 다루기(2) : 로그인 여부 체크, 로그인 여부에 따른 페이지 접근 (0) | 2022.08.12 |
댓글