0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HTTPに新メソッド「QUERY」が登場 — GETとPOSTの間を埋めるRFC 10008を理解する

0
Posted at

はじめに

HTTPの代表的なメソッドといえば、GETPOSTPUTDELETEなどです。

ここに2026年6月、新しい標準メソッド QUERY が加わりました。IETFのStandards Track文書であるRFC 10008: The HTTP QUERY Methodとして公開され、IANAのHTTP Method Registryにも登録されています。

QUERYを一言で表すと、次のようになります。

検索条件などをリクエストBodyで送りつつ、安全かつ冪等に問い合わせるためのHTTPメソッド

この記事では、なぜGETPOSTだけでは不十分だったのか、QUERYは何を解決するのか、実際にどのように利用するのかを解説します。

この記事は2026年7月時点の仕様を基にしています。QUERY自体は標準化済みですが、サーバー、フレームワーク、プロキシ、CDN、WAFなどの対応状況はそれぞれ異なります。

QUERYメソッドとは

QUERYは、リクエスト先のリソースに対して、Bodyに記述した問い合わせを処理し、その結果を返すよう求めるメソッドです。

基本形は次のようになります。

QUERY /products/search HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json

{
  "category": "book",
  "price": {
    "min": 1000,
    "max": 3000
  },
  "keywords": ["HTTP", "API"],
  "sort": "-publishedAt"
}

レスポンスは通常のHTTPレスポンスです。

HTTP/1.1 200 OK
Content-Type: application/json

{
  "items": [
    {
      "id": 123,
      "title": "HTTP API Design"
    }
  ]
}

リクエストのContent-Typeは必須です。サーバーは、Content-Typeが存在しない場合や、実際のBodyと矛盾している場合にリクエストを失敗させなければなりません。

なぜQUERYが必要になったのか

GETではURLが複雑になる

検索APIでは、次のようにGETのクエリパラメータへ条件を含める設計が一般的です。

GET /products?category=book&minPrice=1000&maxPrice=3000&keyword=HTTP&keyword=API&sort=-publishedAt

条件が少なければ、この方法はシンプルで分かりやすいものです。しかし、検索条件が複雑になると次の問題が生じます。

  • URLが長くなり、途中のサーバーやプロキシの上限に達する可能性がある
  • JSONのような入れ子構造や配列を表現しにくい
  • URLエンコードによって可読性が低下する
  • URLはアクセスログ、ブラウザ履歴、ブックマークなどに残りやすい

RFC 10008は、HTTPの送信者と受信者に少なくとも8,000オクテットのURIを扱えることが推奨されている一方、実際の通信経路に存在するすべてのシステムの制限を事前に把握することは難しいと説明しています。

GETのBodyには定義された意味がない

「それならGETにBodyを付ければよいのでは」と思うかもしれません。

しかし、HTTP Semanticsを定めるRFC 9110では、GETリクエストのBodyに一般的な意味は定義されていません。クライアント、プロキシ、キャッシュ、サーバーフレームワークがBodyを拒否したり無視したりする可能性があります。

つまり、GETのBodyをAPI独自ルールとして利用することはできても、HTTP全体で相互運用できる設計にはなりにくいという問題があります。

POSTでは「安全な問い合わせ」だと伝わらない

複雑な検索APIでは、これまでPOSTがよく使われてきました。

POST /products/search HTTP/1.1
Content-Type: application/json

{
  "category": "book",
  "keywords": ["HTTP", "API"]
}

技術的には実用的ですが、POSTはリソースの作成や状態変更にも使われます。HTTPの仕組みから見ただけでは、そのリクエストが「何度実行しても安全な検索」なのか、「注文の確定のように状態を変更する処理」なのかを判別できません。

そのため、通信失敗時の自動再試行やキャッシュなどを汎用的な仕組みとして適用しにくくなります。

QUERYは、このGETPOSTの間にあった空白を埋めます。

GET・QUERY・POSTの違い

