zoukankan      html  css  js  c++  java
  • coding++:拦截器拦截requestbody数据如何防止流被读取后数据丢失

    1):现在开发的项目是基于SpringBoot的maven项目,拦截器的使用很多时候是必不可少的,当有需要需要你对body中的值进行校验,例如加密验签、防重复提交、内容校验等等。 

    2):当你开开心心的在拦截器中通过 request.getInputStream(); 获取到body中的信息后。

        你会发现你在 controlle r中使用了 @RequestBody 或者 再次 request.getInputStream() 获取数据时报错了,流数据丢失了。

    3):@RequestBody 只能以流的方式读取,流被读过一次后,就不在存在了,会导致会续无法处理,因此不能直接读流。

    I/O error while reading input message; nested exception is java.io.IOException: Stream closed
    org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:229)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:150)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:128)
        at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
        at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    错误信息 仅供参考

    为了解决这个问题,思路如下:

    1、读取流前先把流保存一下

    2、使用过滤器拦截读取,再通过chain.doFilter(wrapper, response);将保存的流丢到后面程序处理

    最简单的方案就是 先读取流,然后在将流写进去就行了

    1):创建 HttpHelper.java  用于读取Body

    package mlq.pic.filter.requestbodyfilter;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.nio.charset.Charset;
    
    /**
     * @author MaLQ
     * @Description: 用于读取Body
     * @date 2020年3月19日14:38:12
     */
    public class HttpHelper {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);
    
        public static String getBodyString(HttpServletRequest request) throws IOException {
            LOGGER.info("开始读取requestBody数据...");
            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();
                    }
                }
            }
            LOGGER.info("结束读取requestBody数据...body={}", sb.toString());
            return sb.toString();
        }
    }

    2):创建 RequestReaderHttpServletRequestWrapper.java  用于 requestBody 数据流处理

    package mlq.pic.filter.requestbodyfilter;
    
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.BufferedReader;
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.nio.charset.Charset;
    
    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    
    /**
     * @author MaLQ
     * @Description: requestBody 数据流处理
     * @date 2020年3月19日14:38:12
     */
    public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(RequestReaderHttpServletRequestWrapper.class);
    
        private final byte[] body;
    
        public RequestReaderHttpServletRequestWrapper(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) {
    
                }
            };
        }
    
    
    }

    3):创建 HttpServletRequestReplacedFilter.java  过滤器

    package mlq.pic.filter.requestbodyfilter;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    
    /**
     * @author MaLQ
     * @Description: 过滤器拦截
     * @date 2020年3月19日14:38:12
     */
    public class HttpServletRequestReplacedFilter implements Filter {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(HttpServletRequestReplacedFilter.class);
    
        @Override
        public void init(FilterConfig arg0) throws ServletException {
            LOGGER.info("HttpServletRequestReplacedFilter...init");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response,
                             FilterChain chain) throws IOException, ServletException {
    
            LOGGER.info("HttpServletRequestReplacedFilter...doFilter");
    
            ServletRequest requestWrapper = null;
            if (request instanceof HttpServletRequest) {
                requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
            }
            //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
            // 在chain.doFiler方法中传递新的request对象
            if (requestWrapper == null) {
                chain.doFilter(request, response);
            } else {
                chain.doFilter(requestWrapper, response);
            }
    
        }
    
        @Override
        public void destroy() {
            LOGGER.info("HttpServletRequestReplacedFilter...destroy");
        }
    
    }

    4):最后我们只需要在 Application.java 启动时加上如下代码注入过滤器即可

    package mlq.pic.config;
    
    import mlq.pic.filter.requestbodyfilter.HttpServletRequestReplacedFilter;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class FilterConfigurerConfig {
    
        @Bean
        public FilterRegistrationBean httpServletRequestReplacedRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new HttpServletRequestReplacedFilter());
            registration.addUrlPatterns("/*");
            registration.addInitParameter("paramName", "paramValue");
            registration.setName("httpServletRequestReplacedFilter");
            registration.setOrder(1);
            return registration;
        }
    
    }

    5):获取@RequestBody 里面的数据(在HttpServletRequest里是以流的方式传输的)

    /**
     * @author MaLQ
     * @Description: 获取@RequestBody 里面的数据(在HttpServletRequest里是以流的方式传输的)
     * @date 2020年3月19日13:39:31
     */
    public static Object getBodyParam(HttpServletRequest request,Class cls) {
        ServletInputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            return JSONObject.parseObject(inputStream, Charset.forName("UTF-8"), cls);
        } catch (Exception e) {
            LOGGER.error("获取@RequestBody数据转换异常:error={}" + e.getMessage(), e);
        }
        return null;
    }
    
    // 案例
    WxAlbumDetailVo detailVo = (WxAlbumDetailVo) RequestUtils.getBodyParam(request, WxAlbumDetailVo.class);

    以上操作就可以解决 requestbody 数据获取一次丢失问题。

  • 相关阅读:
    5-python基础—获取某个目录下的文件列表(适用于任何系统)
    Automated, Self-Service Provisioning of VMs Using HyperForm (Part 1) (使用HyperForm自动配置虚拟机(第1部分)
    CloudStack Support in Apache libcloud(Apache libcloud中对CloudStack支持)
    Deploying MicroProfile-Based Java Apps to Bluemix(将基于MicroProfile的Java应用程序部署到Bluemix)
    Adding Persistent Storage to Red Hat CDK Kit 3.0 (在Red Hat CDK Kit 3.0添加永久性存储)
    Carve Your Laptop Into VMs Using Vagrant(使用Vagran把您笔记本电脑刻录成虚拟机)
    使用Python生成一张用于登陆验证的字符图片
    Jupyter notebook的安装方法
    Ubuntu16.04使用Anaconda5搭建TensorFlow使用环境 图文详细教程
    不同时区的换算
  • 原文地址:https://www.cnblogs.com/codingmode/p/12524581.html
Copyright © 2011-2022 走看看