RFC 10008 引入了新的 HTTP
QUERYMethod,用來處理「帶 request body 的查詢」— 保留 GET 的 Safe 與 Retryable Semantics,同時解決 URI 過長與查詢過於暴露的問題。
背景:為什麼需要新的Method?
HTTP 一直只有三個常見的 Read Method:GET、HEAD、OPTIONS,其中只有 GET 被廣泛用在實際的資料讀取與查詢上。
現實世界的查詢越來越複雜 — 多維度篩選 (faceted search)、聚合、排序、DSL — 全部硬塞在 URI 裡的話,既難讀又難維護,更有URI過長與隱私考慮。
為此,有些做法會使用request body,並設計成 POST /search 等「名義上查詢、實際用 POST」的 endpoint,讓 cache、代理與 automatic retry 等行為變得模糊,甚至有些不可預測,例如 CDN 收到 POST 請求時可能拒絕 cache 或 automatic retry,即便那個請求實際上只是查詢資料。
QUERY 正是為了補上這個缺口:它是 Safe + Idempotent + Cachable 的新 method,專門用來處理「有 body 的查詢」。
GET / QUERY / POST 比較
重新審視 read operation 與 write operation 的邊界。
Method Semantics 與 特性
- GET:Safe、Idempotent,傳統用來讀取資源與列表,規範並未定義 request body 的 Semantics。
- QUERY:Safe、Idempotent,明確預期有 request body,內容由目標資源解讀為「查詢條件」。
- POST:規範不保證 Safe、Idempotent,一般用於建立資源或觸發動作,body 是操作的輸入資料。
| 特性 / 行為 | GET | QUERY | POST |
|---|---|---|---|
| Safe(不改變目標資源狀態) | ✅ | ✅ | 視乎 Semantics,一般視為可能改狀態 |
| Idempotent(重試結果一致) | ✅ | ✅ | 規範不保證 |
| Request body Semantics | 未定義,多數實作忽略 | 明確:查詢內容,由資源定義 Semantics | 明確:操作輸入資料 |
| 查詢條件放哪裡 | URI path + query string | Request body + 可選 URI query | 多數在 body,有時搭配 URI query |
| 典型用途 | 讀取資源、列表、靜態內容 | 複雜搜尋、過濾、分析查詢(JSONPath/SQL 等) | 建立資源、執行命令、非安全操作 |
| Cacheable | ✅ cache key 主要看 URI | ✅ cache key 必須納入 body 與相關 metadata | 視乎情況,實務較少 cache |
| 自動重試友善度 | 高,Semantics 清晰 | 高,可安全重試 / restart | 需小心副作用與重複提交 |
| Content-Type 要求 | 一般只在需要時檢查 | 必須正確設定,不應依賴 Sniffing | 需要 Content-Type,錯誤時由應用決定處理方式 |
什麼時候該用 QUERY?
1. 查詢很複雜、URI過長
當你需要:
- 多欄位 filter(時間區間、狀態、金額、標籤等)
- 自訂排序與分頁 (Pagination)
- 以 JSONPath、SQL-like 或其他 DSL 形式表達查詢
把這些全部塞進 query string 會大大降低 readability 與 maintainability。QUERY 則提供一條更乾淨的路:把查詢條件放在結構化的 body 裡(例如 application/json 或 application/sql)。
2. 保留 Read Semantics,但又想用 body
很多系統有 POST /search 或 POST /analytics 之類的 endpoint,Semantics 上是「讀」,技術上卻被當作「寫」來對待。
改用 QUERY /search 或 QUERY /analytics,就能讓 cache、代理與 automatic retry 邏輯與 GET 的 Semantics 對齊,避免因使用 POST 可能帶來的副作用影響。
API 例子一:訂單搜尋
從
GET /orders?…走到QUERY /orders。
原本設計(GET / POST)
GET /orders?status=PAID&from=2026-01-01&to=2026-06-30&minTotal=1000&maxTotal=10000&tag=premium&sort=-createdAt&page=2&pageSize=50或:
POST /orders/search
Content-Type: application/json
{
"status": ["PAID", "REFUNDED"],
"createdAt": { "from": "2026-01-01", "to": "2026-06-30" },
"total": { "min": 1000, "max": 10000 },
"tags": ["premium"],
"sort": [{ "field": "createdAt", "order": "desc" }],
"page": 2,
"pageSize": 50
}第一種 URI 長度與可讀性都堪憂;第二種 Semantics 是查詢,Method 卻是 POST。
使用 QUERY 的版本
QUERY /orders
Content-Type: application/json
Accept: application/json
{
"filter": {
"status": ["PAID", "REFUNDED"],
"createdAt": { "from": "2026-01-01", "to": "2026-06-30" },
"total": { "min": 1000, "max": 10000 },
"tags": ["premium"]
},
"sort": [{ "field": "createdAt", "order": "desc" }],
"pagination": { "page": 2, "pageSize": 50 }
}回應:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Location: /query-results/orders/abc123
{
"items": [ /* 訂單列表 */ ],
"page": 2,
"pageSize": 50,
"totalCount": 4321
}- 這個操作是 Safe + Idempotent,失敗時可安全 Retry 同一個 QUERY。
Content-Location指向這次結果的資源,之後客戶端可以直接GET /query-results/orders/abc123重取同一份資料。
API 例子二:分析查詢(SQL-like)
報表或 Analytics 服務,讓客戶端以 SQL-like 語法描述查詢。
QUERY /analytics/sales
Content-Type: application/sql
Accept: application/json
SELECT region, SUM(amount) AS total
FROM sales
WHERE created_at BETWEEN DATE '2026-01-01' AND DATE '2026-06-30'
GROUP BY region
ORDER BY total DESC;伺服器行為建議:
- 如果
Content-Type缺失或不支援,回 415 Unsupported Media Type。 - 如果 SQL 語法正確,但查詢內容無法處理(例如 Table 不存在),回
422 Unprocessable Content。這和 spec 的建議一致。
成功回應可以宣告一個等價資源 URI (Equivalence):
HTTP/1.1 200 OK
Content-Type: application/json
Location: /queries/sales-by-region-2026H1
[
{ "region": "APAC", "total": 12345678 },
{ "region": "EMEA", "total": 9876543 }
]之後客戶端只需:
GET /queries/sales-by-region-2026H1即可重新讀取同一報表,而不必再傳送 SQL body。這對經常查詢的報表與 Cache 策略非常友好。
API 例子三:JSONPath 文件查詢
文件存儲服務,提供 JSONPath 查詢能力。
QUERY /documents
Content-Type: application/jsonpath
Accept: application/json
$.items[?(@.status == "active" && @.priority >= 3)]回應:
HTTP/1.1 200 OK
Content-Type: application/json
{
"matches": [ /* 符合條件的文件Segment */ ],
"query": "$.items[?(@.status == \"active\" && @.priority >= 3)]"
}Server可以透過 Accept-Query Response Header 宣告支援的查詢格式:
HTTP/1.1 200 OK
Accept-Query: "application/jsonpath", "application/sql"這樣 Client-side 就能在發 QUERY 前先協商並選擇合適的查詢 Media Type。
在現有 Backend 專案中導入 QUERY
Routing 與 Framework支援
-
確認 API gateway / web framework 是否已支援自訂 HTTP 方法;例如 ASP.NET Core, Spring, Express 等通常都可以映射
QUERY(儘量支援方式和成熟度或有所不同)。 -
在 OpenAPI / API spec 中明確宣告
QUERY方法,讓文件與 Client SDK 生成工具能理解新動詞。部分框架已開始原生支援。
Media Type 與 Validation
-
對所有
QUERYendpoint 強制檢查Content-Type,缺失或不支援即回415 Unsupported Media Type。 -
把
422 Unprocessable Content用於「查詢內容無法處理」的場景,讓客戶端明`確區分通訊錯誤與業務查詢錯誤。這與草案建議一致。
與既有 GET / POST 共存
-
對簡單的資源讀取與列表,維持
GET /resources即可,不必強迫遷移。 -
優先鎖定那些「名義上查詢、實際用 POST」的 endpoint(例如
/search、/analytics),逐步改為QUERY,讓 Semantics 與基礎設施行為更一致。
結語與延伸閱讀
HTTP QUERY 本質上就是「GET with request body」,但多了明確的 Safe / iIdempotent Semantics 與 Cache / 等價資源模式,對設計現代 Backend API 非常有幫助。
它不會取代 GET 或 POST,而是填補它們之間的空隙——尤其適合複雜查詢、報表與 DSL 驅動的讀操作。
延伸閱讀建議:
- RFC 10008:The HTTP QUERY Method。
- http.dev 的 QUERY 專題頁,對等價資源與 cache 模式有好整理。