• 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 数据获取一次丢失问题。

  • 相关阅读:
    黑客工具包ShadowBrokers浅析
    浅谈Miller-Rabin素数检测算法
    辗转相除法(欧几里得算法)的证明
    2019年年终感言
    详解矩阵乘法
    计数类问题中的取模运算总结
    浅谈同余方程的求解与中国剩余定理
    模板测试题
    洛谷 P3811 【模板】乘法逆元
    同余知识点全析
  • 原文地址:https://www.cnblogs.com/codingmode/p/12524581.html
Copyright © 2020-2023  润新知