HTTPにQUERYというメソッドが追加されていました。

2026年6月に公開されたRFC 10008「The HTTP QUERY Method」です。RFC 1008ではなく、ついに5桁へ入ったほうのRFC 10008です。

名前のとおりサーバーへ問い合わせるためのメソッドで、検索条件をリクエストボディへ入れられます。

それだけならPOSTでよさそうにも見えます。実際、複雑な検索APIをPOSTで作ることは珍しくありません。

QUERYが追加された理由は、ボディを送れることだけではなく、そのリクエストが読み取り専用で、同じ内容を再送してもよいとHTTP上で明示できるところにあります。

GETで検索すると条件がURIへ集まる

単純な検索なら、GETのクエリパラメーターで困りません。

GET /feed?q=foo&limit=10&sort=-published HTTP/1.1
Host: example.org

ただ、検索条件が増えて入れ子になったり、ある程度まとまった式を渡したくなったりすると、URIへ全部詰めるのが苦しくなります。

URIの長さについて、経路上にいるすべてのクライアント、プロキシ、CDN、サーバーが共有する単純な上限があるわけではありません。長いURIをどこまで通せるかは、実際の構成に左右されます。

JSONなどの構造をURIとして扱える形へエンコードする手間もあります。また、URIはアクセスログや履歴などへ残りやすいため、検索条件に扱いづらい情報が含まれる場合もあります。

ではGETにボディを付ければよいのかというと、これも標準的な解決にはなりません。

RFC 9110のGETの定義では、GETで受け取ったボディには一般的に定義された意味がありません。一部の実装ではrequest smugglingの可能性を理由に拒否されることもあり、事前に対応が分かっているオリジンサーバーへ直接送る場合を除いて、クライアントはGETにボディを付けるべきではないとされています。

送信元とアプリケーションが対応していても、途中にある別の実装まで同じ解釈をするとは限りません。

POSTならボディを送れる

GETで扱いづらい検索をPOSTにする方法は、すでに広く使われています。

POST /feed/search HTTP/1.1
Host: example.org
Content-Type: application/json

{
  "query": "foo",
  "limit": 10,
  "sort": "-published"
}

これなら複雑な条件をボディへ入れられます。

一方でPOSTは、リソースの作成や状態変更にも使われるメソッドです。そのPOSTが実際には読み取り専用だったとしても、HTTPの一般的な仕組みからは、安全で何度でも再送できる問い合わせなのか判断できません。

接続が切れたときに自動で再試行してよいのか、レスポンスを同じ問い合わせへ再利用してよいのかといった扱いには、そのAPI固有の知識が必要になります。

QUERYは、ここをHTTPメソッドの意味として定義します。

QUERYはボディを持つ安全で冪等なメソッド

QUERYで同じ検索を書くと、次のようになります。

QUERY /feed HTTP/1.1
Host: example.org
Content-Type: application/json
Accept: application/json

{
  "query": "foo",
  "limit": 10,
  "sort": "-published"
}

問い合わせの内容はリクエストボディとContent-Typeによって決まり、対象となる範囲はリクエスト先のリソースによって決まります。

GET、QUERY、POSTの違いを大まかに並べると次のようになります。

性質GETQUERYPOST
安全はいはい保証されない
冪等はいはい保証されない
リクエストボディの意味定義されていない問い合わせとして使う対象リソース次第
レスポンスのキャッシュ可能可能制限あり

ここでいう安全とは、クライアントが対象リソースの状態変更を要求しないという意味です。アクセスログが増える、計算資源を使う、問い合わせ結果を表す一時的なリソースをサーバーが作る、といった副作用まで禁止するものではありません。

冪等であるため、応答を受け取る前に接続が切れた場合などに、クライアントや中間システムが同じQUERYを再送できます。

POSTで作った検索APIでも、アプリケーションの設計として同じことはできます。ただ、QUERYならその性質をAPI固有の約束ではなく、HTTPメソッドそのものから判断できます。

QUERYという案は急に出てきたわけではない

検索用HTTPメソッドの話自体は新しくありません。

WebDAVには、2008年のRFC 5323SEARCHメソッドが定義されています。さらに2015年には、このSEARCHを一般的なHTTP検索へ広げようとするdraft-snell-search-method-00が公開されました。

このドラフトは更新を重ねたあと、2021年にHTTP Working Groupのdraft-ietf-httpbis-safe-method-w-bodyへ引き継がれています。

最初は既存のSEARCHを使う方向でしたが、最終的には新しいQUERYという名前になりました。

RFC 10008のAppendix Bでは、既存のSEARCHPROPFINDREPORTはWebDAV由来で、リクエストの意味をXMLの内容へ持たせる設計との関係もあることが説明されています。既存メソッドの意味を広げるより、URIのquery componentとの関係も分かりやすいQUERYが選ばれました。

2015年の個人ドラフトから数えると、RFCになるまで11年ほどかかっています。突然HTTPに思いつきでメソッドが足されたという話ではありませんでした。

Content-Typeは必須