特性 GET QUERY POST
Safe(安全) はい はい 保証されない
Idempotent(冪等) はい はい 保証されない
リクエストBody 一般的な意味は未定義 問い合わせ内容として想定 処理内容として想定
レスポンスのキャッシュ 可能 可能 明示的な条件の下で可能。ただし後続のGET/HEAD用
主な用途 URIで特定した表現の取得 Bodyで条件を渡す問い合わせ 作成・実行・状態変更など

Safe(安全)とは

HTTPで「安全」とは、クライアントが対象リソースの状態変更を要求・期待していないことを意味します。

したがって、QUERYで次のような処理を行うべきではありません。

  • 注文を確定する
  • ユーザー情報を更新する
  • ポイントを消費する
  • データを削除する

一方、アクセスログの記録、メトリクスの更新、キャッシュの生成など、リクエスト処理に付随する内部動作まで禁止されるわけではありません。

Idempotent(冪等)とは

同じQUERYを複数回送っても、1回だけ送った場合と比べて、対象リソースに意図される効果が変わらないことを意味します。

これは「必ず同じレスポンスが返る」という意味ではありません。問い合わせ対象のデータが更新されれば、同じ検索条件でも結果は変わります。

安全かつ冪等であるため、接続が途中で切れた場合などに、クライアントや中間システムがQUERYを再試行しやすくなります。

QUERYの重要な仕様

1. 問い合わせ形式はContent-Typeで示す

QUERYのBodyはJSONに限定されません。問い合わせ形式をメディアタイプによって示します。

Content-Type: application/json
Content-Type: application/sql
Content-Type: application/x-www-form-urlencoded

どの形式をどのような意味で処理するかは、対象リソース側が定義します。application/jsonと書いただけで、共通の検索構文が自動的に決まるわけではありません。

2. Accept-Queryで対応形式を通知できる

サーバーはAccept-Queryレスポンスヘッダーを使い、利用できる問い合わせ形式を通知できます。

Accept-Query: application/json, application/sql

対応メソッド自体はOPTIONSで確認できます。

OPTIONS /products/search HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
Allow: GET, QUERY, OPTIONS, HEAD
Accept-Query: application/json

サポートしていないメソッドには405 Method Not Allowed、サポートしていない問い合わせ形式には415 Unsupported Media Typeを返す設計が考えられます。

3. QUERYのレスポンスはキャッシュできる

QUERYのレスポンスはキャッシュ可能です。ただし、キャッシュキーにはURIだけでなく、リクエストBodyと関連するメタデータを組み込む必要があります。

たとえば次の2つは、同じURIに対するリクエストでも別の問い合わせです。

{ "category": "book" }
{ "category": "game" }

Bodyを無視して同じキャッシュキーにすると、異なる検索結果を返してしまいます。

また、JSONの空白やプロパティ順など、意味に影響しない差を正規化してキャッシュ効率を高めることも仕様上は可能です。ただし、サーバーとキャッシュで正規化の解釈が異なると誤った結果を返す危険があります。

4. 問い合わせや結果にURIを割り当てられる

サーバーは、LocationContent-Locationを使って、問い合わせや検索結果を後からGETできるURIとして提示できます。

HTTP/1.1 200 OK
Content-Type: application/json
Location: /stored-queries/42
Content-Location: /stored-results/17
  • Location: 同じ問い合わせを表すリソース。後からGETすると、その時点での結果を取得できる
  • Content-Location: 今回返された結果を表すリソース

これらのURIは一時的なものでも構いません。

実際に送信する

curl

curlでは任意のメソッドを-Xで指定できます。

curl -X QUERY 'https://api.example.com/products/search' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  --data '{
    "category": "book",
    "keywords": ["HTTP", "API"],
    "limit": 10
  }'

JavaScriptのfetch

