상황
HTTP Post 메서드로 전달된 특정 데이터를 spring interceptor에서 확인 후 controller로 넘기거나 또는 exception 처리를 해야 했다. 하지만 Post 내에 Body에 접근하고 controller로 넘기니 이미 처리된 데이터라는 식의 error 메시지 발견!!
java.lang.IllegalStateException: getReader() has already been called for this request
원인
HttpServletRequest의 inputStream은 한번 읽으면 다시 읽을 수가 없다. 즉, 앞선 상황에서 interceptor에서 stream을 이용해 데이터를 처리했기 때문에 controller에서는 문제가 됐다.
해결
HttpServletRequestWrapper을 이용해 데이터를 request에서 읽고 복사를 해둔 뒤 inputStream 요청이 있을 때마다 복사된 데이터를 넘긴다.
public class RereadableRequestWrapper extends HttpServletRequestWrapper {
private final Charset encoding;
private byte[] rawData;
public RereadableRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String characterEncoding = request.getCharacterEncoding();
if (StringUtils.isBlank(characterEncoding)) {
characterEncoding = StandardCharsets.UTF_8.name();
}
this.encoding = Charset.forName(characterEncoding);
try {
InputStream inputStream = request.getInputStream();
this.rawData = IOUtils.toByteArray(inputStream);
} catch (IOException e) {
throw e;
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.rawData);
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream(), this.encoding));
}
@Override
public ServletRequest getRequest() {
return super.getRequest();
}
}
이후 filter에서 request를 대체하면 정상 작동한다.
public class RequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
RereadableRequestWrapper rereadableRequestWrapper = new RereadableRequestWrapper((HttpServletRequest)request);
chain.doFilter(rereadableRequestWrapper, response);
}
}
Filter을 사용하지 않고 interceptor만으로 json body을 여러번 read 할 수 있을까? interceptor만으로는 한계가 있는 것 같다.
servlet내부 코드를 보면 applyPrehandle로 interceptor를 호출하고 applyPrehandle은 내부적으로 Loop 구문으로 동작한다.
즉, handler.applyPreHandle(processedRequest, response)로 넘겨진 processedRequest를 interceptor 내부에서 변경을 한다. 하지만 call by value 형태로 전달된 prehandle을 처리하기는 어렵다.
REF
https://meetup.toast.com/posts/44
Spring Interceptor(혹은 Servlet Filter)에서 POST 방식으로 전달된 JSON 데이터 처리하기 : NHN Cloud Meetup
Spring Interceptor(혹은 Servlet Filter)에서 POST 방식으로 전달된 JSON 데이터 처리하기
meetup.toast.com
'spring' 카테고리의 다른 글
[Gradle]Multi Application.properties 사용하기 (1) | 2024.03.23 |
---|---|
스프링부트 2.2.10에서 2.6.6으로 upgrade (0) | 2022.04.05 |
Static Dispatch, Dynamic Method Dispatch (0) | 2022.03.31 |
Spring-Kafka에서 읽은 Record 개수 알기 (0) | 2021.07.13 |