スペースのURLエンコード: %20 と + のどちらを使うべきか
%20 はスペース文字を表す RFC 3986 標準のパーセントエンコーディングであり、URL のパスをはじめとするほとんどの場面で使うべきものです。一方、+ 記号がスペースを表すのは、HTML フォームの送信や一部のクエリ文字列で使われる application/x-www-form-urlencoded 形式に限られます。
%20 とは何を意味するのか
%20 は、RFC 3986 で定義されているスペース文字(ASCII コード 32、16 進数で 0x20)のパーセントエンコード表現です。パスセグメント、クエリ文字列、フラグメント識別子など、URI のあらゆる部分でスペースを表すための標準的な方法です。
URL の中に %20 を見かけたら、それは単に「ここにスペースがある」という意味です。たとえば https://example.com/my%20file.pdf は「my file.pdf」という名前のリソースを指します。%20 は、すべての Web サーバー、ブラウザ、HTTP ライブラリで例外なく理解されます。
URL の中で + は何を意味するのか
スペースを表す + 記号は、HTML 仕様の中でフォームデータをエンコードするために定義された application/x-www-form-urlencoded コンテンツタイプに由来します。HTML フォームが GET メソッドで送信されると、ブラウザは生成されるクエリ文字列の中でスペースを + としてエンコードします。
たとえば、検索フォームに「hello world」と入力して送信すると、ブラウザは https://example.com/search?q=hello+world のような URL を生成します。ここでの + は「スペース」を意味しますが、それはクエリ文字列が form-urlencoded 形式を使っている場合に限られます。
注意すべき点として、+ は application/x-www-form-urlencoded 形式の外では特別な意味を持ちません。/files/my+file.pdf のような URL パスでは、+ はスペースではなく、文字どおりのプラス記号です。この場合に参照されるファイルは、文字どおり「my+file.pdf」という名前です。
決定的な違い
根本的な違いは、それぞれが属している仕様にあります。%20 は RFC 3986(URI)で定義されており、URL のあらゆる場所で機能します。一方、+ をスペースとして扱う慣習は HTML/WHATWG 仕様のフォームデータ用に定義されたもので、form-urlencoded データとして明示的に扱われるクエリ文字列でのみ機能します。
%20はパス、クエリ文字列、フラグメントなど、すべての URI コンテキストで機能する+が「スペース」を意味するのはapplication/x-www-form-urlencodedのクエリ文字列に限られる- URL パスでは、
+はスペースではなく文字どおりのプラス記号である %20は常に安全だが、+は文脈に依存する- フォームデータの中で文字どおりの
+をエンコードすると%2Bになる
どちらを使うべきか
URL をプログラムで組み立てるとき、REST API リクエストを構築するとき、パスセグメントをエンコードするとき、あるいは判断に迷うときは必ず %20 を使ってください。これは URI 内のスペースに対して普遍的に正しいエンコーディングです。
+ を使うのは、HTML フォーム送信を模したクエリ文字列を組み立てる場合や、API がこの形式を明示的に要求する場合など、意図的に application/x-www-form-urlencoded データを生成するときだけにしてください。
最近のほとんどの API やフレームワークは、クエリ文字列の中で %20 と + の両方を受け付けます。とはいえ、%20 を使うほうが移植性が高く、曖昧さを避けられます。特にクエリ文字列がフォーム送信以外のコンテキストで使われる可能性がある場合はなおさらです。
各言語でのスペースの扱い
プログラミング言語やその標準ライブラリによって、スペースのエンコードに %20 と + のどちらをデフォルトとするかは異なります。使っている言語のデフォルトの挙動を把握しておくと、思わぬ落とし穴を避けられます。
// JavaScript
encodeURIComponent('hello world'); // "hello%20world" (%20)
new URLSearchParams({q: 'hello world'}).toString(); // "q=hello+world" (+)
// クエリ文字列で %20 を強制するには:
new URLSearchParams({q: 'hello world'}).toString().replace(/\+/g, '%20');
# Python
from urllib.parse import quote, quote_plus, urlencode
quote('hello world', safe='') # "hello%20world" (%20)
quote_plus('hello world') # "hello+world" (+)
urlencode({'q': 'hello world'}) # "q=hello+world" (デフォルトは +)
# %20 を強制するには:
urlencode({'q': 'hello world'}, quote_via=quote) # "q=hello%20world"
// PHP
rawurlencode('hello world'); // "hello%20world" (%20)
urlencode('hello world'); // "hello+world" (+)
// rawurlencode は RFC 3986 に従う
// urlencode は application/x-www-form-urlencoded に従う