背景
写代码的时候遇到 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();