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