HttpServletRequest.getInputStream()多次读取问题


转自:https://www.jianshu.com/p/85feeb30c1ed

HttpServletRequest.getInputStream()多次读取问题

 

背景

使用POST方法发送数据时,我们习惯于把数据包装成json格式。

 

 
image.png

有些情况下,我们会在Filter中读取body数据进行数据校验,
GET方法获取参数比较简单。对于POST方法,可使用如下方法从request中获取body参数:

 private String getBody(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(); } 

注意,这里有了一次request.getInputStream()调用。

但是在测试时,一直报JSON格式不正确的错误。经调查发现,项目中使用了公司基础组件中的Filter,而该Filter中也解析了body。同时,不出所料,也是通过调用getInputStream()方法获取的。

原来:

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

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

解决方法

  1. 缓存数据
  2. 使用HttpServletRequestWrapper进行包装

缓存数据
所谓缓存数据,其实就是调用ServletRequestsetAttribute(String s, Object o)来存储数据。

  1. 获取到body后,直接缓存
String body = getBody(request); request.setAttribute("body", body); 

优点:
方便
缺点:
不能控制第三方Filter

  1. 其他地方需要使用body时,只需调用getAttribute方法就能获取数据了:
request.getAttribute("body"); 

HttpServletRequestWrapper包装

public class RequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RequestWrapper(HttpServletRequest request) throws IOException { super(request); body = getBodyStringFromReq(request).getBytes(Charset.forName("UTF-8")); } public String getBodyString() { try { return new String(body, "UTF-8"); } catch (UnsupportedEncodingException ex) { return new String(body); } } private String getBodyStringFromReq(ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } } 

在Filter中使用时,FilterChain.doFilter()传入Wrapper对象:

public class TestFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest)request); String body = requestWrapper.getBodyString(); chain.doFilter(requestWrapper, response); //传入Wrapper对象 } @Override public void init(FilterConfig arg0) throws ServletException { } } 

这样,位于后面的Filter就可以拥有唯一一次调用HttpServletRequest.getInputStream()的机会了。

优点:
不影响第三方Filter

缺点:
多写了这么多代码,麻烦了一些

参考


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM