フロントエンドでは正しく見える URL が、バックエンドでは文字化けして届く。あるいは + や空白、マルチバイト文字を含めた途端に API が 400 を返す。こうした現象の多くはネットワーク障害ではなく、URL エンコードを誤った場所で適用していることが原因です。
1. URL エンコードとは何か
URL は本来、利用できる文字が限られています。空白、Unicode 文字、絵文字、あるいは & や = のような区切り記号を安全に運ぶには、パーセントエンコード(% + 16 進数 2 桁)が必要です。例えば空白は通常 %20 になります。
正しくエンコードしない場合、値の一部が区切り記号として解釈され、パラメータ分割や値破損が発生します。
2. どの文字をエンコードすべきか
実務では次の 3 分類で考えると整理しやすくなります。
- Unreserved(通常そのままで可):
A-Z、a-z、0-9、-、_、.、~ - Reserved(構造上の意味を持つ):
:、/、?、#、[、]、@、&、= - 非 ASCII 文字:UTF-8 化したうえでパーセントエンコードが必要
重要なのは「全部をエンコードする」ことではなく、「URL の区画ごとに必要な文字だけを処理する」ことです。
3. 空白が %20 と + の 2 種類ある理由
通常の URL では空白は %20 ですが、application/x-www-form-urlencoded(HTML フォーム送信)では空白が + として表現されます。
受信側が + を空白として扱うか、文字どおりのプラスとして扱うかが揃っていないと、同じ入力でも結果がずれます。サービス間で仕様を明文化しましょう。
4. JavaScript の encodeURI と encodeURIComponent
どちらもエンコード関数ですが用途は明確に異なります。
encodeURI:URL 全体向け。:、/、?など構造文字を保持します。encodeURIComponent:パラメータ値向け。&や=もエンコードし、クエリ構造を壊しません。
const keyword = 'C++ 入門 & 実例';
const url = '/search?q=' + encodeURIComponent(keyword);
// /search?q=C%2B%2B%20%E5%85%A5%E9%96%80%20%26%20%E5%AE%9F%E4%BE%8B
値に対して encodeURI を使うと、& が区切りとして解釈されるリスクがあります。
5. 代表的な不具合:二重エンコード
フロントエンド、SDK、API ゲートウェイなど複数層で同じ値を再エンコードすると二重エンコードが発生します。% が %25 に変換され、値が別物になります。
- 元データ:
hello world - 1 回目:
hello%20world - 2 回目:
hello%2520world
どの境界でエンコード/デコードするかを設計書で固定することが重要です。
6. Path・Query・Fragment は分けて扱う
URL の各区画はルールが異なります。URL 全体を 1 つの文字列として処理すると、ルーティング崩壊やパラメータ欠落を招きます。
| 区画 | 推奨アプローチ | よくある誤り |
|---|---|---|
| Path | セグメント単位でエンコードし、スラッシュ構造を維持 | パス全体を一括エンコードしてルート不能になる |
| Query | キーと値を個別にエンコード | 値中の & / = を未処理のまま送る |
| Fragment | フロントエンドのルーター仕様に合わせる | サーバー側のクエリ規則と混在させる |
7. セキュリティ観点:エンコードは防御そのものではない
URL エンコードはあくまで転送形式を整える技術です。XSS や SQL インジェクション対策を代替するものではありません。
- 受信後データにはバリデーションを実施する。
- 出力先(HTML/SQL/Shell)ごとに適切なエスケープを使う。
- デコード後文字列をそのまま DOM に挿入しない。
まとめ
URL エンコードの本質は文字表の暗記ではなく、責務と境界の明確化です。区画単位でルールを統一すれば、再現しにくい API 不具合を大幅に減らせます。