zoukankan      html  css  js  c++  java
  • HttpRequest中InputStream只能一次性读取问题采坑

    场景:前端在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 IOException
    11     {
    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     @Override
    38     public BufferedReader getReader() throws IOException
    39     {
    40         return new BufferedReader(new InputStreamReader(getInputStream()));
    41     }
    42 
    43     @Override
    44     public ServletInputStream getInputStream() throws IOException
    45     {
    46         final ByteArrayInputStream bais = new ByteArrayInputStream(body);
    47 
    48         return new ServletInputStream(){
    49 
    50             @Override
    51             public boolean isFinished()
    52             {
    53                 return false;
    54             }
    55 
    56             @Override
    57             public boolean isReady()
    58             {
    59                 return false;
    60             }
    61 
    62             @Override
    63             public void setReadListener(ReadListener listener)
    64             {
    65 
    66             }
    67 
    68             @Override
    69             public int read() throws IOException
    70             {
    71                 return bais.read();
    72             }
    73 
    74         };
    75 
    76     }
    77 
    78     @Override
    79     public String getHeader(String name)
    80     {
    81         return super.getHeader(name);
    82     }
    83 
    84     @Override
    85     public Enumeration<String> getHeaderNames()
    86     {
    87         return super.getHeaderNames();
    88     }
    89 
    90     @Override
    91     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之前,盗用网上一张执行流程图,如下:

                                                             

     

  • 相关阅读:
    python并发编程之IO模型
    协程与concurent.furtrue实现线程池与进程池
    网络编程之线程进阶
    多线程讲解
    网络编程之进阶2
    网络编程之进阶
    网络编程之进程
    函数复习之2
    函数复习
    深克隆和浅克隆
  • 原文地址:https://www.cnblogs.com/jing-yi/p/14367455.html
Copyright © 2011-2022 走看看