Search code examples
javaspringrestspring-bootinterceptor

Cannot properly read reqest body in interceptor - Spring BOOT 2.0.4


I have a problem with reading request body in interceptor. Both getReader() and getInputStream() causing problems. My interceptor:

public class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
        throws Exception {
    // TODO Auto-generated method stub

}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
        throws Exception {
    // TODO Auto-generated method stub
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    String requestBody = httpRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator()));

//or
// String requestBody = new BufferedReader(new InputStreamReader(httpRequest.getInputStream()))
//                .lines().collect(Collectors.joining("\n"));
//some logic...
    return true;
}

Both approaches failing because probably spring uses such resource somewhere internally. First causess java.lang.IllegalStateException: getReader() has already been called for this request and other Required request body is missing: org.springframework.http.ResponseEntity...

I have tried some workarounds with wrappers without an effect. I think its because I cannot pass wrapper down like in filters(i dont want to use filter cause I have common exception managager(@ControllerAdvice).

Is this a known issue? Is there any workaround for this?


Solution

  • Finally I have figured it out so I will leave here some simple but useful advice for others. I have used a request wrapper but to make it work properly I have added a filter with highest order to wrap every request into wrapper at the beginning, before interceptor is executed. Now it works well ;) Here's most important code - filter to wrap every request into multi read wrapper(interceptor looks almost the same as above and wrapper is not invented by me, found on stack and I found it as most clear and readable):

    import lombok.SneakyThrows;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.BufferedReader;
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.nio.charset.StandardCharsets;
    import java.util.stream.Collectors;
    
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class GlobalWrapFilter implements Filter {
    
    
        @Override
        @SneakyThrows
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
            MultiReadRequest wrapper = new MultiReadRequest((HttpServletRequest) request);
            chain.doFilter(wrapper, response);
        }
    
        @Override
        public void destroy() {
        }
    
        class MultiReadRequest extends HttpServletRequestWrapper {
    
            private String requestBody;
    
            @SneakyThrows
            public MultiReadRequest(HttpServletRequest request) {
                super(request);
                requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
            }
    
            @Override
            public ServletInputStream getInputStream() throws IOException {
                final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes());
                return new ServletInputStream() {
                    @Override
                    public boolean isFinished() {
                        return byteArrayInputStream.available() == 0;
                    }
    
                    @Override
                    public boolean isReady() {
                        return true;
                    }
    
                    @Override
                    public void setReadListener(ReadListener readListener) {
    
                    }
    
                    @Override
                    public int read() throws IOException {
                        return byteArrayInputStream.read();
                    }
                };
            }
    
            @Override
            @SneakyThrows
            public BufferedReader getReader() {
                return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
            }
        }
    }