RFC 10008: 新しい HTTP QUERY メソッド
(rfc-editor.org)- RFC 10008 は、リクエスト本文に含まれるクエリを対象リソースが 安全かつ冪等に処理 し、その結果を返す HTTP の QUERY メソッド を定義する
- QUERY は GET の safe/idempotent な性質と POST の本文送信方式を組み合わせ、長い URI・URI エンコーディングのコスト・ログ露出・クエリの組み合わせごとにリソース化する負担を減らす
- サーバーは QUERY リクエストの Content-Type と本文が一貫していなければ処理できず、未対応の型・本文不一致・処理不能なクエリはそれぞれ異なる 4xx 応答で区別できる
- 成功応答では Content-Location にクエリ結果リソースを、Location に同じクエリを再実行する equivalent resource を示せる
- QUERY 応答はキャッシュ可能だが、キャッシュキーには本文とメタデータまで含める必要があり、CORS 環境では safelisted method ではないため preflight が必要になる
QUERY が解決しようとする HTTP クエリパターン
- RFC 10008 は HTTP の QUERY リクエストメソッド を定義する Internet Standards Track 文書
- QUERY は、対象リソースがリクエスト本文を処理し、その結果を応答するよう要求する
- POST のように本文を使うが、safe と idempotent として定義されているため、自動再試行や再開が可能
- 従来の GET クエリでは、入力を URI に入れる方式が一般的
GET /feed?q=foo&limit=10&sort=-published HTTP/1.1
- URI にクエリデータを入れると、データが大きくなるほど制約が大きくなる
- 複数の独立したシステムを経由するため、実際の URI サイズ制限を事前に知るのは難しい
- HTTP は送信者と受信者が最低 8000 octets をサポートすることを推奨するが、経路上のすべてのシステムを保証するものではない
- 一部のデータは有効な URI にエンコードするコストが高い
- リクエスト URI は、リクエスト本文よりもログに残ったりブックマークに含まれたりする可能性が高い
- クエリを URI に直接エンコードすると、可能な入力の組み合わせごとに別個のリソースとして扱われる
GET と POST の間の意味を明確にするメソッド
- 多くの実装は、GET の代わりに POST 本文でクエリを渡している
POST /feedContent-Type: application/x-www-form-urlencoded- 本文:
q=foo&limit=10&sort=-published
- この方式では、特定のリソースとサーバー知識がなければ、安全で冪等なクエリかどうかが分からない
- QUERY は同じ入力をリクエスト本文として送りつつ、メソッド自体が安全かつ冪等
QUERY /feedContent-Type: application/x-www-form-urlencoded- 本文:
q=foo&limit=10&sort=-published
- この明示的な意味づけにより、キャッシュや自動再試行のような HTTP 機能を適用しやすくなる
- サーバーはクエリ自体、または特定のクエリ結果に URI を割り当て、後続の GET リクエストで使えるようにできる
QUERY メソッドの中核ルール
- QUERY はサーバー側クエリを開始するために使われる
- GET は対象 URI が識別するリソースの表現を要求するが、QUERY は対象リソースの範囲内で クエリ処理 を実行するよう要求する
- リクエスト本文とメディア型がクエリを定義し、origin server が対象リソースを基準に処理範囲を定める
- サーバーは Content-Type リクエストフィールドがない、またはリクエスト本文と一致しない場合、リクエストを失敗させなければならない
- 対象 URI の query part は、他の HTTP メソッドと同様にクエリ対象リソースの識別に関与する
- その query part が結果に直接影響するかどうか、またどう影響するかはリソースごとの動作であり、この仕様の範囲外
- QUERY は対象リソースの観点で安全
- クライアントは対象リソースの状態変更を要求も期待もしない
- サーバーが追加情報を参照できる HTTP リソースを作ることは禁止されていない
- QUERY は冪等なので、接続障害後に必要なら再試行や繰り返しが可能
200 OK応答は、クエリが正常に処理され、結果が応答本文に含まれていることを示す
メディア型、ネゴシエーション、エラー処理
- QUERY リクエストの意味は、リクエスト本文と メディア型 のような関連メタデータによって変わる
- 本文とメタデータが整合しないリクエストは、一般に 4xx Client Error で拒否すべき
- エラー処理は、リクエストのどこが誤っているかによって異なる
- メディア型情報がなければ、定義上不正なリクエストなので
400のような 4xx ステータスコードで失敗させるべき - メディア型が指定されていてもリソースがサポートしなければ、
415 Unsupported Media Typeが適切 - メディア型自体は一般に知られていても、対象リソースにその QUERY の意味がなければ
415の対象になる - メディア型が実際のリクエスト本文と一致しなければ、
400 Bad Requestを返せる - サーバーは本文を見てメディア型を推測し、欠落または誤った値を上書きする content sniffing をしてはならない
- 型と本文は一致していても、実際のクエリ内容のために処理できないなら
422 Unprocessable Contentを使える - 構文的には正しい SQL クエリが存在しないテーブルを指すケースが
422の例 - クライアントが
Acceptで要求した応答メディア型をリソースがサポートしないなら、406 Not Acceptableが適切
- メディア型情報がなければ、定義上不正なリクエストなので
Accept-Query応答フィールドは、クライアントにサポートされるクエリメディア型を知らせられる
equivalent resource、Content-Location、Location
- equivalent resource は、特定の QUERY リクエストとその対象を表し、GET リクエストに応答するリソース
- equivalent resource は、リクエスト本文とメタデータの両方を考慮する
- 本文のメディア型のような representation metadata が含まれる
- サーバーは equivalent resource に URI を割り当てられるが、必須ではない
- 成功応答では Content-Location ヘッダーに、クエリ結果に対応するリソース識別子を含められる
- クライアントは示された URI に GET を送って、今実行したクエリ処理の結果を取得できる
- そのリソースは一時的な場合がある
- 成功応答では Location ヘッダーに、QUERY リクエストの equivalent resource URI を含められる
- クライアントはクエリ本文を再送せずに、示された URI に GET を送って同じクエリ処理を繰り返せる
- この URI も一時的な場合がある
- 後続リクエストが失敗した場合、クライアントは元の QUERY 対象と以前送信した本文で再試行できる
リダイレクトと条件付きリクエスト
- サーバーは QUERY リクエストに対し、別の URI へユーザーエージェントをリダイレクトする間接応答を選べる
301 Moved Permanentlyと308 Permanent Redirectは、対象リソースが Location の指す別 URI へ恒久的に移動したことを示す302 Foundと307 Temporary Redirectは、対象リソースの一時的な移動を意味する- 4 つのケースすべてで、サーバーは新しい対象 URI へ同様の QUERY リクエスト を送れば元のリクエストを実行できると示唆する
301または302の後に POST を GET へリダイレクトする例外は、QUERY リクエストには適用されない- QUERY に対する
303 See Otherは、元のクエリを Location の指す URI への通常の取得リクエストとして実行できることを示す- HTTP では新しい対象 URI に GET リクエストを送る
- 条件付き QUERY における selected representation は、その QUERY リクエストの equivalent resource に対する GET と同じ
- クライアントは、条件付きヘッダーが指定した条件のもとでのみクエリ結果を応答として返すよう要求できる
キャッシュと Range リクエスト
- QUERY メソッドの応答はキャッシュ可能で、キャッシュは後続の QUERY リクエストを満たすために利用できる
- QUERY リクエストの キャッシュキー には、リクエスト本文と関連メタデータを含めなければならない
- キャッシュは、キャッシュキー生成のために意味上重要でない差異を除去できる
- content encoding の除去
+jsonのようなメディア subtype suffix が示す形式慣習に基づく正規化Content-Typeが示す本文の意味に応じた正規化
- これらの変換はキャッシュキー生成のためだけのものであり、リクエスト自体は変更しない
- クライアントは
no-transformキャッシュディレクティブでこのような変換を望まないと示せるが、このディレクティブは advisory - QUERY 応答のキャッシュは GET より本質的に複雑
- キャッシュキーを決めるにはリクエスト本文全体を読む必要がある
- QUERY 応答が Location で equivalent resource URI を提供すれば、クライアントはその後 GET に切り替えて処理を単純化できる
- QUERY の Range Request の意味は GET と同じ
- 執筆時点で定義されている唯一の range unit である Byte Range Request は、QUERY 結果には価値が小さい
- SQL の
FETCH FIRST ... ROWS ONLYのように、クエリ形式自体が結果制限やページネーションを提供することが多く、そのような組み込み機能の利用が想定される
Accept-Query 応答ヘッダー
- Accept-Query 応答ヘッダーは、リソースが QUERY メソッドをサポートすることを直接示し、利用可能なクエリ形式メディア型を識別する
- Accept-Query は Structured Fields 構文を使う media range の一覧
- media range は、パラメータなしの media range 値を含む Token または String の List Structured Header Field として表現される
- メディア型パラメータは Structured Field Parameters にマッピングされる
- String と Token の選択は意味的に重要ではない
- 受信者は Token を String に変換できるが、受け取った型によって異なる処理をしてはならない
- メディア型は Token に正確にはマッピングされず、先頭数字を許可する場合は String 形式を使う必要がある
- サポートされるワイルドカードは
*/*またはxxxx/*のみ - フィールド値に列挙された型の順序は重要ではない
- Accept-Query 値は、同じ path を共有するサーバー上のすべての URI に適用され、query component は無視される
- 同じリソースへの要求が異なる Accept-Query 値を返した場合、直近に受け取った fresh な値が使われる
- 例は次のとおり
Accept-Query: "application/jsonpath", application/sql;charset="UTF-8"
- Accept-Query は
Acceptに似て見えるが、Structured Field であるため RFC 9651 の Structured Fields 処理規則に従う必要がある
セキュリティ考慮事項と CORS
- QUERY は RFC 9110 で定義されるすべての HTTP メソッドの一般的なセキュリティ考慮事項に従う
- QUERY は、リクエスト情報を URI query component に入れる方式の代替として使える
- URI はリクエスト本文よりもログに残ったり中継者に処理されたりする可能性が高いため、機微情報を含むクエリでは GET より QUERY を使う動機になり得る
- サーバーが QUERY 結果を表す一時リソースを作成して URI を割り当てる際、元のリクエスト本文にログへ残してはならない機微情報があるなら、その URI に機微な部分を含めてはならない
- キャッシュが QUERY 本文を不適切に正規化したり、リソース処理方式と大きく異なる形で正規化したりすると、false positive により誤った応答を返す可能性がある
- CORS を実装するユーザーエージェントの QUERY リクエストでは preflight リクエストが必要
- QUERY は CORS-safelisted methods の集合に含まれない
IANA 登録とメソッド名の選定
- IANA は QUERY メソッドを HTTP Method Registry に追加する
- Method Name:
QUERY - Safe:
yes - Idempotent:
yes - Specification: RFC 10008 Section 2
- Method Name:
- IANA は Accept-Query フィールドを HTTP Field Name Registry に追加する
- Field Name:
Accept-Query - Status:
permanent - Structured Type:
List
- Field Name:
- HTTP Method Registry にはすでに safe と idempotent の属性を持つ
PROPFIND、REPORT、SEARCHが存在していた - 初期段階では
SEARCHが使われていたが、最終的なメソッド名は QUERY になった - QUERY が選ばれた理由は次のとおり
- 代替案はいずれもリクエスト本文に一般的なメディア型
application/xmlを使い、リクエスト意味が全面的に本文へ依存していた - 代替案はいずれも WebDAV の活動に由来していた
QUERYは URI の query component との関係をうまく捉えている
- 代替案はいずれもリクエスト本文に一般的なメディア型
1件のコメント
Hacker Newsのコメント
強い動機づけの例があればもっと説得力があったはずだが、GETであまりに簡単に表現できる例を使っていて、かえって話が散漫になっている
大きなJSONフィルタ構造や画像入力をリクエスト本文に入れるQUERYを想像してみても、リクエスト本文がキャッシュキーの一部になるというのはかなり奇妙に感じる。ユーザー制御の無限のキャッシュキーが生まれ、一般的なキャッシュ戦略は実質的にリクエスト本文をビット単位で比較するかハッシュするしかないので、悪意ある状況ではキャッシュ無効化が非常に容易になる
複雑なフィルタリングや画像のような複雑な入力が必要なサービスを作るなら、キャッシュはHTTP層から遠く離れた場所にある可能性が高い。たとえば結合の個別データ列や、デコード済み画像入力の知覚ハッシュでキー付けされた埋め込みのように、ワイヤ上の正確なビット表現とは無関係になるはずだ
こういうものをなぜわざわざ汎用的な方式で捉えようとするのか分からない。むしろPOSTに
"Vary: request-body"のような新しいヘッダーでキャッシュ意味論を表現するほうがはるかに良いと思う。完全な下位互換になり、この動作が有用かもしれない0.1%のCDN用途以外では無視してもよいキャッシュ関連の攻撃で、クエリパラメータにも同じように当てはまらないものは特に思い浮かばない。キャッシュを溢れさせたいなら、一意な30文字のクエリパラメータを作るのも30MBのリクエスト本文を作るのと同じくらい簡単だ
現実的には公開インターネット向けシステムはセキュアハッシュをキャッシュキーに使って常に同じサイズにするだろう。キャッシュキーには、すでに非常に長くなり得るURLや任意のヘッダー値の集合も含まれている
クエリパラメータ付きGETもすでに不透明で、キャッシュ無効化を容易にしている
だが、QUERYの後にPOSTを置けば改善しそうだ。単に安全フラグが付いた同じリクエストではなく、異なるリクエスト種別になるからだ
HTMLフォームがQUERYサポートを追加するのか気になる
QUERYは冪等であるべきなので、POSTフォーム送信の結果ページを再読み込みしたときに出る厄介な再送信警告を避けられる
method=QUERYが追加されれば、こうした奇妙さの新しい変種が1つ増えるまだ前世紀のままでいたい人のために: https://www.rfc-editor.org/rfc/rfc10008.txt
「GETリクエストに本文を付ける案はIETF作業部会で深く検討されたが、最終的には新しいQUERYメソッドを作る方向に決まった。別メソッドを作るという決定は、歴史的な相互運用性の問題とHTTPの中核アーキテクチャ定義への厳格な準拠によるものだった」
でも私はここ数年ずっとGETメソッドにリクエスト本文を付けて送っている
fetchがそうだhttps://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/U...
「GETリクエストには本文を含められない」
透過キャッシュのために妙な問題が起きる可能性もある
ついに5桁のRFC番号に到達したのが驚きだ
誰かがRFC 10000がいつ発行されるかについて曖昧な賭けをしていたが、番号は9998からそのまま10008に飛んだ。誰も勝てなかった
https://manifold.markets/CollectedOverSpread/when-will-rfc-1...
HTTPの問い合わせで検索結果を問い合わせるにはQUERYメソッドを使い、クエリパラメータは追加するな、という感じになる
名前が紛らわしい。
queryという用語がすでにHTTPリクエスト一般を指すのに使われているからだRFCのタイトルを見ただけでも混乱した
queryという用語がHTTPリクエスト一般を指すのは、どの分野の話だろう? 口語でGETリクエストを問い合わせと呼ぶことはあっても、POST、PUT、DELETEをそう呼ぶことは絶対にない修正: ああ、キャッシュ可能性のためにQUERYを副作用のない「安全な」メソッドとして宣言したのか。勘違いしていた
これが実際にクエリ文字列付きGETリクエストを現場で置き換えるようになるなら、ブラウザのブックマークがリクエストパラメータの保持をサポートしてほしい
このRFCの範囲外なのは分かっているが、これを簡単に拡張すればJSの
EventSourceがストリーミングAI問い合わせでも動くようにできる点がよいリクエストに本文が必要なので皆POSTを使い、ストリーミング結果にはレスポンスで
text/event-streamプロトコルをよく使う。だが実際には状態が変わるわけではないので技術的にはしっくりこず、EventSourceは頑固にもGETしか使えない。だから多くのAPIが独自パーサーで同じ機能を再実装しているGET: Content (body) "no defined semantics"を見て、GETメソッドで本文を許可しても悪くないと思っていたが、元の仕様ではGET本文は完全に無視されるべきだったまた、リクエストの重要な部分が削除される本文に入るとキャッシュも壊れ得る
GETに本文パラメータを追加すると、同じURIを使う2つのリクエストを同じものを指すと見なせなくなり、このメソッドの制約を壊してしまう