阅读 148

HttpRequest中InputStream只能一次性读取问题采坑

标签:report   har   deb   过滤   bool   原理   wrap   数据读取   框架   

场景:前端在request body中传了多个参数,为了方便使用@RequestBody映射成相应的参数对象。

@PostMapping(value = "/game/sync")
public WebMessage gameMsgReport(UserInfo userInfo,@RequestBody GameMsg msgJson){
     gameService.gameMsgReport(userInfo.getUserId(), msgJson);
     return WebMessage.DEFAULT;
}   

问题:部署到测试后对象参数始终是空的,传的参数无法映射。

原因分析:@RequestBody 原理上就是spring在进行请求转发时,通过InputStream读取Request body中的数据,然后分装成指定的对象类型参数。所以怀疑问题出在数据读取上,经过资料查询发现HttpServletRequest的数据流只能被读取一次,读取后就不能再重置。所以排查项目拦截器中使用到Request的InputStream的地方,果然发现:在日志输出时使用InputStream读取了Request中的数据

accessLogger.debug("user access,requestURI = {},headers = {},params = {}",request.getRequestURI(),ServletUtils.getHeaderInfo(request),ServletUtils.getParameters(request));
 1 public static String getParameters(HttpServletRequest request){ 2         String params = request.getQueryString(); 3         if (StringUtils.isNotBlank(params)) { 4             return params; 5         } 6         try { 7             StringBuilder stringBuilder = new StringBuilder(); 8             BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8")); 9             String inputStr;10             while ((inputStr = streamReader.readLine()) != null) {11                 stringBuilder.append(inputStr);12             }13             return stringBuilder.toString();14         } catch (Exception e) {15             return null;16         }17     }

解决方法:将Request中的数据读取后缓存下来。

1、继承HttpServletRequestWrapper,将Request请求中的数据读取完缓存到封装类中。

 1 @Slf4j 2 public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper { 3  4     private final byte[] body; 5  6     private static final int BUFFER_START_POSITION = 0; 7  8     private static final int CHAR_BUFFER_LENGTH = 1024; 9 10     public RepeatedlyReadRequestWrapper(HttpServletRequest request) throws IOException11     {12         super(request);13         StringBuilder stringBuilder = new StringBuilder();14 15         InputStream inputStream = null;16         try {17             inputStream = request.getInputStream();18         } catch (IOException e) {19             log.error("Error reading the request body…", e);20         }21         if (inputStream != null) {22             try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {23                 char[] charBuffer = new char[CHAR_BUFFER_LENGTH];24                 int bytesRead;25                 while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {26                     stringBuilder.append(charBuffer, BUFFER_START_POSITION, bytesRead);27                 }28             } catch (IOException e) {29                 log.error("Fail to read input stream",e);30             }31         } else {32             stringBuilder.append("");33         }34         body = stringBuilder.toString().getBytes();35     }36 37     @Override38     public BufferedReader getReader() throws IOException39     {40         return new BufferedReader(new InputStreamReader(getInputStream()));41     }42 43     @Override44     public ServletInputStream getInputStream() throws IOException45     {46         final ByteArrayInputStream bais = new ByteArrayInputStream(body);47 48         return new ServletInputStream(){49 50             @Override51             public boolean isFinished()52             {53                 return false;54             }55 56             @Override57             public boolean isReady()58             {59                 return false;60             }61 62             @Override63             public void setReadListener(ReadListener listener)64             {65 66             }67 68             @Override69             public int read() throws IOException70             {71                 return bais.read();72             }73 74         };75 76     }77 78     @Override79     public String getHeader(String name)80     {81         return super.getHeader(name);82     }83 84     @Override85     public Enumeration<String> getHeaderNames()86     {87         return super.getHeaderNames();88     }89 90     @Override91     public Enumeration<String> getHeaders(String name)92     {93         return super.getHeaders(name);94     }95 }

2、将包装后的HttpServletRequest传递到后面的请求链路中。

 1 @Component 2 @WebFilter 3 public class RepeatlyReadFilter implements Filter { 4  5     @Override 6     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 7         if (request instanceof HttpServletRequest) { 8             request = new RepeatedlyReadRequestWrapper((HttpServletRequest) request); 9         }10         chain.doFilter(request, response);11     }12 }

还有一个知识点:因为第一次读取Request中的数据是在拦截器Interceptor中,而缓存Request中的数据是在过滤器Filter中做的,所以就涉及到两者的执行顺讯问题。Filter依赖于Servlet容器,基于函数回调;Interceptor依赖于web框架(如SpringMVC),基于java反射,两者并没有什么关系,而Filter的执行在Interceptor之前,盗用网上一张执行流程图,如下:

                                                         技术图片

 

 

 

 

HttpRequest中InputStream只能一次性读取问题采坑

标签:report   har   deb   过滤   bool   原理   wrap   数据读取   框架   

原文地址:https://www.cnblogs.com/jing-yi/p/14367455.html


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