Spring에서 Request 2번 읽기 - HttpServletRequestWrapper
필자가 Restful을 스프링으로 만들고 있을 때 JWT(토큰 인증 방식) 을 이용한 인증방식을 이용하려고 했다.
그 인증을 해주는 Interceptor을 하나 만들어서 토큰을 인증했다. 하지만 interceptor에서 request를 읽어버리면
Controller에서 request를 읽지 못하는 단점이 있다. Controller에서 request를 읽어주게 하는 방법이 바로 HTTPServletRequestWrapper
이다.
최근에 평범한 흐름에서 HttpServletRequest body를 두번 읽고 필터 체인에게 넘겨주는 요구가 많아졌다. ( 필자가 위에서 설명한 방법도 body를 두번 읽어야 한다. 한번은 인증을 위해 읽어야하고 한번은 인증이 되면 그 값들을 이용하려는 컨트롤러에게 줘야 하므로)
HttpServletRequestWrapper 예제
이 커스텀 소스는 HttpServletRequestWrapper
를 구현한 것이다. 자세한 내용은 서블릿 2.5의 내용을 읽어보면 된다.
RequestWrapper.java
x
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class RequestWrapper extends HttpServletRequestWrapper {
private final String body;
public RequestWrapper(HttpServletRequest request) throws IOException
{
//So that other request method behave just like before
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
//Store request pody content in 'body' variable
body = stringBuilder.toString();
}
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
//Use this method to read the request body N times
public String getBody() {
return this.body;
}
}
HttpServletRequestWrapper 사용하기
이 wrapper를 사용하려면 서블릿 필터를 먼저 추가해줘야 한다. 이 서블릿은 request body
를 두번 읽는걸 도와준다.
wrapper를 사용하기 위해서 web.xml
에 서블릿 필터 매핑을 추가해주자.
(위에 소스하고는 무관)
web.xml
xxxxxxxxxx
<filter>
<filter-name>cacheFilter</filter-name>
<!-- 패키지 명 . 클래스이름 까지 써줄 것 -->
<filter-class>com.howtodoinjava.filter.RESTCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cacheFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
/*
이 있기 때문에 위에 있는 필터는 모든 요청들을 필터링 해준다.
서블릿 필터에서 request body를 두번 읽는 법
CacheFilter.java
xxxxxxxxxx
public class CacheFilter implements Filter {
private FilterConfig filterConfig = null;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request = new RequestWrapper((HttpServletRequest) request);
//Read request.getBody() as many time you need
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfiguration) throws ServletException {
this.filterConfig = filterConfiguration;
}
public void destroy() {
this.filterConfig = null;
}
}
이 서블릿 필터는 http request body를 원하는 만큼을 읽을 수 있고 filter chain으로 전달도 가능 하다.
마치며
여러분도 알듯이, 기본적으로 http request body
는 오직 한번만 읽을 수 있다. 만약 filter에서 body를 읽었다면, servlet은 다시 읽을 수 없으며 IllegalStateException
오류를 낼 것이다.
위에 있는 HttpServletRequestWrapper
을 사용하면 서블릿에서도 읽을 수 있으며 그 다음에도 읽을 수 있게 된다. 마지막으로 중요한 것은 request body의 컨텐트는 wrapper 오브젝트 안에 캐시화 되어 있어서, request 라이플 사이클 만큼 있을 수 있다.
댓글
댓글 쓰기