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" }
]
仕様における最も重要なルールは次のとおりです。
- すべてのQUERYリクエストには、ボディに一致する
Content-Typeを含める必要があります。ヘッダーが欠落している場合や内容と矛盾する場合、サーバーは4xxステータス(400、415、422のいずれか)でリクエストを拒否しなければなりません。 - レスポンスはキャッシュ可能です。レスポンスを保存するキャッシュは、キャッシュキーにリクエストボディを含める必要があるため、異なる2つのクエリが衝突することはありません。
- リダイレクトはメソッドを保持します。301、302、307、308の後、クライアントは新しいURIに対してQUERYを繰り返します。リダイレクトされたPOSTをGETに書き換える従来の例外は、QUERYには適用されません。303 See Otherは、サーバーが「このLocationからGETで結果を取得せよ」と明示的に伝える手段です。
- サーバーは、新しい
Accept-Queryレスポンスヘッダーによって、リソースが理解できるクエリ形式を通知できます。また、サポートされている場合、QUERYはOPTIONSレスポンスのAllowヘッダーに現れます。 - ブラウザーでは、QUERYはCORSセーフリストに含まれるメソッドではないため、クロスオリジンの呼び出しはプリフライトを引き起こします。sgcWebSockets OpenAPIサーバーのCORSデフォルト設定では、すでに
Access-Control-Allow-MethodsにQUERYが含まれています。
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のダウンロードページから入手できます。
ご質問、フィードバック、移行のサポートが必要ですか?お問い合わせください。コードを書いた本人たちから返信が届きます。
