문제 사항
Graphql로 들어오는 요청을 구분하여 받기 위해 Filter를 추가했다. Filter에 요청된 Query를 확인하기 위해 InputStream을 사용했다. 데이터가 정상적으로 출력되는 것은 확인할 수 있었다. 하지만 데이터 출력 이후부터 백엔드가 정상적으로 동작하지 않는 문제가 생겼다.
[ 문제 코드 ]
public class GraphqlAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request
,HttpServletResponse response
,FilterChain filterChain) throws ServletException, IOException
{
var inputStream = httpServletRequest.getInputStream();
var data = HttpUtil.getDataOf(inputStream);
filterChain(request, response)
}
}
원인
InputStream을 사용한 이후부터 문제가 발생했기 때문에 해당 부분 위주로 확인을 했다. HttpServletRequest의 getInputStream 메서드로 가져온 InputStream을 끝까지 읽는 경우에 문제가 된다는 것을 알 수 있었다. 검색해서 찾아보니 InputStream은 한 번만 읽을 수 있다고 했다. 이미 읽은 InputStream을 다시 읽을 경우 문제가 되는 것이다.
시도
InputStream을 여러 번 사용할 수 있도록 복사를 해주기로 했다. InputStream의 별도 Clone 메서드가 존재하지 않아 직접 메서드를 만들어 복사를 해보았다. 그런데 이것 또한 이미 한 번 읽어서 복사가 이루어져서 동일한 문제가 발생했다. 검색해보니 HttpServletRequest와 InputStream을 별도로 만들어서 재사용하는 방식이 있었다.
해결
CustomHttpServletRequest를 만들어 기존 HttpServletRequest에 있는 요청들을 담고 InputStream 데이터는 byte 배열로 저장을 했다. ServletInputStream을 상속받아 필요한 메서드를 오버라이딩 해서 CustomServletInputStream을 만들었다. getInputStream 메서드를 호출할 때마다 저장해놓은 byte배열을 사용해 CustomServletInputStream 객체를 만들어 반환함으로 재사용이 가능해졌다.
[ 문제 해결 코드 ]
public class GraphqlAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request
,HttpServletResponse response
,FilterChain filterChain) throws ServletException, IOException
{
var cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(request);
var data = HttpUtil.getDataOf(cachedBodyHttpServletRequest.getInputStream());
filterChain(cachedBodyHttpServletRequest, response);
}
}
public class CustomHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
public CustomHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new CustomServletInputStream(this.cachedBody);
}
}
public class CustomServletInputStream extends ServletInputStream {
private InputStream customInputStream;
public CustomServletInputStream(byte[] body) {
this.customInputStream = new ByteArrayInputStream(body);
}
@Override
public boolean isFinished() {
try {
return customInputStream.available() == 0;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isReady() {
return true;
}
@Override
public int read() throws IOException {
return customInputStream.read();
}
@Override
public void setReadListener(ReadListener listener) {
// NO LOGIC
}
}
느낀점
InputStream을 한 번만 사용할 수 있다는 걸 알았다. 하지만 정작 내가 구현하지 않은 라이브러리 내부에서 InputStream을 가져다 쓰다 보니 어디서 정확히 문제가 생기는지는 알지 못했다. 이러한 부분 때문에 개운하지 못한 것 같다. 시간이 생겼을 때 더 확인해 봐야겠다.
참고
Reading HttpServletRequest Multiple Times in Spring
Learn how to read Spring's HttpServletRequest multiple times
www.baeldung.com
'개발일지 > TIL' 카테고리의 다른 글
[ 230622 ] 제대로 이해하고 하자 (0) | 2023.06.22 |
---|---|
[ 230621 ] 공공데이터 포털 200만건 데이터 DB에 넣기 (0) | 2023.06.21 |
[ 230619 ] Chained Exception ? (0) | 2023.06.19 |
[ 230618 ] 사이드 프로젝트 Graphiql 적용하기 (0) | 2023.06.18 |
[ 230617 ] Java Spring WireMock 이슈 및 적용 (0) | 2023.06.17 |