HttpSevletRequest Body信息多次读取填坑录-java

标签: httpsevletrequest body 信息 | 发表时间:2020-06-05 11:13 | 作者:gloryxiao
出处:https://juejin.im/welcome/backend

1、背景

业务拦截器需要拦截request的所有传入信息。通常情况下,HttpServletRequst 中的 body 内容只会读取一次,但是可能某些情境下可能会读取多次,由于 body 内容是以流的形式存在,所以第一次读取完成后,第二次就无法读取了,一个典型的场景就是 Filter 在校验完成 body 的内容后,业务方法就无法继续读取流了,导致解析报错。

2、解决方案

我们是否可以用装饰器来修饰一下 request,使其可以包装读取的内容,供多次读取。

这个装饰器要符合httpsevletrequest接口规范,在原有的框架中spring boot提供了一个简单的封装器ContentCachingRequestWrapper,从源码上看这个封装器并不实用,没有封装http的底层流ServletInputStream信息,导致使用@RequestParam,@RequestBody等使用底层流构建的逻辑依然无用,只能硬生生的使用

  public byte[] getContentAsByteArray() {
   return this.cachedContent.toByteArray();
}
复制代码

来自己在业务中处理body信息。 这里,我参照ContentCachingRequestWrapper封装了一个更具可靠性,更低侵入性的装饰器:

  package com.interceptor;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.web.util.WebUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author sean
 */
public class RepeatReadHttpRequest extends HttpServletRequestWrapper {
    private static final Logger LOGGER = LoggerFactory.getLogger(RepeatReadHttpRequest.class);
    private final ByteArrayOutputStream cachedContent;
    private Map cachedForm;

    @Nullable
    private ServletInputStream inputStream;

    public RepeatReadHttpRequest(HttpServletRequest request) {
        super(request);
        this.cachedContent = new ByteArrayOutputStream();
        this.cachedForm = new HashMap<>();
        cacheData();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        this.inputStream = new RepeatReadInputStream(cachedContent.toByteArray());
        return this.inputStream;
    }

    @Override
    public String getCharacterEncoding() {
        String enc = super.getCharacterEncoding();
        return (enc != null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING);
    }

    @Override
    public BufferedReader getReader() throws IOException {
         return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
    }

    @Override
    public String getParameter(String name) {
        String value = null;
        if (isFormPost()) {
            String[] values = cachedForm.get(name);
            if (null != values && values.length > 0) {
                value = values[0];
            }
        }

        if (StringUtils.isEmpty(value)) {
            value = super.getParameter(name);
        }

        return value;
    }

    @Override
    public Map getParameterMap() {
        if (isFormPost() && !CollectionUtils.sizeIsEmpty(cachedForm)) {
            return cachedForm;
        }

        return super.getParameterMap();
    }

    @Override
    public Enumeration getParameterNames() {
        if (isFormPost() && !CollectionUtils.sizeIsEmpty(cachedForm)) {
            return Collections.enumeration(cachedForm.keySet());
        }

        return super.getParameterNames();
    }

    @Override
    public String[] getParameterValues(String name) {
        if (isFormPost() && !CollectionUtils.sizeIsEmpty(cachedForm)) {
            return cachedForm.get(name);
        }

        return super.getParameterValues(name);
    }

    private void cacheData() {
        try {
            if (isFormPost()) {
                this.cachedForm = super.getParameterMap();
            } else {
                ServletInputStream inputStream = super.getInputStream();
                IOUtils.copy(inputStream, this.cachedContent);
            }
        } catch (IOException e) {
            LOGGER.warn("[RepeatReadHttpRequest:cacheData], error: {}", e.getMessage());
        }

    }

    private boolean isFormPost() {
        String contentType = getContentType();
        return (contentType != null &&
                (contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE) ||
                        contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) &&
                HttpMethod.POST.matches(getMethod()));
    }

    private static class RepeatReadInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;

        public RepeatReadInputStream(byte[] bytes) {
            this.inputStream = new ByteArrayInputStream(bytes);
        }

        @Override
        public int read() throws IOException {
            return this.inputStream.read();
        }

        @Override
        public int readLine(byte[] b, int off, int len) throws IOException {
            return this.inputStream.read(b, off, len);
        }

        @Override
        public boolean isFinished() {
            return this.inputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener listener) {

        }
    }
}
复制代码

使用这个装饰器时需要配合Filter对原有的request进行偷梁换柱,注册Filter并在调用链中将原有的request换成

  RepeatReadHttpRequest
