[25/08/29] HttpServletRequest 여러 번 읽기

김기수·6일 전
0

HttpServletRequest 여러 번 읽기가 필요한 이유


  • 과제에서 AOP(Aspect-Oriented Programming)를 활용해 로깅을 구현하던 중 RequestBody를 JSON 타입으로 로깅하라는 요구사항이 있었다.
  • @Aspect가 적용된 클래스에서 RequestBody를 가져오려면 우선
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
    이런식으로 HttpServletRequest를 불러와서 내부의 Body를 읽어들여야 하는데, 이렇게 받은 요청은 한번 읽으면 데이터가 휘발돼 로깅 작업이 끝난 뒤의 Controller에서 요청을 받지 못하는 상황이 발생한다.

HttpServletRequest란


  • HttpServletRequest 객체는 자바 서블릿 API의 일부로, 클라이언트가 서버에 보낸 HTTP 요청을 나타낸다.
  • HttpServletRequest은 매우 짧은 사용주기를 갖고 있어 한번 getInputStream()을 통해 읽으면 휘발된다.
  • 사용 주기가 짧은 이유
    • HTTP는 Stateless 프로토콜로 독립적일 필요가 있다.
    • 서버가 보내는 많은 요청을 동시에 처리해야 하고 요청마다 생성되는 HttpServletRequest의 임시 객체가 메모리가 쌓이기 때문에 빠르게 지워줄 필요가 있다.

해결 방법


  • HttpServletRequest은 한 번 읽으면 휘발되기 때문에 로깅을 하기 힘든 구조지만 스프링은 ContentCachingRequestWrapper 클래스를 제공한다.
  • ContentCachingRequestWrapper 클래스는 HttpServletRequest를 메모리에 저장해 여러번을 읽을 수 있게 만들어준다.
// HttpServletRequest
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
// ContentCachingRequestWrapper
ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
// RequestBody
String requestBody = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
  • (ContentCachingRequestWrapper)를 사용하려면 사전 작업으로 필터를 구현해야 한다.
@Component
// 클래스가 서블릿 필터임을 나타내며, 모든 URL 패턴("/*")에 대해 필터가 적용, decription으로 수행하는 역할 설명
@WebFilter(urlPatterns = "/*", description = "Wrapping Request")
// OncePerRequestFilter를 상속받으면 한 요청 당 필터 로직이 한 번만 실행 되도록 보장한다. (중복 실행 방지)
public class ContentCachingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    // 받은 HttpServletRequest를 ContentCachingRequestWrapper로 래핑 해준다.
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        try {
        	// 필터 체인을 통해 래핑된 요청을 스프링 MVC에 전달
            filterChain.doFilter(requestWrapper, responseWrapper);
        } finally {
            responseWrapper.copyBodyToResponse();
        }
    }
}

결론


  • 로깅을 찍는데 성공 했지만 ContentCachingRequestWrapper를 쓰면 본래 HttpServletRequest가 가진 짧고 가벼운 생명주기의 장점이 줄어들고, 성능에 영향을 줄 수 있기 때문에 로깅같이 Body를 미리 읽어야하는 특수한 경우에만 사용하는게 맞는 것 같다.

출처

https://goddaehee.tistory.com

profile
백엔드 개발자

0개의 댓글