欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring中GET請(qǐng)求參數(shù)偶發(fā)性丟失問(wèn)題分析及修復(fù)

 更新時(shí)間:2025年04月23日 11:02:41   作者:不愿放下技術(shù)的小趙  
本文描述了一種在SpringCloud微服務(wù)架構(gòu)下GET接口偶爾出現(xiàn)參數(shù)丟失的問(wèn)題,通過(guò)源碼分析和復(fù)現(xiàn),發(fā)現(xiàn)問(wèn)題是由于線程中請(qǐng)求對(duì)象的生命周期管理導(dǎo)致的,解決方法包括改用POST請(qǐng)求或更換Tomcat為Undertow中間件,需要的朋友可以參考下

一、問(wèn)題現(xiàn)象

最近偶遇一詭異棘手問(wèn)題:一個(gè)用于獲取 tokenGET 接口,在生產(chǎn)環(huán)境不定期偶發(fā)出現(xiàn) 參數(shù)不存在 的問(wèn)題。一度懷疑是前端的鍋,雖然前端同學(xué)再三以人格擔(dān)保!經(jīng)過(guò)長(zhǎng)時(shí)間觀察,發(fā)現(xiàn)每每出現(xiàn)問(wèn)題時(shí),“再點(diǎn)一下就好了”!錯(cuò)誤信息簡(jiǎn)單明確,是大家熟知的參數(shù)缺失異常:

Required request parameter ‘phone’ for method parameter type String is not present

在這里插入圖片描述

這是怎么回事呢?這只是再普通不過(guò)的一個(gè) GET 接口!

在這里插入圖片描述

二、問(wèn)題分析

2.1 發(fā)生時(shí)間

由于項(xiàng)目使用的是 Spring Cloud 微服務(wù)框架,當(dāng)請(qǐng)求從瀏覽器發(fā)送過(guò)來(lái)后,經(jīng)過(guò)了以下步驟:

在這里插入圖片描述

順著這個(gè)思路逐層排查:

  • HTTP請(qǐng)求: F12查看參數(shù)正常,排除。
  • Nginx: 日志打印參數(shù)正常,排除。
  • Gateway: 日志打印參數(shù)正常,排除。
  • Controller: 參數(shù)丟失。。。

所以可以得出結(jié)論:參數(shù)丟失問(wèn)題發(fā)生在 Spring Cloud 微服務(wù)內(nèi)部。

2.2 發(fā)生位置

我們進(jìn)一步分析,在過(guò)濾器增加請(qǐng)求參數(shù)的打?。?/p>

LogFilter.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class LogFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        log.info(">>>>>>>>>>【INFO】request.getQueryString(): {}", httpServletRequest.getQueryString());
        log.info(">>>>>>>>>>【INFO】request.getParameter(): {}", httpServletRequest.getParameter("phone"));

        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

再次復(fù)現(xiàn)問(wèn)題后,在同一個(gè) traceId 對(duì)應(yīng)的日志中,打印結(jié)果如下:

在這里插入圖片描述

可以發(fā)現(xiàn)在問(wèn)題請(qǐng)求中 request.queryString() 正常,而 request.getParameter() 值卻沒(méi)有獲取到!

眾所周知,SpringBoot 默認(rèn)內(nèi)置 tomcat 容器,SpringMVC 則通過(guò) request.getParameter() 方法獲取并綁定 Controller 接口參數(shù)的。因此,初步判斷:在 tomcat 獲取 parameter 參數(shù)的時(shí)候出現(xiàn)了問(wèn)題。

那么,parameter 參數(shù)的獲取過(guò)程是怎樣的?

  1. SpringMVC 框架通過(guò) DispatcherServlet 實(shí)現(xiàn)。
  2. Tomcat 接收到外部請(qǐng)求,將由 connector 通過(guò) Processor 受理 http 請(qǐng)求。
  3. SpringMVC 通過(guò) request.getParameter() 獲取并綁定 Controller 接口參數(shù)。
  4. request.getParameter() 方法 在請(qǐng)求處理過(guò)程中僅在第一次調(diào)用 時(shí)通過(guò)解析 queryString 獲取 parameters 參數(shù)值,并設(shè)置 didQueryParameter=true 標(biāo)識(shí)已解析處理。
  5. Http 請(qǐng)求處理完成,processor 通過(guò) release 方法釋放連接重置參數(shù)屬性,request.recycle 方法重置 request 參數(shù)屬性(注意:這里 連接器及 request 對(duì)象并不會(huì)銷毀,connector 再次受理新的請(qǐng)求時(shí),將復(fù)用連接器、processor 及 request 對(duì)象而非創(chuàng)建)。

在這里插入圖片描述

2.3 源碼解析

下面,我們可以看一些源碼的片段來(lái)驗(yàn)證一下:

源碼1:SpringBoot 從 request 獲取 parameter 參數(shù)。

RequestParamMethodArgumentResolve 類的 resovleName() 方法,可以看到這里調(diào)用了 request.getParameterValue() 方法。

在這里插入圖片描述

源碼2:tomcat 封裝了解析參數(shù)。

org.apache.catalina.connector.Request 類的 getParameterValues() 方法,request 通過(guò) Parameters 獲取 parameter 參數(shù)。

在這里插入圖片描述

在這里插入圖片描述

源碼3:Parameters 從 queryString 解析封裝 parameter 參數(shù)。

org.apache.tomcat.util.http.Parameters 類的 handleQueryParameters() 方法,可以發(fā)現(xiàn),參數(shù)在解析處理后會(huì)設(shè)置 didQueryParameters 參數(shù)為 true。

在這里插入圖片描述

源碼4:請(qǐng)求處理結(jié)束,重置參數(shù)屬性,并不銷毀對(duì)象。

org.apache.tomcat.util.http.Parameters 類的 recycle() 方法。

在這里插入圖片描述

在這里插入圖片描述

2.4 Tomcat機(jī)制

Tomcat 機(jī)制如下:

  • tomcat 可支持多個(gè) service 示例;
  • 每個(gè) service 實(shí)例維護(hù)了一個(gè)包含多個(gè) connector 的連接池;
  • 當(dāng) service 接收到了一個(gè) http 請(qǐng)求時(shí),則從 connector 池中獲取一個(gè) connector 連接器進(jìn)行響應(yīng)處理。
  • connector 連接器是通過(guò) Processor 對(duì)應(yīng) HTTP 請(qǐng)求進(jìn)行響應(yīng)處理。

Processor 封裝了 requestresponse 對(duì)象,在請(qǐng)求處理開(kāi)始時(shí)進(jìn)行初始化封裝(進(jìn)封裝參數(shù)屬性,并不創(chuàng)建對(duì)象),請(qǐng)求處理完成后,則進(jìn)行釋放重置。(注意:這里的釋放僅指重置參數(shù)屬性,并不銷毀對(duì)象!

在這里插入圖片描述

2.5 原因總結(jié)

本次問(wèn)題的根本原因在于 線程中引用了 request 對(duì)象,并在線程中調(diào)用了 request.getParameter() 方法使參數(shù)屬性 didQueryParameter 錯(cuò)誤而導(dǎo)致 http 請(qǐng)求無(wú)法正確獲取參數(shù)值。

  • 假設(shè)第一次受理 http 請(qǐng)求的連接器為 connector1;
  • 請(qǐng)求 request 在子線程 thread1 中被引用;
  • connector1 完成 http 請(qǐng)求并執(zhí)行 release 釋放連接,這時(shí) request.didQueryParameters 值為 false;
  • 如果子線程 thread1 處理任務(wù)的時(shí)間較長(zhǎng),調(diào)用了 getParameter() 方法,這時(shí) request.didQueryParameters 值將再次被更新為 true;
  • 當(dāng) tomcat 再次通過(guò) connector1 受理新的 http 請(qǐng)求時(shí),由于 request.didQueryParameters=true,這時(shí)新請(qǐng)求調(diào)用 getParameter() 方法將不會(huì)再解析 queryString,因而無(wú)法正確獲取 parameter 參數(shù)值。

在這里插入圖片描述

三、問(wèn)題復(fù)現(xiàn)

這里為了方便,我們使用 Hutool 的線程池工具。依賴如下:

<!-- Hutool -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.23</version>
</dependency>

復(fù)現(xiàn)代碼如下:

DemoController.java

import cn.hutool.core.thread.ThreadUtil;
import com.demo.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Slf4j
@RestController
@RequestMapping("/demo")
public class DemoController {

    /**
     * 根據(jù)手機(jī)號(hào)獲取token
     */
    @GetMapping("/getToken")
    public Result<Object> getToken(@RequestParam String phone) {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        ThreadUtil.execute(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            ThreadUtil.safeSleep(1000);
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            System.out.println("********** " + request.getParameter(phone));
        });
        return Result.succeed();
    }

}

使用 Jmeter 壓測(cè)工具,設(shè)置 200 線程并發(fā)請(qǐng)求:

在這里插入圖片描述

壓測(cè) http://localhost:8080/demo/test?phone=111111 接口,配置請(qǐng)求信息如下:

在這里插入圖片描述

成功復(fù)現(xiàn),結(jié)果如下所示:

在這里插入圖片描述

四、問(wèn)題修復(fù)

修復(fù)這個(gè)問(wèn)題的話有兩種方式:

方式一: GET 請(qǐng)求改為 POST請(qǐng)求,使用 JSON 格式傳輸數(shù)據(jù)。

(經(jīng)過(guò)嘗試,即使使用 POST 請(qǐng)求,不使用 JSON 格式傳輸數(shù)據(jù)的話,還是會(huì)丟失參數(shù)。)

方式二: 將 tomcat 中間件替換為 undertow 中間件。修改后如下所示:

<dependency>
    <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

將 tomcat 替換為 undertow 之后,發(fā)現(xiàn)不再出現(xiàn)參數(shù)丟失的情況。

在這里插入圖片描述

以上就是Spring中GET請(qǐng)求參數(shù)偶發(fā)性丟失問(wèn)題分析及修復(fù)的詳細(xì)內(nèi)容,更多關(guān)于Spring GET請(qǐng)求參數(shù)丟失的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論