欢迎来到代码驿站!

当前位置:首页 >

完美解决request请求流只能读取一次的问题

时间:2020-08-24 10:00:23|栏目:|点击:

解决request请求流只能读取一次的问题

实际开发碰到的问题

解决request请求流中的数据二次或多次使用问题

实际开发碰到的问题

springboot项目中,为了防止sql注入,采用Filter拦截器对所有请求流中的json数据进行校验,请求数据没问题则继续向下执行,在后边的代码中应用到请求参数值时,发现request中的json数据为空;

除上边描述的情况,尝试过两次从request中获取json数据,第二次同样是获取不到的。

解决request请求流中的数据二次或多次使用问题

继承HttpServletRequestWrapper,将请求体中的流copy一份,覆写getInputStream()和getReader()方法供外部使用。每次调用覆写后的getInputStream()方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一直存在,这样就实现了流的重复读取。

//增强类
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
  //保存流
  private byte[] requestBody = null;
 
  public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
    super(request);
    requestBody = StreamUtils.copyToByteArray(request.getInputStream());
  }
 
  @Override
  public ServletInputStream getInputStream() throws IOException {
 
    final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
 
    return new ServletInputStream() {
 
      @Override
      public int read() throws IOException {
        return bais.read();
      }
 
      @Override
      public boolean isFinished() {
        return false;
      }
 
      @Override
      public boolean isReady() {
        return false;
      }
 
      @Override
      public void setReadListener(ReadListener readListener) {
 
      }
    };
  }
 
  @Override
  public BufferedReader getReader() throws IOException{
    return new BufferedReader(new InputStreamReader(getInputStream()));
  }
} 
 
//过滤器
@Component
@WebFilter
public class RequestSqlValidFilter implements Filter {
  private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
 
  }
 
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest)request);
 
    //请求参数合法,无sql注入
    if((sqlValid(request, response))){
      chain.doFilter(requestWrapper, response);//requestWrapper中保存着供二次使用的请求数据
    }else {
      logger.error("RequestSqlValidFilter sqlValid param error");
    }
  }
 
  @Override
  public void destroy() {
 
  }

补充知识:【java web】解决流读完一次就不能再次获取body数据的问题

问题来自我工作业务上的需求:前端请求时需要将json用RSA算法加密,数据经过后端过滤器进行自动解密,这样做的好处是以后不需要在每一个方法里都手动解密一次,增加代码的简洁性、可维护性。

但这样一来便会面临一个问题:http的request请求的输入流在过滤器中就已经被读取了(因为需要读取并解密request body 里被前端加密了的json数据),流只能被读取一次,这样一来数据便传不进controller里,导致接下来的业务无法进行。

于是我上网找了一些资料并成功解决了这个问题,基本思路是封装原生的HttpServletRequest请求,将其输入流里的数据保存在字节数组里,最后重写getInputStream方法,使其之后每次读取数据都是从字节数组里读取的。

第一步:写一个Request包装类BodyReaderHttpServletRequestWrapper

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
 
  private byte[] body;
 
  public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
    super(request);
    body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
  }
 
  @Override
  public BufferedReader getReader() throws IOException {
    return new BufferedReader(new InputStreamReader(getInputStream()));
  }
 
  @Override
  public ServletInputStream getInputStream() throws IOException {
 
    final ByteArrayInputStream bais = new ByteArrayInputStream(body);
 
    return new ServletInputStream() {
 
      @Override
      public int read() throws IOException {
        return bais.read();
      }
 
      @Override
      public boolean isFinished() {
        return false;
      }
 
      @Override
      public boolean isReady() {
        return false;
      }
 
      @Override
      public void setReadListener(ReadListener readListener) {
 
      }
 
    };
  }
 
  public void setInputStream(byte[] body){
    this.body = body;
  }

里面涉及一个HttpHelper类,顺便也贴出来

public class HttpHelper {
 
  /**
   * 获取请求Body
   * @param request
   * @return
   */
  public static String getBodyString(ServletRequest request) {
    StringBuilder sb = new StringBuilder();
    InputStream inputStream = null;
    BufferedReader reader = null;
    try {
      inputStream = request.getInputStream();
      reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
      String line = "";
      while ((line = reader.readLine()) != null) {
        sb.append(line);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (inputStream != null) {
        try {
          inputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if (reader != null) {
        try {
          reader.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    return sb.toString();
  }  
}

第二步:编写包装Filter

这个filter类可以将经过的原生Request请求自动包装成BodyReaderHttpServletRequestWrapper

/**
 * desc : 用于包装原生request, 解决流读完一次就不能再次获取body数据的问题
 * Created by Lon on 2018/3/9.
 */
public class RequestWrapperFilter implements Filter{
 
  private static final Logger LOGGER = LoggerFactory.getLogger(RequestWrapperFilter.class);
 
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
 
  }
 
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    ServletRequest requestWrapper = null;
    if(request instanceof HttpServletRequest) {
      requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
    }
    if(null == requestWrapper) {
      LOGGER.error("包装request失败!将返回原来的request");
      chain.doFilter(request, response);
    } else {
      LOGGER.info("包装request成功");
      chain.doFilter(requestWrapper, response);
    } 
  }
 
  @Override
  public void destroy() {
 
  }
}

第三步:在web.xml上配置过滤器

 <filter>
  <filter-name>RequestWrapperFilter</filter-name>
  <filter-class>com.kx.security.filter.RequestWrapperFilter</filter-class>
 </filter>
 <filter-mapping>
  <filter-name>RequestWrapperFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

这里要注意的是这个包装过滤器可能要写在web.xml配置文件里某些过滤器的前面,比如解密过滤器,否则被解密过滤器先读取流的话,包装过滤器就读取不了流了。

至此一个简单的解决方法就完成啦!

上一篇:springboot启动脚本start.sh和停止脚本 stop.sh的详细教程

栏    目:

下一篇:R语言ggplot2边框背景去除的实现

本文标题:完美解决request请求流只能读取一次的问题

本文地址:http://www.codeinn.net/misctech/3955.html

推荐教程

广告投放 | 联系我们 | 版权申明

重要申明:本站所有的文章、图片、评论等,均由网友发表或上传并维护或收集自网络,属个人行为,与本站立场无关。

如果侵犯了您的权利,请与我们联系,我们将在24小时内进行处理、任何非本站因素导致的法律后果,本站均不负任何责任。

联系QQ:914707363 | 邮箱:codeinn#126.com(#换成@)

Copyright © 2020 代码驿站 版权所有