HttpServletReqeust는 getBody를 지원하지 않고, InputStream을 지원한다. 이유는 TCP 통신 특성상 데이터의 순차 도착을 보장하지 않고, 요청과 함께 온 파라미터가 유형이 다양하며, 계속 메모리에 들고있는건 비효율적이기 때문
그런데 InputStream은 한번 읽으면 끝! 로그를 찍는 Filter나 Interceptor에서 InputStream을 이용하여 body를 읽으면, 컨트롤러에서
@RequestBody로 매핑된 Class로 매핑하기위해 request body를 읽을때 오류가 생긴다. 이미 InputStream을 읽었기때문에 내용물이 없는것처럼 보이기때문이다.
따라서 이 Request Body를 담는 HttpServletRequestWrapper를 구현해야한다. 그런데! 친절한 스프링은 이미 구현을 해주셨다. 그게 바로 ContentCachingRequestWrapper.
아래처럼 필터를 만들고, 적당한곳에 필터를 등록해주면된다.
나는 Spring Security를 사용하고있어서 적당히 JwtTokenFilter후에 등록했다.
public class RestRequestWrapperFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (request.getHeader("accept") != null && request.getHeader("accept").contains("text/event-stream")) {
filterChain.doFilter(request, response);
return;
}
ContentCachingRequestWrapper wrappingRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappingResponse = new ContentCachingResponseWrapper(response);
filterChain.doFilter(wrappingRequest, wrappingResponse);
wrappingResponse.copyBodyToResponse();
}
}
//Security Config 코드
httpSecurity
.addFilterAfter(new RestRequestWrapperFilter(), JwtTokenFilter.class);
그리고 Interceptor에서 preHandle에서 request의 body를 찍어봤는데 여전히 Empty!!! 왜 그런가 찾아보았더니..
요약 : content를 읽어야 cache에 저장됨.
InputStream을 이용하여 content를 읽어야, Wrapper에서 해당 내용을 저장하여 Cache로 저장한다는 뜻이였고, 아직 읽지 않은 상태라서 Emtpy..
그래서 preHandle에서 getInputStream으로 읽으면? 로그는 찍히나... 컨트롤러에서 @RequestBody로 지정한 DTO로 매핑할때 Stream을 읽어야하니 에러...
그래서 afterCompletion에서 reqeust/response 로그를 한번에 찍는것으로 변경. afterCompletion은 Exception이 발생해도 호출되는 인터셉터 메소드이기때문에 무조건 호출된다!
이게 싫으면 HttpServletRequestWrapper를 직접 구현해야하는데...
그건 또 싫기 때문에 로그 찍기는 여기서 마무리...
난 아직 딥다이브할 마음의 준비가 안되었어..