SpringBoot 拦截器获取http请求参数

SpringBoot 拦截器获取http请求参数—— 全部骚操做基础

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

我想有的小伙伴确定有过获取http请求的须要,好比想spring

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

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

定义拦截器获取请求

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

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

/**
 * @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;
    }
}

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

/**
 * WebMVC配置,你能够集中在这里配置拦截器、过滤器、静态资源缓存等
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**");
    }
}

定义个接口测试一下app

/**
 * @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请求获取请求参数示例:
imagedom

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

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

image

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

实际上是由于 tomcat截取到异常后就转发到/error页面,就在这个转发的过程当中致使了springmvc从新开始DispatcherServlet的整个流程,因此拦截器执行了两次,咱们能够看下第二次调用时的url路径:post

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的请求参数后,咱们能够前置作不少操做,好比经常使用的服务端接口签名验证,敏感接口防重复请求等等。

我的水平有限,若是文章有逻辑错误或表述问题还请指出,欢迎一块儿交流。

相关文章
相关标签/搜索