• 如何在流中重复获取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  (已做验证,可使用)

     

  • 相关阅读:
    关于ugc的一点思考
    Fenng早年间对推荐系统的思考
    对于软件开发的一些思考
    并发排序
    Standford CoreNLP使用
    做事情的方式
    JAVA! static的作用
    struts2学习笔记--使用Validator校验数据
    Struts2中的ModelDriven机制及其运用
    ValueStack与ContentMap (ActionContext.getContext().getValueStack().set())
  • 原文地址:https://www.cnblogs.com/zhtzyh2012/p/13962402.html
Copyright © 2020-2023  润新知