본문 바로가기
Spring

request body 한번만 읽어올수 있는 제약

by ybs 2021. 6. 9.
반응형

이전 글에서 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 를 활용하는 방법도 있다.

https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java

반응형

'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