雜記 › 技術筆記 › 網址裡哪一段會到伺服器?六段拆分與 %20、+、二次編碼

網址裡哪一段會到伺服器?六段拆分與 %20、+、二次編碼

日誌裡找不到頁面跳轉時帶的那段錨點,中文搜尋詞在日誌裡變成了一串百分號,金鑰裡的加號莫名變成空格——這三件事都指向同一個源頭:一條網址並不是一整塊送到伺服器的,它被切成六段,每一段的命運不一樣。

六段分別在哪裡

段本文實測用的位址會不會送給伺服器
協定 schemehttp決定連接埠與是否加密
主機 host127.0.0.1會
連接埠 port8198會;省略時按協定取預設值,http 是 80、https 是 443
路徑 path/a/b/c會
查詢 queryx=1&y=2會,帶的是編碼原文
錨點 fragmentsection-3不會

實測一:錨點根本沒出瀏覽器

在本機起了一個最小服務,把收到的欄位原樣印出來。請求 http://127.0.0.1:8198/a/b/c?x=1&y=2#section-3 時,伺服器端記錄的是 REQUEST_URI 等於 /a/b/c?x=1&y=2,QUERY_STRING 等於 x=1&y=2,解析後拿到 x=1、y=2,而 section-3 哪裡都沒有出現。所以後台日誌查不到錨點不是欄位丟了,是它從來不上網:頁內定位、前端路由的 hash 都由瀏覽器自己處理。

實測二:加號能當空格,反過來不行

同一組請求裡,q=a+b+c 和 q=a%20b%20c 在伺服器端都解析成 a b c;而 token=a%2Bb 解析出來是字面的 a+b,寫成 token=a+b 得到的卻是「a b」。區別在於查詢字串的解碼規則把加號當成空格的一種寫法,所以你要傳一個真正的加號就必須寫成 %2B。直接拼上去會被解成空格,很多「金鑰莫名不對」的排查最後就落在這一個字元上。

實測三:中文與二次編碼

請求 q=%E4%B8%AD%E6%96%87,QUERY_STRING 裡看到的是編碼原文,而解析後的參數已經是中文。如果同一個字串被編碼了兩遍,位址裡就成 q=%25E4%25B8%25AD,伺服器端解一次得到的是 %E4%B8%AD——仍然帶著百分號,說明只解了一層。這類現象沒有玄學,唯一可靠的解法是數清楚自己解了幾次:傳輸層解一次,業務層若還要再解就得顯式約定。

實測四:同名鍵、沒有鍵的值、非法轉義

重複的鍵名不會自動變成陣列。?q=a&q=b 解析後只剩最後一個值 b;要同時拿到兩個,得寫成 q[]=a&q[]=b,伺服器端才給出含兩項的陣列。只有值沒有鍵的片段更乾脆,?=onlyvalue&k= 解析出來只有一個空字串的 k,那個孤零零的 onlyvalue 直接消失,任何程式碼都取不到它。

鍵名裡也能藏編碼。?a=1&%20b=2&c=%20 這一串,伺服器端解出的鍵是 a 和 b——前導空格被吃掉,而值末尾的 %20 老實變成一個空格保留下來。非法寫法同樣不報錯:q=100%、q=a%zzb 都原樣到了伺服器端,解析結果裡仍然是 100% 和 a%zzb。這只說明本機這個解析器寬容——百分號本該由兩個十六進位字元收尾,寫成裸 % 時不能指望鏈路上每個中介軟體都用同樣的方式放過它。真要傳一個字面的百分號,就寫 %25,實測 q=100%25 解析出來是 100%。

哪些字元在查詢值裡必須轉義

要盯的是那幾個承擔結構職責的字元:& 會被當成分隔符,= 會被當成鍵值分界,+ 會被當成空格,# 會當場把整條查詢截斷,% 是所有轉義的起始符。實測把 q=%3D%2B%26%25 送出去,伺服器端解出的是 =+&% 四個字元,一個不多一個不少,說明一次解碼就還原到位。斜線反而不必管,?path=/x/y 原樣通過。

路徑段是另一套:# 一旦以本來面目出現,它後面的內容全部留在瀏覽器裡;寫成 %23 就只是路徑裡的三個字元,實測 /a%23b/c?q=1 到伺服器端的 REQUEST_URI 仍是 /a%23b/c,查詢照常解析。同類地,/a%2Fb/c 裡的 %2F 會保持編碼態傳到伺服器端,是否被解成一個額外的路徑分隔,就交給各框架的路由規則決定了——這正是「路徑裡帶斜線要不要轉義」不能靠猜的原因。

表單體和查詢字串不是同一套習慣

以 application/x-www-form-urlencoded 提交時,請求體裡的編碼規則和查詢字串一致,空格同樣寫成 +;換成 JSON 請求體之後,這些字元由字串本身承載,不需要百分比轉義。把查詢字串的編碼習慣帶進 JSON,就會存下帶 %20 的髒資料。同一段中文,走查詢要寫成 %E4%B8%AD%E6%96%87,走 JSON 只需要正確的 UTF-8 編碼。

拆分怎麼做省事

排查時我習慣把整條位址貼進 URL 解析工具,讓它把六段列出來,順帶把 QUERY_STRING 原文與解析後的參數並排看清;需要手工往某個值裡塞中文或特殊字元時,用 URL 編解碼 產生轉義結果,別靠肉眼拼百分號,長度一長必錯。

Q:路徑裡的中文和查詢裡的中文編碼規則一樣嗎

都用百分比編碼,但允許的字元集不同:路徑段裡有若干字元可以原樣出現,查詢字串則要把當分隔符用的等號、與號轉義掉。整串交給標準編碼函式,比逐字元判斷安全。

Q:為什麼日誌裡存的是編碼態

因為存取日誌記的是 QUERY_STRING 原文,解碼發生在框架解析參數的那一步。上面實測輸出的同一行裡,兩個欄位分別就是編碼原文和解碼結果,可以直接對照。

Q:hash 路由的頁面切換能被後端統計到嗎

不能,錨點不出瀏覽器。要按頁面維度統計,就得讓不同檢視有不同的路徑或查詢參數,這也是前端路由從 hash 模式改走 history 模式的主要原因。

Q:用 hash 還是 query 存頁面狀態

看要不要離開瀏覽器。改 hash 不會發出請求,不進伺服器端日誌、不被爬蟲當成不同頁面,適合純前端的展開、定位、分頁游標;一旦這個值需要伺服器端讀到——篩選條件、分享連結、埋點維度——就必須放進路徑或查詢,因為實測裡 REQUEST_URI 從頭到尾不含 # 之後的任何東西。

📎 相關文章

PNG 壓了三遍只減 426 位元組:無損壓縮的真實邊界 2026-10-22 WebP 轉 PNG 後體積漲了幾倍:什麼時候必須轉,怎麼把漲幅壓住 2026-10-22 截圖縮到四分之一,小字糊成一團的三種原因 2026-10-22 同樣裁 300×300,頭像為什麼總偏右下角半格 2026-10-22 按每月 1.7 倍預估 18 個月,腳本和表格為什麼給出兩個數 2026-10-22