API呼び出しは正しいように見えました。エンドポイントは一致し、ヘッダーも正しいものでしたが、応答は400 Bad Requestでした。20分間、それを眺め続けた後、問題を見つけました。クエリ文字列に含まれていたメールアドレスに「」が含まれていました。 + サーバーはそれをスペースとして解釈しました。これはURLエンコードの動作であり、問題が実際にデバッグするのが非常に難しい方法です。
このガイドでは、パーセントエンコードが実際に何をやっているか、どの文脈でどの文字をエンコードすべきか、JavaScriptでよく見られる罠、そしてURLエンコーダーのようなツールを使って自分の作業をチェックする方法について説明します。 URLエンコーダー/デコーダー を使用してください。
URLエンコードが実際に何をやっているか
URLにはASCII文字の一部しか含まれることができません。それ以外のもの——スペース、国際文字、URLで特別な意味を持つ記号——は送信前に安全なフォーマットに変換される必要があります。
パーセントエンコードは、それぞれの危険なバイトをパーセント記号と2桁の16進数で置き換えることでこれを実現します。スペースは %20、ハッシュは %23、フォワードスラッシュは %2Fになります。名前はその先頭のパーセント記号から来ています。
規格(RFC 3986)では、「未制限文字」と呼ばれる文字がエンコードを必要としないと定義されています。それはアルファベット(A-Z、a-z)、数字(0-9)および4つの記号です。 - _ . ~。それ以外のすべては、構造を区切り用に使う「制限文字」またはエンコードする必要があります。
APIを壊す文字
実際の問題で最も被害を与えるものリストです。
| 文字 | エンコード済み | エンコードが必要な文脈 | 注記 |
|---|---|---|---|
| 空間 | %20 | すべての文脈 | もしくは + フォームデータにおいて——以下を参照 |
| & | %26 | クエリ文字列の値 | クエリパラメータを分離する;値の中ではエンコードする必要があります |
| = | クエリ文字列の値 | キーと値を分離する;値自体にエンコードする必要があります | |
| + | クエリ文字列の値 | フォームエンコードでスペースとして解釈される—— %2B で、プラス記号を文字通りに表します |
|
| # | %23 | パス、クエリ文字列 | フラグメントをマークする;それ以降のすべてはサーバーに送られない |
| ? | パスセグメント、クエリ値 | クエリ文字列を開始する;パスまたは値にエンコードする必要があります | |
| / | パスセグメント(文字通り) | パスセグメントを分離する;セグメント値の中に入れてエンコードする必要があります | |
| @ | %40 | クエリ文字列の値 | クエリパラメータ内のメールアドレスはこのようにエンコードする必要があります |
3つの文脈、異なるルール
エンコードする部分がURLの構造を変えるため、何をエスケープする必要があるかが変わります。
完全なURL — 完全なURLを渡す場合、構造を維持したいので、スラッシュ、質問マーク、ハッシュ記号はそのまま残します。許容されない文字だけがエンコードされます。
クエリ文字列の値 — これは多くのAPIのバグが生じる場所です。クエリ文字列内の各値は、構造を形成するための文字(&, =, #, +)が値に字面として現れないようにエンコードされる必要があります。ユーザー名が「John & Jane」の場合、クエリ文字列は name=John%20%26%20Jane、ではなく name=John & Jane (サーバーはこれで2つのパラメータとして解析します)。
パスセグメント — パスセグメントはスラッシュの間の部分です。セグメントにスラッシュが含まれている場合(たとえば、ファイル名にスラッシュがある場合)、それを %2Fとしてエンコードする必要があります。一部のサーバーはパス内の %2F をセキュリティ上の懸念として扱います;バックエンドを理解してから使用してください。
encodeURI と encodeURIComponent — JavaScriptの罠
JavaScriptには2つの組み込みエンコード関数があります。間違った関数を使うことは非常に一般的なバグです。
// encodeURI — encodes a full URL
// Preserves: : / ? # [ ] @ ! $ & ' ( ) * + , ; =
encodeURI("https://example.com/search?q=hello world&lang=en")
// → "https://example.com/search?q=hello%20world&lang=en"
// Note: & and = are NOT encoded — the query structure is preserved
// encodeURIComponent — encodes a single value
// Encodes everything except: A-Z a-z 0-9 - _ . ! ~ * ' ( )
encodeURIComponent("hello world&lang=en")
// → "hello%20world%26lang%3Den"
// Note: & and = ARE encoded — safe to use as a query value
// The bug: using encodeURI on a value
encodeURI("hello world&lang=en")
// → "hello%20world&lang=en" ← & survives! Server sees two parameters.
// The correct approach for building query strings
const name = "John & Jane"
const email = "john+jane@example.com"
const url = `https://api.example.com/users?name=${encodeURIComponent(name)}&email=${encodeURIComponent(email)}`
// → "https://api.example.com/users?name=John%20%26%20Jane&email=john%2Bjane%40example.com"
ルール:個々の値に encodeURIComponent を使用してURLに組み込む前にエンコードしてください。完全なURLをもつ場合で構造を破らないようにするには、 encodeURI のみを使用してください。
プラス記号:フォームエンコードとパーセントエンコード
HTMLフォームが method="GET"を送信する場合、ブラウザはデータを application/x-www-form-urlencodedでエンコードします。このフォーマットでは、スペースは + の代わりに %20に変換されます。多くのサーバーフレームワーク(PHP、Django、Rails)はクエリ文字列内で + をスペースとして自動的に解釈します。
値にプラス記号が実際に含まれている場合(例:電話番号 +44 7700 900000)に問題が生じます。もしそれを +44...として渡すと、サーバーは先頭のプラスをスペースとして解釈し、 44...を得ます。解決策は、文字通りのプラス記号を %2Bとしてエンコードすることです。これにより、どちらの解釈スキームでもそのまま保存されます。
現代のAPI作業では、スペースを %20 ( encodeURIComponent が生成するもの)として使用し、 +.
に頼らないようにしてください。
二重エンコード:エンコードが間違った場合 %20 二重エンコードは、すでにエンコードされたものに再度エンコードするときに起こります。 %2520 自身が %25 にエンコードされ、サーバーは %20 を文字通りのパーセントとして解釈し、スペースではなく
を取得します。
- これは次のケースで現れます:
- データベースにURLを保存し、それを使用する前に再度エンコードする
- フレームワークまたはライブラリがすでに手動でエンコードした値をエンコードする
パラメータとして別のURLを含むURLを作成する decodeURIComponentこれを避けるには、URLを構成するときにちょうど1回エンコードしてください。値がすでにエンコードされているかどうかわからない場合は、まず
で解釈し、その後、きれいに再エンコードしてください。
開発ツールでURLエンコードをデバッグする
APIリクエストが不正に動作する場合、開発ツール(F12)を開き、ネットワークタブに移動し、失敗したリクエストをクリックします。ヘッダーの下で、リクエストURLを見つけて、ブラウザはそのエンコードされた形式で表示します。パラメータの下では、クエリパラメータが元の値に戻された形で表示され、アンパースンドが分離されたか、または文字通りに渡されたかを簡単に確認できます。 IO Tools URL エンコーダー/デコーダー 両方向を処理し、出力がすぐに表示されます。
あなたも好きかもしれません
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
