乐趣区

关于java:解决-HttpServletRequest-的输入流不能重复读的问题

背景

写代码的时候遇到 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 流只读取一次的问题
 */
@Slf4j
public 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);
  }
}

配置过滤器

@Configuration
class 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();
退出移动版