📌 HttpServletRequest
🔵 HttpServletRequest란?
Extends the javax.servlet.ServletRequest interface to provide request information for HTTP servlets. The servlet container creates an HttpServletRequest object and passes it as an argument to the servlet's service methods
쉽게 말해서 HTTP 요청과 관련된 데이터를 저장하고, 이를 이용할 수 있는 메서드를 제공한다.
🔵 왜 값이 사라질까?
서블릿의 HttpServletRequest는 body 데이터를 stream으로 제공한다.
내용을 가져오기 위해 getInputStream 함수를 사용하는데, 이 함수를 사용하게 된다면, 데이터를 한 번만 읽을 수 있다.
따라서 만일 먼저 수행된 로직에서 데이터를 먼저 읽어버리면, 다음 로직에서는 해당 값을 이용하지 못한다.
그래서 값에 접근할 경우
java.lang.IllegalStateException: getReader() has already been called for this request
에러가 발생한다.
📌 해결 방법
클라이언트에서 요청이 들어오면, 해당 요청은 Controller에 들어오기 전
Filter, Dispatcher Servlet, Interceptor를 거쳐 Controller에 들어오게 된다.
클라이언트의 요청이 가장 먼저 만나게 되는 filter에서 Input Stream 값을 캐싱하게 된다면,
HttpServletRequest 값을 여러번 사용하는 게 가능하지 않을까?
이를 구현해 보도록 하자
🔵 MultiReadHttpServletRequest
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public MultiReadHttpServletRequest(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 {
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
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");
}
}
}
이 클래스는 HttpServletRequest를 Wrapping하는 클래스이다.
HTTP 요청 본문을 캐싱하고, 캐싱한 데이터를 읽을 수 있도록 해준다.
Input Stream의 값을 읽을 수 있는 getInputStream 함수를 stream 값을 저장하고 반환하도록 재정의하고,
getReader 함수도 저장해 둔 buffer 값을 반환하도록 재정의해준다.이런 방식으로 입력 stream을 buffer에 저장할 수 있다.
🔵 MultiReadHttpServletRequestFilter
@Component
public class MultiReadHttpServletRequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);
chain.doFilter(multiReadRequest, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
MultiReadHttpServletRequestFilter라는 새로운 Filter를 생성한 후, 필터에 추가해준다.
필터에서 input stream을 캐싱해주기 때문에,
controller와 이후 로직에서 httpServletRequest의 값에 여러 번 접근할 수 있다.
'Backend' 카테고리의 다른 글
<Spring> Redis 기반 검색어 자동 완성 기능 구현하기 (1) | 2023.10.01 |
---|---|
<Spring> AOP 기반 분산락 (0) | 2023.10.01 |
<Spring> ResponseEntityExceptionHandler 이용한 공통 에러 처리 (0) | 2023.05.07 |
<Spring> Success Custom Response 적용하기 (0) | 2023.05.02 |
<Spring> 공통 에러 핸들러 구현하기 (1) | 2023.03.28 |