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的請求參數後,咱們能夠前置作不少操做,好比經常使用的服務端接口簽名驗證,敏感接口防重複請求等等。

我的水平有限,若是文章有邏輯錯誤或表述問題還請指出,歡迎一塊兒交流。

相關文章
相關標籤/搜索