• SpringBoot 拦截器获取http请求参数


    SpringBoot 拦截器获取http请求参数—— 所有骚操作基础

    获取http请求参数是一种刚需

    我想有的小伙伴肯定有过获取http请求的需要,比如想

    1. 前置获取参数,统计请求数据
    2. 做服务的接口签名校验
    3. 敏感接口监控日志
    4. 敏感接口防重复提交

    等等各式各样的场景,这时你就需要获取 HTTP 请求的参数或者请求body,一般思路有两种,一种就是自定义个AOP去拦截目标方法,第二种就是使用拦截器。整体比较来说,使用拦截器更灵活些,因为每个接口的请求参数定义不同,使用AOP很难细粒度的获取到变量参数,本文主线是采用拦截器来获取HTTP请求。

    定义拦截器获取请求

    基于 spring-boot-starter-parent 2.1.9.RELEASE

    看起来这个很简单,这里就直接上code,定义个拦截器

    /**
     * @author axin
     * @summary HTTP请求拦截器
     */
    @Slf4j
    public class RequestInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            //获取请求参数
            String queryString = request.getQueryString();
            log.info("请求参数:{}", queryString);
    
            //获取请求body
            byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
            String body = new String(bodyBytes, request.getCharacterEncoding());
    
            log.info("请求体:{}", body);
            return true;
        }
    }
    

    然后把这个拦截器配置一下中:

    /**
     * WebMVC配置,你可以集中在这里配置拦截器、过滤器、静态资源缓存等
     */
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**");
        }
    }
    

    定义个接口测试一下

    /**
     * @author axin
     * @summary 提交测试接口
     */
    @Slf4j
    @RestController
    public class MyHTTPController {
    
        @GetMapping("/v1/get")
        public void get(@RequestParam("one") String one,
                        @RequestParam("two") BigDecimal number) {
            log.info("参数:{},{}", one, number);
        }
    
    
        @PostMapping("/v1/post")
        public void check(@RequestBody User user) {
    
            log.info("{}", JSON.toJSONString(user));
        }
    }
    

    GET请求获取请求参数示例:
    image

    POST请求获取请求Body示例:
    image

    我们发现拦截器在获取HTTP请求的body时出现了 400 (Required request body is missing: public void com.axin.world.controller.MyHTTPController.check(com.axin.world.domain.User));同时也发现拦截器竟然走了两遍,这又是咋回事呢?

    image

    为什么拦截器会重复调两遍呢?

    其实是因为 tomcat截取到异常后就转发到/error页面,就在这个转发的过程中导致了springmvc重新开始DispatcherServlet的整个流程,所以拦截器执行了两次,我们可以看下第二次调用时的url路径:

    image

    ServletInputStream(CoyoteInputStream) 输入流无法重复调用

    而之前出现的 Required request body is missing 错误 其实是ServletInputStream被读取后无法第二次再读取了,所以我们要把读取过的内容存下来,然后需要的时候对外提供可被重复读取的ByteArrayInputStream。

    对于MVC的过滤器来说,我们就需要重写 ServletInputStream 的 getInputStream()方法。

    自定义 HttpServletRequestWrapper

    为了 重写 ServletInputStream 的 getInputStream()方法,我们需要自定义一个 HttpServletRequestWrapper :

    /**
    * @author Axin
    * @summary 自定义 HttpServletRequestWrapper 来包装输入流
    */
    public class AxinHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
        /**
         * 缓存下来的HTTP body
         */
        private byte[] body;
    
        public AxinHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            body = StreamUtils.copyToByteArray(request.getInputStream());
        }
    
        /**
         * 重新包装输入流
         * @return
         * @throws IOException
         */
        @Override
        public ServletInputStream getInputStream() throws IOException {
            InputStream bodyStream = new ByteArrayInputStream(body);
            return new ServletInputStream() {
    
                @Override
                public int read() throws IOException {
                    return bodyStream.read();
                }
    
                /**
                 * 下面的方法一般情况下不会被使用,如果你引入了一些需要使用ServletInputStream的外部组件,可以重点关注一下。
                 * @return
                 */
                @Override
                public boolean isFinished() {
                    return false;
                }
    
                @Override
                public boolean isReady() {
                    return true;
                }
    
                @Override
                public void setReadListener(ReadListener readListener) {
    
                }
            };
        }
        
        @Override
        public BufferedReader getReader() throws IOException {
            InputStream bodyStream = new ByteArrayInputStream(body);
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    }
    

    然后定义一个 DispatcherServlet子类来分派 上面自定义的 AxinHttpServletRequestWrapper :

    /**
    * @author Axin
    * @summary 自定义 DispatcherServlet 来分派 AxinHttpServletRequestWrapper
    */
    public class AxinDispatcherServlet extends DispatcherServlet {
    
        /**
         * 包装成我们自定义的request
         * @param request
         * @param response
         * @throws Exception
         */
        @Override
        protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            super.doDispatch(new AxinHttpServletRequestWrapper(request), response);
        }
    }
    

    然后配置一下:

    /**
     * WebMVC配置,你可以集中在这里配置拦截器、过滤器、静态资源缓存等
     */
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**");
        }
    
        @Bean
        @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            return new AxinDispatcherServlet();
        }
    }
    

    再调用一下 POST请求:

    image

    请求成功!

    总结一下 展望一下

    如果你想对HTTP请求做些骚操作,那么前置获取HTTP请求参数是前提,为此文本给出了使用MVC拦截器获取参数的样例。

    在获取HTTP Body 的时候,出现了 Required request body is missing 的错误,同时拦截器还出现执行了两遍的问题,这是因为 ServletInputStream被读取了两遍导致的,tomcat截取到异常后就转发到 /error 页面 被拦截器拦截到了,拦截器也就执行了两遍。

    为此我们通过自定义 HttpServletRequestWrapper 来包装一个可被重读读取的输入流,来达到期望的拦截效果。

    在获取到HTTP的请求参数后,我们可以前置做很多操作,比如常用的服务端接口签名验证,敏感接口防重复请求等等。

    个人水平有限,如果文章有逻辑错误或表述问题还请指出,欢迎一起交流。

  • 相关阅读:
    android 圆角图片的实现
    navigationView 的使用和布局文件的绑定
    android listview 的监听事件
    android第三方框架 xlistview 的使用
    android Baseadapter 和 ViewHolder的使用
    android 调用电话功能
    android 颜色对照
    Android_menu_SubMenu
    Android_menu_optionMenu
    Android_Menu_contextMenu
  • 原文地址:https://www.cnblogs.com/keeya/p/13634015.html
Copyright © 2020-2023  润新知