HTTP request body copy 방법

HTTP request 시 json 스트링을 서버에 넘겨주고, 서버는 interceptor에서 먼저 HTTP request body에 있는 json 스트링을 읽어와 값에 대한 체크를 진행하는 로직을 개발하고 있었다.


json 스트링 검증 후 spring controller에서 @RequestParam 으로 데이터를 받으려고 할 때 다음과 같은 예외가 발생

java.lang.RuntimeException: java.io.EOFException: No content to map to Object due to end of input


interceptor에서 request body의 데이터를 이미 읽어 들였기 때문에 나오는 증상이다.

시퀀스 다이어그램으로 표현하자면 다음의 4번에 해당하는 부분에서 예외가 발생된다.



인터셉터와 컨트롤러 모두에서 request 의 body 데이터를 읽어 들이고 싶을 때 어떤 방법을 이용해야 할까 고민을 해봤지만 명확한 답을 찾을 수가 없어 KSUG에 질문을 올려 그 해답을 찾을 수 있었다.


해결 방법


해결 방법은 request body 데이터를 저장해 놓는 객체를 생성 후 이를 doFilter로 넘기는 것이였다.

말로 설명하는 것 보다는 코드를 예로 들면 다음과 같다.


## HttpRequestWrapperFilter.java

public class HttpRequestWrapperFilter implements Filter {

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

HttpServletRequest httpServletRequest = (HttpServletRequest) request;

HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpServletRequest);

chain.doFilter(requestWrapper, response);

}


## HttpRequestWrapper.java

public class HttpRequestWrapper extends HttpServletRequestWrapper {


private byte[] bodyData;

public HttpRequestWrapper(HttpServletRequest request) throws IOException {

super(request);

InputStream is = super.getInputStream();

bodyData = IOUtils.toByteArray(is);

}

@Override

public ServletInputStream getInputStream() throws IOException {

final ByteArrayInputStream bis = new ByteArrayInputStream(bodyData);

return new ServletImpl(bis);

}

}


class ServletImpl extends ServletInputStream {


private InputStream is;


public ServletImpl(InputStream bis) {

is = bis;

}


@Override

public int read() throws IOException {

return is.read();

}


@Override

public int read(byte[] b) throws IOException {

return is.read(b);

}


}