인터셉터에서 RequestBody를 Read 하지 못하는 이유 및 해결방법
들어가기 앞서
Filter는 ServletRequest를 통해 사용자가 보낸 Http요청에 대한 requestBody 값을 읽어 들일 수 있다.
Interceptor에서 또한, ServletRequest를 상속한 HttpServletRequest를 갖는데, 왜 requestBody를 읽을 수 없을까?
[Filter]
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
[HandlerInterceptor]
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exceptio…
이유
ServletRequest를 통해 사용자는 Http 요청을 보내게 된다.(RequestBody)
Filter를 통과하게 되면, Filter에서 RequestBody를 읽게 된다.
이때 RequestBody는 InputStream()을 통해 읽게 된다. HttpServletRequest의 InputStream 은 한번 읽으면 지워진다.
따라서 Interceptor에서 HttpServletRequest를 통해 읽으려 해도 예외 값을 보내는 것이다!
(Interceptor 레벨에서 하게 되면 DispatcherServlet 이후에 동작하므로, 요청 바디가 캐시 하기에 이미 바디가 소모되어있을 수 있어 유의해야 한다.)
컨트롤러의 핸들러 매핑 메서드의 파라미터에 @RequestBody 바인딩 될 때도 InputStream에서 읽어온다.
[해결 방안 1. 스프링 제공하는 ContentCachingRequestWrapper 클래스로 캐싱 ]
ContentCachingRequestWrapper는 스프링에서 제공하는 유틸리티 클래스로, HttpServletRequest를 Wrapper하여 요청 바디를 캐싱할 수 있도록 해준다. 이를 통해 요청 바디를 여러 번 읽을 수 있다. 사용 예시는 다음과 같다.
@WebFilter(urlPatterns = {"/*"}, description = "wrapping request")
public class ContentCachingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
filterChain.doFilter(requestWrapper, response);
}
Filter에서 HttpServletRequest -> ContentCachingRequestWrapper Wrapper 맵핑하여 요청 바디를 캐싱한다. Filter 레벨에서 하는 이유는 서블릿 컨테이너에서 동작하기 때문에 DispatcherServlet이 처리하기 전에 요청을 가로채서 캐싱해야 한다.
public class TestController {
@PostMapping("/test")
public void test(@RequestBody String body, HttpServletRequest request)
// 요청의 path 가져오기
String path = request.getRequestURI();
// 요청의 body 가져오기
ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
String body = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
System.out.println("Http Path = " + path);
System.out.println("Http Body = " + body);
}
}
필터에서 맵핑한 클래스를 Casting(타입 변환) 해서 요청 바디 캐싱한 값을 가져와서 사용하면 된다.