URL 編碼完整指南:百分比編碼、常見陷阱與實務建議

你可能遇過這種狀況:明明前端把查詢參數帶好了,後端卻收到亂碼;或是網址中的 +、空白、中文一混在一起,API 就開始回 400。這些問題多半不是「網路壞掉」,而是 URL 編碼(URL Encoding) 沒有在正確的時機、對正確的區段處理。

一、URL 編碼是什麼?為什麼要做?

URL 原本只能安全傳遞一小部分字元(英數與少數符號)。當你要在 URL 放入空白、中文、emoji、或像 &= 這種具語意的符號時,必須先把它轉成百分比編碼(Percent-Encoding),格式是 % 加上兩位十六進位值,例如空白常見為 %20

如果不編碼,URL 解析器可能把你的資料當成分隔符號,導致參數被切斷、值被誤解,甚至被惡意插入額外參數。

二、哪些字元需要編碼?

實務上可以分成三類來理解:

  • Unreserved(通常不需編碼)A-Za-z0-9-_.~
  • Reserved(有特殊語意):如 :/?#[]@&=
  • 非 ASCII 字元:中文、日文、韓文、emoji 等都要先轉成 UTF-8 再百分比編碼

重點不是「全部都編」,而是「在正確區段中,只編該編的字元」。

三、空白為什麼有時是 %20,有時是 +?

這是最常見的混淆點。一般 URL 百分比編碼中,空白常表示為 %20。但在 application/x-www-form-urlencoded(例如 HTML 表單提交)規則中,空白會轉成 +。因此你會看到同一個空白在不同情境出現兩種形式。

若系統把 + 當字面值,而不是空白,就會發生資料不一致。前後端需要先對齊編碼規格。

四、JavaScript:encodeURI 與 encodeURIComponent 差在哪?

兩者都會做編碼,但用途不同:

  • encodeURI:用在整段 URL,會保留像 :/? 這些 URL 結構符號。
  • encodeURIComponent:用在單一參數值,會連 &= 也一起編掉,避免汙染查詢字串結構。
const keyword = 'C++ 教學 & 範例';
const url = '/search?q=' + encodeURIComponent(keyword);
// /search?q=C%2B%2B%20%E6%95%99%E5%AD%B8%20%26%20%E7%AF%84%E4%BE%8B

如果你把參數值用 encodeURI 代替,& 可能被誤判成新參數分隔符。

五、常見錯誤:雙重編碼(Double Encoding)

雙重編碼通常發生在「前端先編一次,後端或 SDK 又再編一次」。例如原本的 % 會變成 %25,資料看起來就像被破壞。

  • 原始:hello world
  • 一次編碼:hello%20world
  • 二次編碼:hello%2520world

建議在系統設計文件明確定義「誰負責編碼、誰負責解碼」,避免責任重疊。

六、Path、Query、Fragment 要分開處理

URL 不同區段有不同規則。最常見的錯誤是把整段 URL 當同一字串處理,造成路徑斜線被錯誤編碼,或查詢參數漏編。

區段 建議做法 常見錯誤
Path 逐段編碼,保留路由分隔結構 把整段 /a/b 一次編碼成不可路由格式
Query 參數鍵與值分開編碼 未編碼 &= 導致參數拆裂
Fragment 依前端路由需求處理 與後端查詢參數混在一起編解碼

七、安全觀點:編碼不是防禦本身

URL 編碼只能確保傳輸格式正確,不等於防止 XSS 或 SQL Injection。它是「資料表示層」的處理,不是「輸入驗證」或「輸出逃脫」的替代品。

  • 接收端仍需做白名單驗證與長度限制。
  • 輸出到 HTML、SQL、Shell 時要用該情境的專用 escaping。
  • 不要把解碼後內容直接插入 DOM。
快速檢查清單
先確認你處理的是哪個 URL 區段,再選擇正確函式編碼。最後做一次端到端 round-trip 測試:編碼後送出,後端解析,再回傳核對原值。

結語

URL 編碼的關鍵不在記憶所有字元表,而在建立一個可預測的協作流程。當你把「區段分離、單一責任、規格對齊」做好,編碼問題會從難以追查的 bug,變成可快速定位的工程細節。