..

Troubleshooting: getReader() has already been called 에러 해결

1. 발생 에러 메세지

서블릿 필터(Filter)나 인터셉터에서 요청 본문(Payload)을 로깅하려고 할 때 다음과 같은 예외가 발생할 수 있습니다.

java.lang.IllegalStateException: getReader() has already been called for this request
# 또는
java.lang.IllegalStateException: getInputStream() has already been called for this request

2. 원인 분석

HTTP 요청의 InputStream단 한 번만 읽을 수 있는 스트림(Stream) 구조입니다. 스프링의 @RequestBody가 동작하여 객체로 변환하려면 스트림을 읽어야 하는데, 그 전 단계인 필터나 인터셉터에서 이미 로깅을 위해 스트림을 다 읽어버렸기(Consume) 때문에 정작 컨트롤러에 도달했을 때는 읽을 수 있는 데이터가 없어 발생하는 오류입니다.

3. 해결 방법: HttpServletRequestWrapper 활용

요청 데이터를 별도의 메모리(cachedPayload)에 복사해두고, 여러 번 읽을 수 있도록 HttpServletRequestWrapper를 상속받은 커스텀 클래스를 구현하여 해결합니다.

해결 전략

  1. CustomServletRequestWrapper 구현: 생성자에서 InputStream의 내용을 읽어 바이트 배열에 캐싱합니다.
  2. getInputStream() 재정의: 호출될 때마다 캐싱된 바이트 배열을 기반으로 새로운 ServletInputStream을 생성하여 반환합니다.
  3. Filter에서 적용: FilterChain을 통해 요청을 넘길 때 원본 대신 커스텀 래퍼 객체를 전달합니다.

이 방식을 적용하면 필터에서 요청 로그를 마음껏 남기면서도, 컨트롤러의 @RequestBody가 정상적으로 데이터를 바인딩할 수 있게 됩니다.