QUERYではリクエストボディが問い合わせそのものなので、Content-Typeが重要になります。

サーバーは、Content-Typeがない場合や内容と一致しない場合にリクエストを失敗させる必要があります。RFCでは、状況に応じて次のステータスコードが挙げられています。

状況ステータスコードの例
Content-Typeがない400 Bad Request
問い合わせ形式に対応していない415 Unsupported Media Type
形式は正しいが内容を処理できない422 Unprocessable Content
要求されたレスポンス形式を返せない406 Not Acceptable

サーバーは新しいAccept-Queryレスポンスヘッダーを使って、受け付けられる問い合わせ形式を通知できます。

Accept-Query: "application/jsonpath", application/sql;charset="UTF-8"

Allow: GET, QUERY, OPTIONS, HEADでQUERYメソッド自体への対応を示し、Accept-Queryでボディに使えるメディアタイプを示す形です。

キャッシュキーにはボディも必要になる

QUERYのレスポンスはキャッシュできます。

ただし、URIが同じでもボディが違えば別の問い合わせです。

QUERY /users

{"country":"JP"}
QUERY /users

{"country":"US"}

そのため、QUERYに対応するキャッシュは、URIだけではなくリクエストボディと関連するメタデータもキャッシュキーへ含める必要があります。

JSONの空白やプロパティの順序など、問い合わせとしては同じでもバイト列が違うケースを正規化することも認められています。ただし、意味を間違えて正規化すると異なる問い合わせへ同じ結果を返してしまいます。

GETよりキャッシュ処理が面倒なのは間違いなさそうです。

問い合わせや結果にURIを付けられる

RFC 10008には、QUERYを実行したあとでGETへ移れる仕組みもあります。

HTTP/1.1 200 OK
Content-Type: application/json
Content-Location: /contacts/stored-results/17
Location: /contacts/stored-queries/42

Content-Locationは、今回得られた結果を後から取得するURIです。

Locationは、同じ問い合わせをボディなしのGETで再実行するためのURIです。元データが変われば、こちらから得られる結果も変わる可能性があります。

Content-Location
  -> 今回の結果を取得する

Location
  -> 同じ条件で現在の結果を取得する

QUERYのキャッシュ処理に対応していないクライアントや中間システムでも、一度QUERYを実行したあとは通常のGETとURIへ寄せられます。

すぐに普通のAPIで使えるか

仕様としてはIETF Standards Trackで公開され、QUERYとAccept-QueryはIANAへ登録されています。

ただ、RFCになったことと、現在使っているWebサーバー、フレームワーク、CDN、WAFがそのまま通せることは別です。未知のHTTPメソッドとして拒否したり、キャッシュがボディを含むキーを作れなかったりする可能性はあります。

ブラウザーからクロスオリジンで送る場合、QUERYはCORS safelisted methodではないためプリフライトも発生します。

RFC自身も、例に出す程度の短い問い合わせならGETを使ったほうがよいとしています。単純な一覧取得まで、すべてQUERYへ置き換えるものではありません。

現時点では、複雑な検索条件を持つAPIを新しく設計するときの選択肢が標準化された、と見るくらいがよさそうです。実際に採用するなら、クライアントからオリジンまでの経路全体でQUERYを扱えるか確認する必要があります。

GET一つからここまで来た

RFC 9110にあるHTTPの歴史を読むと、最初のHTTPはパスで指定したハイパーテキスト文書をGETするだけの単純な仕組みだったと説明されています。

HTTPは今でもステートレスです。これはサーバーがデータを保存してはいけないという意味ではなく、それぞれのリクエストの意味を単独で理解でき、同じ接続上にある過去のリクエストへ依存しないという意味です。

QUERYもこの原則を変えてはいません。問い合わせに必要な条件はボディとメタデータへ入り、リクエスト単体で意味が決まります。繰り返し使いたい問い合わせや結果にはサーバーがURIを与え、次からGETできるリソースとして表現します。

一つのGETで文書を取るところから始まったHTTPに、複雑な問い合わせをボディで渡し、その結果をキャッシュしたり別のURIから再利用したりするためのメソッドが追加されました。それでもステートレスという基本は残っています。

HTTPが使われる範囲が、文書の取得から一般的なAPIへ広がってきた時間を感じる仕様です。

まとめ

RFC 10008のQUERYは、GETとPOSTの間にあった検索APIの扱いづらさを埋めるHTTPメソッドです。

GETのように安全かつ冪等で、POSTのように問い合わせをリクエストボディへ入れられます。レスポンスのキャッシュや条件付きリクエストにも対応し、問い合わせそのものや結果へURIを割り当てる方法も定義されています。

一方で、キャッシュキーへボディを含める必要があり、既存のミドルウェアがすぐ対応できるとは限りません。

POSTで検索APIを作れば動くので、QUERYがなくて困っていたようには見えにくい問題です。ただ、読み取り専用の問い合わせであることをHTTPの外側の約束にせず、メソッドの意味として持てるようになったのはわりと大きな違いだと思います。