java拦截器获取POST请求体后Controller异常Required request body is missing OR Stream closed

发布时间 2023-07-14 18:38:12作者: White_白

解决办法参考文档:https://blog.csdn.net/qierkang/article/details/88544691

springboot拦截器获取POST请求体后导致Controller中@RequestBody参数异常Required request body is missing OR Stream closed.

1.为什么会报这个错?

因为http的body只能读取一次。

2.为什么body设计为只能读取一次?

A.由于我们获取POST请求参数的时候,是通过读取request的IO流来实现的,一旦读取了那么流关闭后,后续就用不了了。

B.假如别人上传1T的文件,然后Java先全部读取到内存,服务器直接就挂了……所以不确定最大能多大的东西,默认都是流式处理,要缓存反复读。

解决办法:

1.写一个可重复读的Request包装类

import org.springframework.http.HttpMethod;
import org.springframework.util.StreamUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * 可重复读的Request包装类
 */
@Slf4j
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {

    /**
     * 缓存下来的HTTP body
     */
    private byte[] body;

    public RepeatedlyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = StreamUtils.copyToByteArray(request.getInputStream());
    }

    /**
     * 重新包装输入流
     *
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        InputStream bodyStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {

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

            /**
             * 下面的方法一般情况下不会被使用,如果你引入了一些需要使用ServletInputStream的外部组件,可以重点关注一下。
             * @return
             */
            @Override
            public boolean isFinished() {
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        InputStream bodyStream = new ByteArrayInputStream(body);
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
    
}

2.添加处理可重复读request的过滤器(过滤器记得开启生效)

import com.kuailaimi.security.client.util.RepeatedlyRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 处理可重复读request的过滤器
 */
public class RepeatedlyReadFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //将request可重复读的包装类传递下去
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            requestWrapper = new RepeatedlyRequestWrapper(httpServletRequest);
        }

        if (null == requestWrapper) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }


    @Override
    public void destroy() {
    }
}

3.在拦截器中使用

 if (request instanceof RepeatedlyRequestWrapper) {
     RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
     String body = new String(StreamUtils.copyToByteArray(repeatedlyRequest.getInputStream()), repeatedlyRequest.getCharacterEncoding());
}