Delphi 中全新的 HTTP QUERY 方法 | eSeGeCe 博客

Delphi 中全新的 HTTP QUERY 方法

· 功能

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" }
]

规范中最重要的规则如下:

从 HTTP/1.1 客户端发送 QUERY

HTTP/1.1 客户端新增了 QueryQueryAsync 方法,其用法与对应的 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 下载页面下载最新版本。

有疑问、反馈或需要迁移方面的帮助?联系我们,回复你的正是编写这些代码的人。