본문 바로가기

spring

Post로 전달된 json Body 여러번 읽기

반응형

상황

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

 

반응형