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 request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
String requestBody = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
(ContentCachingRequestWrapper)
를 사용하려면 사전 작업으로 필터를 구현해야 한다.
@Component
@WebFilter(urlPatterns = "/*", description = "Wrapping Request")
public class ContentCachingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
try {
filterChain.doFilter(requestWrapper, responseWrapper);
} finally {
responseWrapper.copyBodyToResponse();
}
}
}
결론
- 로깅을 찍는데 성공 했지만
ContentCachingRequestWrapper
를 쓰면 본래 HttpServletRequest
가 가진 짧고 가벼운 생명주기의 장점이 줄어들고, 성능에 영향을 줄 수 있기 때문에 로깅같이 Body를 미리 읽어야하는 특수한 경우에만 사용하는게 맞는 것 같다.
출처
https://goddaehee.tistory.com