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

 

原文地址:https://www.cnblogs.com/zhtzyh2012/p/13962402.html