const response = await fetch('https://api.example.com/products/search', {
  method: 'QUERY',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
  body: JSON.stringify({
    category: 'book',
    keywords: ['HTTP', 'API'],
    limit: 10,
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

const result = await response.json();
console.log(result);

ブラウザからオリジンをまたいで送信する場合、QUERYはCORS-safelisted methodではないため、プリフライトリクエストが発生します。サーバー側では、たとえば次のような許可が必要です。

Access-Control-Allow-Methods: GET, QUERY, OPTIONS

Goで受け取る最小例

フレームワークがQUERY専用のルーティングAPIを提供していなくても、メソッド文字列を判定できれば処理できます。

package main

import (
	"encoding/json"
	"mime"
	"net/http"
)

type SearchCondition struct {
	Category string   `json:"category"`
	Keywords []string `json:"keywords"`
	Limit    int      `json:"limit"`
}

func searchHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Accept-Query", "application/json")

	if r.Method == http.MethodOptions {
		w.Header().Set("Allow", "QUERY, OPTIONS")
		w.WriteHeader(http.StatusNoContent)
		return
	}

	if r.Method != "QUERY" {
		w.Header().Set("Allow", "QUERY, OPTIONS")
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
	if err != nil || mediaType != "application/json" {
		http.Error(w, "unsupported media type", http.StatusUnsupportedMediaType)
		return
	}

	var condition SearchCondition
	if err := json.NewDecoder(r.Body).Decode(&condition); err != nil {
		http.Error(w, "invalid JSON", http.StatusBadRequest)
		return
	}

	// conditionを使って読み取り専用の検索を行う
	result := map[string]any{
		"condition": condition,
		"items":     []any{},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(result)
}

func main() {
	http.HandleFunc("/products/search", searchHandler)
	http.ListenAndServe(":8080", nil)
}

想定されるユースケース

QUERYは、読み取り専用でありながら複雑な入力を必要とする処理に適しています。

  • 複数条件、入れ子、配列を含む商品検索
  • 分析APIへの集計条件の送信
  • JSONPath、GraphQL、SQLなどのクエリ送信
  • 地理空間検索におけるポリゴンや複雑なフィルターの指定
  • ログ検索や全文検索
  • 長いベクトルや検索条件を扱う検索API

一方、単純なID指定や数個の検索条件しかない場合は、URIで共有・ブックマークしやすいGETの方が適しています。

QUERYをすぐ本番採用すべきか

RFCとして標準化されたことと、既存のWeb基盤ですぐ問題なく使えることは別です。

導入前に、通信経路全体を確認する必要があります。

  • APIクライアントが任意のHTTPメソッドを送信できるか
  • Webサーバーやアプリケーションフレームワークが受け付けるか
  • リバースプロキシ、API Gateway、CDN、WAFが拒否しないか
  • CORS設定でQUERYを許可できるか
  • APM、アクセスログ、モック、テストツールが正しく扱えるか
  • キャッシュがBodyを含むキーを安全に構成できるか

特に既存システムとの互換性が重要な場合は、当面POST /searchを維持しつつ、QUERYを追加提供する段階的な移行も現実的です。

よくある誤解

「QUERYはBodyを持てるGET」である

説明の入口としては近いものの、厳密には異なります。

GETはターゲットURIで識別されるリソースの表現を取得します。QUERYは、ターゲットリソースの範囲内で、Bodyに記述された問い合わせ処理を実行するよう求めます。

機密情報をBodyに移せば安全である

URLよりBodyの方がアクセスログなどに残りにくい傾向はありますが、暗号化やアクセス制御の代わりにはなりません。Bodyを記録するAPI GatewayやAPMもあります。

HTTPS、認証・認可、ログのマスキング、保存期間の管理などは引き続き必要です。

冪等だからレスポンスは常に同じである

冪等性は、同じリクエストによって対象リソースに与える意図された効果についての性質です。検索対象データが変われば、結果が変わることはあります。

まとめ

QUERYは、複雑な検索条件をBodyで表現しながら、その処理が安全かつ冪等であることをHTTPレベルで明示するメソッドです。

  • 2026年6月にRFC 10008として標準化された
  • Bodyを問い合わせ内容として利用できる
  • SafeかつIdempotentで、自動再試行に適した意味を持つ
  • レスポンスをキャッシュできる
  • Accept-Queryで対応する問い合わせ形式を通知できる
  • ブラウザではCORSプリフライトが必要になる
  • 周辺ツールやインフラの対応確認は欠かせない

単純な取得には引き続きGET、状態を変更する処理にはPOSTなどを使い、複雑な読み取り専用クエリが必要な場面でQUERYを検討するのがよいでしょう。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?