Delphiの新しいHTTP QUERYメソッド | eSeGeCe Blog

Delphiの新しいHTTP QUERYメソッド

· 機能

IETF HTTPワーキンググループは、QUERYと呼ばれる新しいHTTPリクエストメソッドを策定しました(draft-ietf-httpbis-safe-method-w-body、RFC化に向けて進行中)。QUERYは、RESTの開発者なら誰もが直面してきたギャップを埋めるものです。GETのように安全かつ冪等でありながら、POSTのようにリクエストボディを持つリクエストです。sgcWebSocketsは、DelphiおよびC++ Builder向けに、HTTP/1.1、HTTP/2、HTTP/3の各クライアントとサーバーコンポーネントで、QUERYをエンドツーエンドでサポートするようになりました。

なぜ新しいHTTPメソッドなのか?

これまで、サーバーにクエリを送る方法は2つあり、どちらにも問題がありました。GETリクエストのURLにクエリをエンコードする方法では、URLに実用上の長さ制限があり、複雑なクエリのエスケープは煩雑で、さらに完全なURLはプロキシやサーバーのログに残りがちです。機密性の高いフィルター値を置く場所としては不適切です。もう一つはPOSTリクエストのボディでクエリを送る方法ですが、POSTは安全でも冪等でもキャッシュ可能でもありません。HTTPクライアントは接続障害の後にPOSTを自動的に再試行することが許されておらず、キャッシュもそのレスポンスを保存しません。

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クライアントには、Post系メソッドとまったく同じように動作するQueryおよびQueryAsyncメソッドが追加されました。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コンテンツタイプをデフォルトとする文字列オーバーロードが用意されており、シンプルなクエリなら1回の呼び出しで済みます。

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のダウンロードページから入手できます。

ご質問、フィードバック、移行のサポートが必要ですか?お問い合わせください。コードを書いた本人たちから返信が届きます。