์ธํฐ์ ํฐ๋?
ํํฐ์ ๋์ผํ๊ฒ ๊ณตํต ๊ด์ฌ ์ฌํญ์ ํจ๊ณผ์ ์ผ๋ก ํด๊ฒฐํ ์ ์๋ ๊ธฐ์ ์ด๋ค. ์ธํฐ์ ํฐ๋ ์๋ธ๋ฆฟ์ด ์ ๊ณตํ๋ ํํฐ์ ๋ค๋ฅด๊ฒ ์คํ๋ง MVC์์ ์ ๊ณตํ๋ ๊ธฐ์ ์ด๋ฉฐ ํํฐ๋ณด๋ค ์ข ๋ ํธ๋ฆฌํ ๊ธฐ๋ฅ์ ํฌํจํ๊ณ ์๋ค. ์ธํฐ์ ํฐ๋ ์คํ๋ง MVC๊ฐ ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ ๋์คํจ์ณ ์๋ธ๋ฆฟ ์ดํ์ ํธ์ถ๋๊ฒ ๋๋ค. ์คํ๋ง MVC์ ์์์ ์ด ๋์คํจ์ฒ ์๋ธ๋ฆฟ์ด๋ผ๊ณ ๋ณด๋ฉด ๋๋ค.
์ํคํ ์ฒ
ํํฐ VS ์ธํฐ์ ํฐ
ํฐ ํ์๋ ์ฐจ์ด๊ฐ ์๋ค.
์ฐ์ ํํฐ์ ์ธํฐ์ ํฐ์ ์ ์ฒด์ ์ธ ๊ธฐ๋ฅ์๋ ํฐ ์ฐจ์ด๊ฐ ์๋ค. ํํฐ ์ธํฐ์ ํฐ ๋๋ค ์์ฒญ์ ๋ํ ์ ํ ์ฒ๋ฆฌ ์ญํ ์ ์ํํ์ฌ ๋ํ URL Pattern์ ์ด์ฉํ์ฌ ์ด๋ ํ ์์ฒญ์ ๋ํด ์ญํ ์ ์ํํ ๊ฒ์ธ์ง ์ง์ ์ด ๊ฐ๋ฅํ๋ค. ๋ํ request, response ๊ฐ์ฒด๋ฅผ ๋ฐ์์ ๋ถ๊ฐ์ ์ธ ๋ก์ง ๋ํ ๊ฐ๋ฐ์๊ฐ ์ง์ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.
์์ธํ ๋ณด์์ผ ์ฐจ์ด์ ์ด ๋ณด์ธ๋ค.
์ ์ฒด์ ์ธ ๊ธฐ๋ฅ์ ๋ณด๋ฉด ๊ฐ์ง๋ง ์์ธํ ๋ค์ฌ๋ค๋ณด๋ฉด ํ์คํ ์ฐจ์ด๊ฐ ์กด์ฌํ๋ค. ๋ค์์ ๋๋ง์ ์ท์ฅ ํ๋ก์ ํธ์์ ๊ตฌํํ ๊ฐ๊ฐ ๋ก๊ทธ ์ธํฐ์ ํฐ์ ๋ก๊ทธ ํํฐ์ ๋ฉ์ธ ๊ตฌํ๋ถ์ด๋ค.
๋ก๊ทธ ํํฐ
@Slf4j
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("init");
}
@Override
public void destroy() {
log.info("destroy");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("doFilter");
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
String method = httpRequest.getMethod();
String uuid = UUID.randomUUID().toString();
try {
log.info("REQUEST [{}][{}: {}]", uuid, method, requestURI);
chain.doFilter(request, response); // ๋ค์ ํํฐ ํธ์ถ
} catch (Exception e) {
throw e;
} finally {
log.info("RESPONSE [{}][{}: {}]", uuid, method, requestURI);
}
}
}
๋ก๊ทธ ์ธํฐ์ ํฐ
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String method = request.getMethod();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid);
/**
* @RequestMapping : HandlerMethod๊ฐ ๋์ด์ด
* ์ ์ ๋ฆฌ์์ค : ResourceHttpRequestHandler
*/
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler; // ํธ์ถํ ์ปจํธ๋กค๋ฌ ๋ฉ์๋์ ๋ชจ๋ ์ ๋ณด
}
log.info("REQUEST [{}][{}: {}], [{}]", uuid, method, requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String method = request.getMethod();
String uuid = UUID.randomUUID().toString();
log.info("RESPONSE [{}][{}: {}], [{}]", uuid, method, requestURI, handler);
if (ex != null) {
log.error("afterCompletion error!!", ex);
}
}
}
์์ ์์ค๋ฅผ ๋น๊ตํ์ ๋ ๊ฐ์ฅ ๋จผ์ ๋์ ๋๋ ๋ถ๋ถ์ ์ธํฐ์ ํฐ๋ ์ด ์ธ ๊ฐ์ง์ ๊ตฌํ๋ถ๊ฐ ์กด์ฌํ๋ ๊ฒ์ด๋ค. ๊ฐ๊ฐ์ ๊ตฌํ๋ถ์ ๋ํด ์ค๋ช ์ ํด๋ณด์๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์ฐธ๊ณ ) HandlerInterceptor์ override ๋ฉ์๋
preHandle : ์ปจํธ๋กค๋ฌ ํธ์ถ ์ ์ ํธ์ถ, true๋ฉด ๊ณ์ ์งํ, false๋ฉด ์ข ๋ฃ
postHandle : ์ปจํธ๋กค๋ฌ ํธ์ถ ํ์ ํธ์ถ (์ ํํ๋ ํธ๋ค๋ฌ ์ด๋ํฐ ํธ์ถ ํ์ ํธ์ถ), ๋ง์ฝ ์ปจํธ๋กค๋ฌ์์ ์์ธ ๋ฐ์์ postHandle ์ ํธ์ถ ์๋จ
afterCompletion : ๋ทฐ๊ฐ ๋ ๋๋ง ๋ ์ดํ์ ํธ์ถ, ์์ธ ๋ฐ์์ ์๋ฅผ ํตํด ์์ธ๋ฅผ ํ์ธํ ์ ์์ -> ์์ธ๊ฐ ๋ฐ์ํด๋ ํธ์ถ
doFilter ๋ฉ์๋์์ ๋ชจ๋ ๊ฒ์ด ์ด๋ฃจ์ด์ง๋ ํํฐ์ ๋ค๋ฅด๊ฒ ์ธํฐ์ ํฐ๋ ์ปจํธ๋กค๋ฌ๋ฅผ ์คํํ๊ธฐ ์ (preHandle), ํธ๋ค๋ฌ๋ฅผ ์คํํ ํ(postHandle), ๋ทฐ๋ฅผ ๋ ๋๋งํ ํ(afterCompletion) ๋ฑ, Servlet๋ด์์๋ ๋ฉ์๋์ ๋ฐ๋ผ ์คํ ์์ ์ ๋ค๋ฅด๊ฒ ๊ฐ์ ธ๊ฐ๋ค.
๋ค์์ผ๋ก ๊ตฌํ๋ถ๋ฅผ ์ด๋ฃจ๊ณ ์๋ ๋ฉ์๋๋ค์ ์ธ์๊ฐ ๋ค๋ฅธ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ธํฐ์ ํฐ๋ ๋ค์ํ ์์ ์ ๋ฐ๋ผ ๋๋ ์ ธ ์๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ๋๋ฌธ์ ๊ทธ ์์ ์ ์กด์ฌํ๋ ๋ถ๊ฐ์ ์ธ ๋ณ์๋ค์ ์ธ์๋ก ๊ฐ์ง๊ณ ์๋ค. ํธ๋ค๋ฌ๋ฅผ ์ด์ฉํ์ฌ ์ปจํธ๋กค๋ฌ ๋ฉ์๋์ ๋ชจ๋ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ ์ข ๋ ๋ณตํฉ์ ์ธ ๋ก์ง์ ๊ตฌํํ ์ ์๊ณ Exception ๋ณ์๋ฅผ ์ด์ฉํ์ฌ ์์ธ ๋ก์ง์ ์์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค. ๋ํ Spring์ Dispatcher-Servlet ๋ด๋ถ์ ์์นํ๊ธฐ ๋๋ฌธ์ Exception์ @ControllerAdvice์์ @ExceptionHandler๋ฅผ ์ด์ฉํ์ฌ ์ฒ๋ฆฌํ ์ ์๋ค.
๋ค์์ผ๋ก ์ธํฐ์ ํฐ๋ ํํฐ๋ณด๋ค ์ข ๋ ์ธ์ธํ urlPattern ์ค์ ์ด ๊ฐ๋ฅํ๋ค.
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/api/v1/**")
.excludePathPatterns("/api/v1/wardrobes");
}
@Bean
public FilterRegistrationBean<Filter> apiAuthorizationFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new ApiAuthorizationFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/api/v1/*");
return filterRegistrationBean;
}
excludePathPattern()๊ณผ ๊ฐ์ด ์ธํฐ์ ํฐ๋ ์ข ๋ ์์ธํ urlPatterns๋ฅผ ์ง์ ํ์ฌ ์ธ์ธํ ์ปจํธ๋กค์ด ๊ฐ๋ฅํ๋ค.
๋ง์ง๋ง์ผ๋ก ํํฐ๋ ๋ค์ ํํฐ์๊ฒ request, response ๊ฐ์ฒด๋ฅผ ์ปค์คํฐ๋ง์ด์งํ์ฌ ๋๊ฒจ์ค ์ ์๋ค.
public class SomeFilter implements Filter {
//...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
chain.doFilter(new CustomServletRequest(), new CustomResponse());
}
}
์์ ๊ฐ์ด ์ปค์คํฐ๋ง์ด์ง๋ Request, Response ๊ฐ์ฒด๋ฅผ ๋๊ฒจ์ฃผ๋ ๊ฒฝ์ฐ๋ ์ด๋ค ๊ฒฝ์ฐ์ผ๊น?
HttpServletRequest์ ๋ฐ๋๋ฅผ ๋ก๊น ํ๋ ์ํฉ์ ์๋ก ๋ค ์ ์๋ค. ์์ฒญ ์ ๋ณด๋ฅผ ๋ด์ HttpServletRequest ๊ฐ์ฒด์๋ ์์ฒญ ๋ฐ๋๋ฅผ ๋ฐํํ๋ getInputStream() ๋ฉ์๋๊ฐ ์กด์ฌํ๋ค. ํ์ง๋ง ์ด ๋ฉ์๋๋ ์ต์ด 1ํ ํธ์ถ ํ์๋ ์ฌํธ์ถ์ java.io.IOException: Stream closed. ์์ธ๋ฅผ ๋ฐ์์ํจ๋ค. ์ผ๋ฐ์ ์ธ ์ํฉ์์๋ ๋ฌธ์ ๊ฐ ์์ง๋ง ๋ก๊น ๋ฑ์ ๋ชฉ์ ์ผ๋ก ์์ฒญ ๋ฐ๋๋ฅผ ํ๋ํ๊ณ ์ ํ ๊ฒฝ์ฐ ์ ๊ทผ์ด ๋ถ๊ฐ๋ฅํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ์ด ๊ฒฝ์ฐ getInputStream()์ ์ฌํธ์ถ์ด ๊ฐ๋ฅํ๋๋ก ์ปค์คํฐ๋ง์ด์ง ๋ HttpServletRequest๋ฅผ ์ ์ํด์ผ ํ๋ค.
๋๋ง์ ์ท์ฅ ํ๋ก์ ํธ์ Request ํค๋์ Body๋ฅผ ๋ก๊น ํ๋ ๋ถ๋ถ์ด ์๋ค. ๊ทธ๋ฐ๋ฐ Body๋ฅผ ๋ก๊น ํ๊ธฐ ์ํด์๋ ์์์ ์ธ๊ธํ ๊ฒ์ฒ๋ผ ์ปค์คํฐ๋ง์ด์ง๋ Response, Request ๊ฐ์ฒด๋ฅผ ๋๊ฒจ์ค ํ์๊ฐ ์์๋ค.
public class CustomServletWrappingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper wrappingRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappingResponse = new ContentCachingResponseWrapper(response);
filterChain.doFilter(wrappingRequest, wrappingResponse);
wrappingResponse.copyBodyToResponse();
}
}
๊ทธ๋์ ์์ ๊ฐ์ด ์ปค์คํฐ๋ง์ด์ง๋ Request์ Response ๊ฐ์ฒด๋ฅผ ๋๊ฒจ์ค ์ ์๋๋ก ํํฐ๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ๊ฐ๋ฐํ์ฌ ๋ก๊น ๊ฐ๋ฐ์ ์๋ฃํ ์ ์์๋ค.
์ฐธ๊ณ ) ContentCachingRequestWrapper, ContentCachingResponseWrapper?
ContentCachingRequestWrapper, ContentCachingResponseWrapper๋ inputstream, outputstream์ ๋ด์ฉ์ ์บ์ฑํ๊ณ , ๋์ค์ ๋ค์ ๊ฐ์ ธ์ฌ ์ ์๋๋ก ํ๋ HttpServletResponse์ Wrapper์ด๋ค.
Reference
https://supawer0728.github.io/2018/04/04/spring-filter-interceptor/
https://jsonobject.tistory.com/522