Contents

有關 URL 空白字符(%20 和 +)

最近在測試 JavaScript 與 API 參數時,才重新注意到一個很容易被忽略的小地方:同樣是空白,為什麼有時候會看到 %20,有時候卻是 +?而且如果字串本來就有加號,又要怎麼保證它不會被當成空白?

這個問題看起來很小,但它其實很常影響 query string、表單提交、搜尋參數,甚至 Base64 字串傳輸。這篇就把這件事整理清楚。

先講結論

簡單版可以先記這 3 件事:

  • %20 代表空白,屬於一般 URL percent-encoding 的表達方式。
  • +application/x-www-form-urlencoded 的脈絡裡,也常被解讀成空白。
  • %2B 才是字面上的加號 +

例如:

1
2
3
hello world  -> hello%20world
hello world  -> hello+world
hello+world  -> hello%2Bworld

為什麼會有兩種寫法

這跟「你現在處理的是哪一種編碼規則」有關。

1. 一般 URI / URL percent-encoding

在一般 URL 編碼規則裡,空白通常會被編成 %20。這是比較通用、比較直覺的表示方式。

例如:

1
http://httpbin.org/get?test=123%20123

這裡的 %20 就是空白。

2. HTML 表單的 form encoding

如果是 HTML 表單送出,尤其是 application/x-www-form-urlencoded 這種格式,空白常會被轉成 +。這是比較舊、但到現在仍然大量存在的規則。

也就是說,+ 不是在所有 URL 場景都等於空白,而是在特定表單編碼規則中,伺服器通常會把它解讀成空白。

最容易搞錯的地方

路徑 path 跟查詢參數 query string 不要混在一起看

下面兩種情境不能混為一談:

  1. URL path,例如 /files/my report.pdf
  2. query string,例如 ?keyword=my report

在 path 裡,通常要用 %20;如果你直接放 +,很多情況它就只是字面上的加號,不一定會被當成空白。

但在 query string,尤其是走表單編碼時,+ 就可能被解讀為空白。

所以不要只記「加號等於空白」,這樣很容易在錯的地方用錯規則。

JavaScript 實際測試

encodeURIencodeURIComponent

1
2
3
4
5
6
7
8
encodeURI('https://example.com?q=hello world')
// https://example.com?q=hello%20world

encodeURIComponent('hello world')
// hello%20world

encodeURIComponent('a+b')
// a%2Bb

如果你是自己組 query string,通常比較建議針對參數值用 encodeURIComponent,不要手動字串拼一拼就上。

URLSearchParams

1
2
3
const params = new URLSearchParams({ keyword: 'hello world', token: 'a+b' });
console.log(params.toString());
// keyword=hello+world&token=a%2Bb

這裡就很能看出差異:

  • 空白被轉成 +
  • 加號被轉成 %2B

這其實就是表單式 query string 的典型行為。

Java / 後端也常遇到

像 Java 的 URLEncoder 也很常讓人誤會:

1
2
3
String value = URLEncoder.encode("hello world", StandardCharsets.UTF_8);
System.out.println(value);
// hello+world

很多人看到這裡會以為 Java 編錯了,其實不是,它是在做偏向 application/x-www-form-urlencoded 的編碼行為。

如果你預期的是純 URI component 的 %20 形式,就要知道工具的語意是否和你要的情境一致。

常見踩雷情境

1. Base64 或 token 裡有 +

如果一段資料本身含有 +,又沒有正確 encode,送到後端後就可能被當成空白,最後驗證失敗。

這也是為什麼很多 token、簽章或 Base64 值放進 URL 時,一定要先 encode。

2. 前端手動拼 query string

像這樣:

1
const url = '/search?keyword=' + keyword;

如果 keyword 有空白、加號、&=,就很容易出事。這種場景應該優先用 URLSearchParams 或至少 encodeURIComponent

3. 後端框架已經幫你 decode 過一次

有些框架在拿到參數時就已經幫你把 + 還原成空白,這時你再手動 decode 一次,反而可能把資料處理壞。

我自己的判斷方式

如果是一般 URL component,我腦中先預設空白應該是 %20

如果看到 +,我會先懷疑目前處理的是:

  • HTML form submission
  • application/x-www-form-urlencoded
  • 某個工具或函式庫幫我用 form 規則編碼

而如果資料裡本來就需要保留加號,那我會特別確認它是不是被轉成 %2B

小結

%20+ 雖然都常代表空白,但它們來自不同脈絡:

  • %20 比較接近通用 URL percent-encoding。
  • + 比較常見於表單編碼與 query string 處理。

真正容易出 bug 的不是「不知道有這兩種寫法」,而是沒有分清楚 path、 query string、表單提交、 token 傳遞這些場景的規則差異。

參考資料: