RFC 10008 引入了新的 HTTP QUERY Method,用來處理「帶 request body 的查詢」— 保留 GET 的 Safe 與 Retryable Semantics,同時解決 URI 過長與查詢過於暴露的問題。

The HTTP QUERY Method

背景:為什麼需要新的Method?

HTTP 一直只有三個常見的 Read Method:GETHEADOPTIONS,其中只有 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 是操作的輸入資料。
特性 / 行為GETQUERYPOST
Safe(不改變目標資源狀態)視乎 Semantics,一般視為可能改狀態
Idempotent(重試結果一致)規範不保證
Request body Semantics未定義,多數實作忽略明確:查詢內容,由資源定義 Semantics明確:操作輸入資料
查詢條件放哪裡URI path + query stringRequest 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/jsonapplication/sql)。

2. 保留 Read Semantics,但又想用 body

很多系統有 POST /searchPOST /analytics 之類的 endpoint,Semantics 上是「讀」,技術上卻被當作「寫」來對待。

改用 QUERY /searchQUERY /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

  • 對所有 QUERY endpoint 強制檢查 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 驅動的讀操作。

延伸閱讀建議: