首页 后端 Spring Boot 正文

记录一次Required request body is missing的问题排查

接口请求一切正常,但是会报错Required request body is missing的错误。

百般查询和debug后都未查到问题,最后还是在stackOverFlow中找解决方案:

https://stackoverflow.com/questions/35549040/failed-to-read-http-message-org-springframework-http-converter-httpmessagenotre

原因是:我新增了一个Filter用于打印请求和返回日志,部分代码如下:

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {   
        this.preHandle(request, (HttpServletResponse) response);
        chain.doFilter(request, response);
        this.afterCompletion((HttpServletRequest) request, (HttpServletResponse) response);
    }

    private void preHandle(HttpServletRequest request, HttpServletResponse response) {
            try {
                RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
                log.info("request params:{}", JSON.toJSON(requestWrapper.getBody()));
            } catch (Exception e) {
                log.info("log error ignore", e);
            }
    }

@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    private final String body;

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        InputStream inputStream = null;
        try {
            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) {

        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("inputStream close error", e);
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    log.error("bufferedReader close error", e);
                }
            }
        }
        body = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;

    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
        return this.body;
    }

}

开始总是找不到原因,最后是上述链接里的一番话,才让我恍然大悟,找到根源,见下图:

记录一次Required request body is missing的问题排查  第1张

inputstream只可以读取一次!看到这句话,基本问题就迎刃而解了。最简单的方式就是在filter里不要读取inputstream,当然如果你想读取的话,可以采用下面的方式,思路也是上图中说的:decorate the httpServletRequest。代码如下:

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
        this.preHandle(requestWrapper, (HttpServletResponse) response);
        //此处传封装后的HttpServletRequest
        chain.doFilter(requestWrapper, response);
        this.afterCompletion((HttpServletRequest) request, (HttpServletResponse) response);
    }

RequestWrapper不用改,封装HttpServletRequest之后,doFilter也传入封装的HttpServletRequest即可。

总结

一. 为什么inputStream只可以读取一次

InputStream read方法内部会记录position,用于记录当前流读取到的位置,若已读完,read方法会返回-1,(经常和inputStream打交道的,应该都会while(read() != -1) ,用这种方式读取inputStream)。若读取完再次读取的话可以调用inputStream.reset方法,此方法的前提是此方法markSupported返回true。HttpServletRequest使用的是ServletInputStream,看源码可知ServletInputStream没有实现reset和markSupported方法,那么ServletInputStream无法reset之后再次读取,所以inputStream只可以读取一次! (我要是对基础有深刻的了解,就不会查这么久了。)

    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    /**
     * Tests if this input stream supports the <code>mark</code> and
     * <code>reset</code> methods. Whether or not <code>mark</code> and
     * <code>reset</code> are supported is an invariant property of a
     * particular input stream instance. The <code>markSupported</code> method
     * of <code>InputStream</code> returns <code>false</code>.
     *
     * @return  <code>true</code> if this stream instance supports the mark
     *          and reset methods; <code>false</code> otherwise.
     * @see     java.io.InputStream#mark(int)
     * @see     java.io.InputStream#reset()
     */
    public boolean markSupported() {
        return false;
    }

二. StackOverFlow真靠谱,有问题先上StackOverFlow

打赏
海报

本文转载自互联网,旨在分享有价值的内容,文章如有侵权请联系删除,部分文章如未署名作者来源请联系我们及时备注,感谢您的支持。

转载请注明本文地址:https://www.shouxicto.com/article/2061.html

-->

相关推荐

支付宝
微信
赞助本站