간단한 flow 설명
request (요청) -> 디스패처 서블릿
-> 컨트롤러의 객체로 값을 바인딩 하는 과정에서 바디 데이터 소비
-> 컨트롤러의 request body 비워져 있음
request.getReader()에서 InputStream을 생성하는데, 이걸 tomcat에서 한번만 사용할 수 있도록 막아두어서, 한번 read한 body값은 다시 읽을 수 없게 되어 있었다.
1. HttpServletRequestWrapper
상속받아 클래스 생성 재정의
import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class RereadableRequestWrapper extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public RereadableRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream(cachedBytes.toByteArray());
}
@Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/* An input stream which reads the cached request body */
private static class CachedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream buffer;
public CachedServletInputStream(byte[] contents) {
this.buffer = new ByteArrayInputStream(contents);
}
@Override
public int read() {
return buffer.read();
}
@Override
public boolean isFinished() {
return buffer.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
}
}
2. OncePerRequestFilter
를 상속받은 클래스의 doFilterInternal
메서드에서 래핑
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
RereadableRequestWrapper rereadableRequestWrapper = new RereadableRequestWrapper((HttpServletRequest)request);
filterChain.doFilter(rereadableRequestWrapper , response);
}
3. AOP 에서 확인해봄
- 래핑해주지 않으면 body 가 비어있지만, 래핑해주면 잘 뜸
@Around("com.dot3company.olobo.api.aop.LoggingAspect.onRequest()")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = // 5
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("Http Body = " + messageBody);
System.out.println("Http Body Length = " + request.getContentLength());
}
참고
Controller에서 HttpRequest Body 값은 왜 비워져 있을까?