背景

写代码的时候遇到 HttpServletRequest 里的数据为null, 感觉很奇怪,同样的申请,方才还不为null。

排查

通过debug发现,HttpServletRequest 在 106行 之前有值, 但在106行之后为null。

而这个办法只是简略地读了一遍 httpServletRequest 外面的数据

ServletInputStream inputStream = httpServletRequest.getInputStream()StringBuilder sb = new StringBuilder();try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()))) {  String line;  while ((line = reader.readLine()) != null) {    sb.append(line);  }} catch (IOException e) {  throw new RuntimeException(e);}return sb.toString();

于是猜测 HttpServletRequest里的 inputStream 只能读一次?

去查了一下果真如此。上面理解一下起因。

HttpServletRequest的输出流只能读取一次的起因

首先咱们应用 httpServletRequest.getInputStream()

返回的是 ServletInputStream 类, 它继承于 InputStream

而咱们对这个流读数据其实 调用了 InputStream的 read()办法。

读取流的时候会依据position来获取以后地位,每读取一次,该标记就会挪动一次,如果读到最初,read()返回-1,示意曾经读取完了。

while(inputStream.read(b)) != -1) {  }

如果想要从新读取,能够调用 inputstream.reset() 办法,

然而是否 reset 取决于markSupported办法,返回true能够reset,反之则不行。

查看 ServletInputStream 可知,这个类并没有重写markSupported和reset办法。

所以这就是 HttpServletRequest输出流只能读取一次起因

解决

解决的外围思路就是对HttpServletRequest输出流进行备份。

将申请体中的流copy一份,重写getInputStream()和getReader()办法

每次调用覆写后的getInputStream()办法都是从复制进去的二进制数组中进行获取。

RequestWrapper.class

/** * 解决request流只读取一次的问题 */@Slf4jpublic class RequestWrapper extends HttpServletRequestWrapper {  /**   * 存储body数据的容器   */  private final byte[] body;  public RequestWrapper(HttpServletRequest request) throws IOException {    super(request);    // 将body数据存储起来    body = getBodyString(request).getBytes(Charset.defaultCharset());  }  /**   * 获取申请Body   *   * @param request request   * @return String   */  private String getBodyString(final ServletRequest request) {    try {      return inputStream2String(request.getInputStream());    } catch (IOException e) {      throw new RuntimeException(e);    }  }  /**   * 获取申请Body   *   * @return String   */  public String getBodyString() {    final InputStream inputStream = new ByteArrayInputStream(body);    return inputStream2String(inputStream);  }  /**   * 将inputStream里的数据读取进去并转换成字符串   *   * @param inputStream inputStream   * @return String   */  private String inputStream2String(InputStream inputStream) {    StringBuilder sb = new StringBuilder();    try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()))) {      String line;      while ((line = reader.readLine()) != null) {        sb.append(line);      }    } catch (IOException e) {      throw new RuntimeException(e);    }    return sb.toString();  }  @Override  public BufferedReader getReader() throws IOException {    return new BufferedReader(new InputStreamReader(getInputStream()));  }  @Override  public ServletInputStream getInputStream() throws IOException {    final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);    return new ServletInputStream() {      @Override      public int read() throws IOException {        return inputStream.read();      }      @Override      public boolean isFinished() {        return false;      }      @Override      public boolean isReady() {        return false;      }      @Override      public void setReadListener(ReadListener readListener) {      }    };  }}

Filter

除了要写一个包装器外,咱们还须要在过滤器里将原生的 HttpServletRequest 对象替换成咱们的RequestWrapper对象。

创立过滤器

/** * 解决request流只读取一次的问题 */public class ReplaceStreamFilter implements Filter {  @Override  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {    ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request);    chain.doFilter(requestWrapper, response);  }}

配置过滤器

@Configurationclass FilterConfig {  /**   * 注册过滤器   *   * @return FilterRegistrationBean   */  @Bean  public FilterRegistrationBean someFilterRegistration() {    FilterRegistrationBean registration = new FilterRegistrationBean();    registration.setFilter(replaceStreamFilter());    registration.addUrlPatterns("/*");    registration.setName("streamFilter");    return registration;  }  /**   * 实例化StreamFilter   *   * @return Filter   */  @Bean(name = "replaceStreamFilter")  public Filter replaceStreamFilter() {    return new ReplaceStreamFilter();  }}

应用

应用的时候用咱们创立的 RequestWrapper 读数据就好了

String requestBody =  new RequestWrapper(httpServletRequest).getBodyString();