IETF HTTP 工作组制定了一个名为 QUERY 的全新 HTTP 请求方法(draft-ietf-httpbis-safe-method-w-body,即将成为 RFC)。QUERY 填补了每位 REST 开发者都曾遇到的空白:它像 GET 一样是安全且幂等的请求,但又像 POST 一样携带请求体。sgcWebSockets 现已为 Delphi 和 C++ Builder 提供端到端的 QUERY 支持,涵盖 HTTP/1.1、HTTP/2 和 HTTP/3 客户端以及服务器组件。
为什么需要一个新的 HTTP 方法?
在此之前,向服务器发送查询有两种方式,而两者都存在问题。你可以把查询编码到 GET 请求的 URL 中,但 URL 有实际的长度限制,复杂查询的转义十分麻烦,而且完整的 URL 往往会出现在代理和服务器日志中,这对敏感的筛选条件来说可不是个好去处。或者你可以把查询放进 POST 请求的请求体,但 POST 既不安全、也不幂等、更不可缓存:HTTP 客户端不允许在连接失败后自动重试,缓存也不会存储其响应。
QUERY 集两者之长。请求内容及其 Content-Type 定义了查询,因此查询可以按需要做到任意大小、任意结构(表单数据、JSON、SQL、JSONPath,只要服务器接受即可)。与此同时,规范声明 QUERY 是安全且幂等的:它绝不会改变资源的状态,可以安全地重试或重复执行,其响应也像 GET 响应一样可以缓存。
QUERY 的工作原理
QUERY 请求看起来就像一个带有 GET 语义的 POST。下面这个改编自规范的示例向联系人资源请求经过筛选的投影:
QUERY /contacts HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Accept: application/json
select=surname,givenname,email&limit=10
HTTP/1.1 200 OK
Content-Type: application/json
[
{ "surname": "Smith", "givenname": "John", "email": "smith@example.org" },
{ "surname": "Jones", "givenname": "Sally", "email": "sally.jones@example.com" }
]
规范中最重要的规则如下:
- 每个 QUERY 请求都必须包含与其请求体相匹配的
Content-Type。当该头缺失或与内容不一致时,服务器必须以 4xx 状态码(400、415 或 422)拒绝该请求。 - 响应是可缓存的。存储这些响应的缓存必须把请求体纳入缓存键,这样两个不同的查询就永远不会冲突。
- 重定向会保留该方法:在收到 301、302、307 或 308 之后,客户端会对新的 URI 重复发送 QUERY。以前把重定向的 POST 改写为 GET 的例外规则不适用于 QUERY。303 See Other 是服务器明确表示“请用 GET 从这个 Location 获取结果”的方式。
- 服务器可以通过新的
Accept-Query响应头声明某个资源支持哪些查询格式;当支持 QUERY 时,它也会出现在 OPTIONS 响应的Allow头中。 - 在浏览器中,QUERY 不是 CORS 安全列表中的方法,因此跨源调用会触发预检请求。sgcWebSockets OpenAPI 服务器的默认 CORS 配置已在
Access-Control-Allow-Methods中包含 QUERY。
从 HTTP/1.1 客户端发送 QUERY
HTTP/1.1 客户端新增了 Query 和 QueryAsync 方法,其用法与对应的 Post 方法完全相同:传入 URL 和包含查询体的流,在请求中设置 Content-Type,然后读取结果。
uses
Classes, sgcHTTP;
var
oClient: TsgcHTTP1Client;
oQuery: TStringStream;
vResult: string;
begin
oClient := TsgcHTTP1Client.Create(nil);
try
oQuery := TStringStream.Create('select=surname,givenname,email&limit=10');
try
oClient.Request.ContentType := 'application/x-www-form-urlencoded';
oClient.Request.Accept := 'application/json';
vResult := oClient.Query('https://api.example.org/contacts', oQuery);
finally
oQuery.Free;
end;
finally
oClient.Free;
end;
end;
由于 QUERY 是幂等的,它也能与客户端的重试选项很好地配合:因连接错误而失败的 QUERY 可以自动重试,而不必担心在服务器上重复触发状态变更。同样的 Query 方法也可在 REST API 基础客户端上使用,因此基于 sgcWebSockets 构建的自定义 API 集成无需任何额外工作即可采用这个动词。
通过 HTTP/2 和 HTTP/3 发送 QUERY
HTTP/2 和 HTTP/3 客户端提供了同样的动词。在 HTTP/2 上,请求会把 :method 伪头设置为 QUERY,并在 DATA 帧中发送请求体:
uses
Classes, sgcHTTP;
var
oClient: TsgcHTTP2Client;
oQuery: TStringStream;
vResult: string;
begin
oClient := TsgcHTTP2Client.Create(nil);
try
oQuery := TStringStream.Create('select=surname,givenname,email&limit=10');
try
vResult := oClient.Query('https://api.example.org/contacts', oQuery);
finally
oQuery.Free;
end;
finally
oClient.Free;
end;
end;
HTTP/3 客户端提供了一个字符串重载,默认使用 form-urlencoded 内容类型,简单查询只需一次调用即可完成:
uses
sgcHTTP;
var
oClient: TsgcHTTP3Client;
vResult: string;
begin
oClient := TsgcHTTP3Client.Create(nil);
try
vResult := oClient.Query('https://api.example.org/contacts',
'select=surname,givenname,email&limit=10');
finally
oClient.Free;
end;
end;
在服务器端处理 QUERY
在服务器端,TsgcWebSocketHTTPServer 通过 HTTP/1.1 和 HTTP/2 接受 QUERY 请求,并把它们分发到 OnCommandOther 事件,此时请求体已被读取并可在 PostStream 中获取。一个最简的处理程序会验证 Content-Type、执行查询并返回结果:
procedure TForm1.ServerCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
vQuery: string;
begin
if SameText(ARequestInfo.Command, 'QUERY') then
begin
// the specification requires a Content-Type on every QUERY request
if (ARequestInfo.ContentType = '') or
not Assigned(ARequestInfo.PostStream) then
begin
AResponseInfo.ResponseNo := 400;
Exit;
end;
ARequestInfo.PostStream.Position := 0;
vQuery := ReadStringFromStream(ARequestInfo.PostStream);
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := RunContactsQuery(vQuery);
end;
end;
HTTP/3 服务器以同样的方式读取 QUERY 请求体;OpenAPI 服务器会路由 OpenAPI 规范中声明的 query 操作,并在其默认 CORS 配置中声明该方法;HTTP 转发功能则会把 QUERY 请求连同其请求体一起代理到后端服务器。
可用性
QUERY 支持已包含在 sgcWebSockets 2026.7 中,覆盖 HTTP/1.1、HTTP/2 和 HTTP/3 客户端、REST API 基础客户端、服务器组件以及 OpenAPI 服务器,适用于 Delphi 7 到 Delphi 13 以及 C++ Builder。由于底层的传输格式就是普通的 HTTP,它如今即可与任何理解该方法的服务器或客户端互操作。你可以从 sgcWebSockets 下载页面下载最新版本。
有疑问、反馈或需要迁移方面的帮助?联系我们,回复你的正是编写这些代码的人。
