반응형
이전 글에서 Spring Cloud Gateway 에서 request body 정보를 요청당 한번밖에 못읽는다고 설명했는데 꼭 Spring Cloud Gateway 만의 문제는 아니다.
아래와 같이 @RequestBody 애노테이션을 통해 request body 정보를 HelloDto 객체에 맵핑 시켜주게 되면 추가적으로 request.getInputStream 요청을 하게 됐을 때 실패하게 된다. 즉 request body 를 한번밖에 못읽는 제약이 있다.
@RequestMapping(value = "/hello")
public Mono<ResponseEntity<String>> hello(
HttpServletRequest request,
@RequestBody HelloDto helloDto) throws Exception {
byte[] bytes = new byte[request.getContentLength()];
ServletInputStream inputStream = request.getInputStream();
inputStream.read(bytes);
}
이를 해결해주기 위해서 CacheBodyFilter 를 등록해서 요청 바디를 캐시해서 꺼내쓰도록 하게 했다.
@Component
public class CacheBodyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
new CachedBodyHttpServletRequest(httpServletRequest);
filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse);
}
}
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
@Override
public ServletInputStream getInputStream() {
return new CachedBodyServletInputStream(this.cachedBody);
}
@Override
public BufferedReader getReader() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
private static class CachedBodyServletInputStream extends ServletInputStream {
private final InputStream cachedBodyInputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
@Override
public boolean isFinished() {
try {
return cachedBodyInputStream.available() == 0;
} catch (IOException e) {
throw new RuntimeException("[CachedBodyServletInputStream] cachedBodyInputStream available fail", e);
}
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
}
}
참고로 이미 스프링에서 구현해둔 ContentCachingRequestWrapper 를 활용하는 방법도 있다.
반응형
'Spring' 카테고리의 다른 글
Webclient Timeout 과 connection pool 전략 (0) | 2021.06.11 |
---|---|
WebClient 사용할때 주의 (2편) (0) | 2021.06.10 |
Spring Web MVC 구조 논의 1편 (0) | 2021.06.06 |
Spring Cloud Gateway CORS 주의사항 (4) | 2021.01.24 |
useInsecureTrustManager 옵션 (0) | 2021.01.20 |