프로젝트/Ku:room

인터셉터에서 RequestBody를 Read 하지 못하는 이유 및 해결방법

개발하는 민우 2025. 2. 20. 01:25

 

들어가기 앞서

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(타입 변환) 해서 요청 바디 캐싱한 값을 가져와서 사용하면 된다.