package com.interceptor;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @author sean
 * 拦截器httprequest替换 可重复获取inputstream
 */
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean requestReplaceFilterRegistration() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new RequestReplaceFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setName("RequestReplaceFilter");
        registrationBean.setOrder(1);
        return registrationBean;
    }

    public static class RequestReplaceFilter implements Filter {
        @Override
        public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {

        }

        @Override
        public void destroy() {

        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            filterChain.doFilter(new RepeatReadHttpRequest((HttpServletRequest) servletRequest), servletResponse);
        }
    }
}
复制代码

后续的request就可以使用封装器多次读取body信息。

相关 [httpsevletrequest body 信息] 推荐:

HttpSevletRequest Body信息多次读取填坑录-java

- - 掘金后端
业务拦截器需要拦截request的所有传入信息. 通常情况下,HttpServletRequst 中的 body 内容只会读取一次,但是可能某些情境下可能会读取多次,由于 body 内容是以流的形式存在,所以第一次读取完成后,第二次就无法读取了,一个典型的场景就是 Filter 在校验完成 body 的内容后,业务方法就无法继续读取流了,导致解析报错.

[一日一美女]最强body 冲田杏梨[30P]

- Mihua - 乐淘吧
摘要:最近网络疯传的“最强body 冲田杏梨”这样的搜索随处可见. 冲田杏梨,S1近来极力大推的一位女优新人,并以人类最完美的身体为宣传口号,对她进行包装. 冲田杏梨网络爆红 K罩杯被赞“人类最强body”. 冲田杏梨,要是你还不知道这个女人的名字话,你Out了,最近网络疯传的“最强body 冲田杏梨”这样的搜索随处可见.

Page Speed Online 和 Public Data Explorer 生,Google Body 死

- 瑠音北樟 - 谷奥——探寻谷歌的奥秘
又有三款Google Labs应用决定了生死. Page Speed Online是今年年初发布的一个网页速度分析工具,你只需给出一个URL地址,即可返回给你速度得分以及一些如何让网页加载速度变快的建议. 尽管目前Page Speed Online还在使用googlelabs.com这一域名,但Page Speed网页里却说将会由其它开发工具继续提供服务,尽管没说会被哪个Google服务接管,但估计应该是Webmaster Tools吧.

【网络爬虫之JSOUP使用简介】解析一个body片断

- - CSDN博客编程语言推荐文章
假如你有一个HTML片断 (比如. 一个 div 包含一对 p 标签; 一个不完整的HTML文档) 想对它进行解析. 这个HTML片断可以是用户提交的一条评论或在一个CMS页面中编辑body部分. Jsoup.parseBodyFragment(String html)方法.. parseBodyFragment 方法创建一个空壳的文档,并插入解析过的HTML到 body元素中.

信息架构

- Michael - Tony-懒得设计
写几篇关于信息架构的文章,系统地输出我理解的信息架构. 发了一篇关于招信息架构实习生的博客,收到不少简历. 但谈起信息架构,多数不了解,稍微了解的扯了很多很偏的东西. 随手搜索了一下,我发现了原因:. 1 《web信息架构》这本书太概念,太学术. 2 有人绑架了“信息架构”这个词,拿出去唬人,内容都是皮毛或者是根本和信息架构不沾边的东西.

信息茧房

- - 云风的 BLOG
最近一个月,无论我打开推特,还是微博豆瓣,每天都被“徐州八孩母亲事件”刷屏. 在公司吃工作餐的时候,也和同事聊过这个事件. 从我的角度看,此事的热度之高,持续时间之久,近年罕见. 能读到我的 blog 这一篇的同学,相信都对这件事的来龙去脉不会太陌生. 如果你不了解,那么 google 一下“徐州八娃女”或是“丰县铁链女” 就能找到很多信息.

免费VPN信息

- 勇 - iGFW
免费试用2天的pptp pn和openvpn. 免费美国pptp vpn帐号. 服务器 199.119.203.166 用户名 vpn 密码 1q2w3e. PageRank为 1及以上的网站,发文介绍此VPN即可得到免费一年的pptp vpn服务,如果再完成其指定的五步操作可获终身免费帐号. Trial accounts have some limitations like you can only use our single demo server, 200mb of bandwidth usage everyday(6GB/Month), some websites are blocked.

Oracle 统计信息

- - 数据库 - ITeye博客
--Oracle 优化器统计信息 Oracle优化器统计信息描述了关于数据库和相关对象的统计信息,当执行SQL查询时,优化器会使用这些统计信息估算出各种不同的执行计划的资源消耗, ,从而选择最高效的执行计划. 当统计信息缺失或者陈旧时,Oracle可能会选择错误的执行计划,导致SQL执行效率低下.