HttpServletRequest.getInputStream() 多次获取post的数据

HttpServletRequest.getInputStream() 多次获取post的数据

在实际的开发过程中,我们会在Filter或者AOP中读取body数据进行数据校验,
GET方法获取参数比较简单。可以直接对HttpServletRequest类使用getQueryString和getParameterMap获取到,但是对于POST方法,可使用如下方法从request中获取body参数:

 private String getPostData(HttpServletRequest request) throws IOException {
    InputStream in = request.getInputStream();
    BufferedReader br = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")));
    StringBuffer sb = new StringBuffer("");
    String temp;
    while ((temp = br.readLine()) != null) {
        sb.append(temp);
    }
    if (in != null) {
        in.close();
    }
    if (br != null) {
        br.close();
    }
    return sb.toString();
}

这样子虽然能够获取到post的数据,但是系统会报一个异常:

java.lang.IllegalStateException: getInputStream() has already been called ...

原来:

  • 一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1;
  • InputStream并没有实现reset方法(可以重置首次读取的位置),无法实现重置操作;

因此,当自己写的Filter中调用了一次getInputStream()后,后面再调用getInputStream()读取的数据都为空,所以才报IllegalStateException错误。

解决办法

1、新建一个 MyRequestWrapper类 对父类的 HttpServletRequestWrappergetInputStream()方法进行重写,代码如下:

import com.tusdao.log.util.RequestParamAware;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
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;


public class MyRequestWrapper extends HttpServletRequestWrapper {
    private final String body;
    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        StringBuilder sb = new StringBuilder();
        String line;
        BufferedReader reader = request.getReader();
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        body = sb.toString();
        request.setAttribute("body",body); //将post的body数据放入缓存,这样子在后面就能够随时取用
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bais.read();
            }

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

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

            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }

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

2、在拦截器中传入Wrapper对象:

@Order(1)
@Component
@Slf4j
@WebFilter(filterName="logFilter", urlPatterns="/*")
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("filter before");
        // 转换成自己覆写的类
        MyRequestWrapper req = new MyRequestWrapper((HttpServletRequest)request);
        log.info("获取到post信息为:{}", RequestParamAware.extractPostBody(request));
        // 如果没有覆写HttpServletRequestWrapper
        // doFilter(request, response)之后 再用到body
        // 会抛出类似错误 Cannot call getInputStream(), getReader() already called
      	// 注意doFilter这里传进去的参数req 而不是request !!!!
        chain.doFilter(req, response);
        log.info("filter after");
    }

    @Override
    public void destroy() {

    }
}

这样,位于后面的controller就可以拥有唯一一次调用HttpServletRequest.getInputStream()的机会了。并且在后序的业务中可以通过getAttribute获取到post的数值。

原文地址:https://www.cnblogs.com/xwxz/p/14061714.html