zoukankan      html  css  js  c++  java
  • 如何在流中重复获取body数据内容

      场景描述:

      要对请求数据,进行通用的XSS规则验证,所以需要在请求进入到具体的Controller的接口前,进行拦截处理.

      在Context-Type=application/json的请求中,要对请求参数进行验证,须从body中的流中去获取数据,但是从流中读取数据只能读取一次.

     

      读取数据:

      在InputStream的read方法内部,存在position,来标识当前流被读取到的位置,读取到哪里就标志到哪里.如果读到最后,那么就返回-1.

      如果想要重新读取流中的数据,那么就需要调用reset方法,那么position就会重置到上次调用mark的位置,mark默认位置是0.就可以从头在读.

      调用reset的方法的前提是:

      1.markSupported 方法返回值必须为true.

      2.必须重写reset方法

     

      只读一次:

      现在需要确定,为什么从body的流中读取数据只能读取一次呢?

      既然已知读取流中数据的原理,以及如何重复读取流中数据的前提,那么我们来查看请求中获取流的方法

      

     

      具体从request中获取流的方法:

      ServletInputStream steam = request.getInputStream();

      首先,我们来分析ServletInputStream源码:

      public abstract class ServletInputStream extends InputStream

      1.ServletInputStream继承自InputStream类

      2.ServletInputStream没有重写reset方法和markSupported 方法

     

      其次,查看其分类是否重写reset和markSupported 方法

      public synchronized void reset() throws IOException {

          throw new IOException("mark/reset not supported");

      }

      public boolean markSupported() {

          return false;

      }

      查看InputStream类可知,没有重写reset方法和markSupported 方法.因此从ServletInputStream steam = request.getInputStream();只能从ServletInputStream 读取流中的数据一次.

      具体我们查看InputStream的API.具体中文版如下:

      https://www.matools.com/file/manual/jdk_api_1.8_google/java/io/InputStream.html

     

      读取多次:

      如何获取流中的数据多次呢?

      前提是ServletInputStream steam = request.getInputStream();去获取流中的数据只能获取一次.而根据面向对象的多态特性,凡是父类出现的地方都可以用子类替换掉,调用的方法都可以使用子类重写后的方法.

      从Filter中方法可知:传递的是ServletRequest接口的实现类

      

      我们可以重写该接口的实现类,然后传入到doFilter方法中.

       

      javax.servlet.HttpServletRequestWrapper已实现 HttpServletRequest

      public class HttpServletRequestWrapper extends

      ServletRequestWrapper implements HttpServletRequest

      因此我们只需要自定义个包装类,然后将从流中的数据保存到自定义的数组中或字符串中,然后重写getInputStream方法即可.

      

    package com.neutron.request.filter;
    
    import lombok.extern.slf4j.Slf4j;
    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.ServletRequest;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.*;
    import java.nio.charset.Charset;
    
    @Slf4j
    public class XssRequestWrapper extends HttpServletRequestWrapper {
        /** 保存获取IO流中数据,以后从body中获取流数据 */
        private final byte[] body;
    public XssRequestWrapper(HttpServletRequest request) { super(request); // 将body中数据存储起来 String stream = getBodyString(request); body = stream.getBytes(Charset.forName("UTF-8")); } /** 获取请求Body */ private String getBodyString(final ServletRequest request) { try { return inputStream2String(request.getInputStream()); } catch (IOException e) { throw new RuntimeException(e); } } /** 获取请求Body */ public String getBodyString() { final InputStream inputStream = new ByteArrayInputStream(body); return inputStream2String(inputStream); } /** 将inputStream里的数据读取出来并转换成字符串 */ private String inputStream2String(InputStream inputStream) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { log.error("", e); throw new RuntimeException(e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { log.error("", e); } } } return sb.toString(); } // 以下需要重写的方法 @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream stream = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return stream.read();} @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }

     

      将自定义的包装类,向后面的接口做参数传递,那么相当于可以从流中多次获取数据.实际上已经从流中获取数据一次,存放在某个缓存中,以后获取流中的数据都从缓存中获取.

       

      否则从流读取完数据后,经过拦截器和过滤器后,映射到具体的接口时,比如:

      

      xss对象的数据会直接为null,不会经过字段映射直接赋值.

     

      参考地址: https://blog.csdn.net/qq_34548229/article/details/104014374

          项目代码: https://github.com/zhtzyh2012/io-flow2  (已做验证,可使用)

     

  • 相关阅读:
    Building Java Projects with Gradle
    Vert.x简介
    Spring及Spring Boot 国内快速开发框架
    dip vs di vs ioc
    Tools (StExBar vs Cmder)which can switch to command line window on context menu in windows OS
    SSO的定义、原理、组件及应用
    ModSecurity is an open source, cross-platform web application firewall (WAF) module.
    TDD中测试替身学习总结
    Spring事务银行转账示例
    台式机(华硕主板)前面板音频接口(耳机和麦克风)均无声的解决办法
  • 原文地址:https://www.cnblogs.com/zhtzyh2012/p/13962402.html
Copyright © 2011-2022 走看看