sgcWebSockets Enterpriseエディションには、新しいコンポーネントTsgcWSAPIServer_OpenAPIが含まれており、OpenAPI 3の記述をDelphiアプリケーション内で動作するRESTサーバーに変換します。フォーム上にドロップしてHTTPサーバーに接続し、仕様ファイルを渡すだけで — ルート、リクエスト検証、エラーレスポンス、ライブのSwagger UIドキュメントが自動的に組み込まれます。本記事では、このコンポーネントの動作の仕組み、駆動する2つの方法(仕様ファーストとコードファースト)、重要な設定項目、そして新規プロジェクトにそのまま貼り付けて使える完全なDelphiサンプルを順を追って解説します。
コンポーネントの機能
TsgcWSAPIServer_OpenAPIは、既存のAPIサーバー拡張ポイントを通じてTsgcWebSocketHTTPServerに組み込まれる薄いAPIサーバーです。サーバーに接続し、OpenAPI 3.0仕様を読み込ませると、受信するHTTPリクエストごとに次の4つの処理を行います:
- 仕様で宣言されたルート(
{path}パラメーターを含む)に対してURLとHTTPメソッドを照合する; - リクエストを — 必須フィールド、クエリパラメーター、パスパラメーター、リクエストボディを — 仕様内のスキーマに対して検証する;
- 解決された
operationIdと完全に構築されたコンテキストオブジェクトを引数にOnRequestイベントを発火するため、開発者はビジネスロジックの記述に集中できる; - 2つの組み込みエンドポイントを標準で提供する —
/openapi.jsonで生の仕様を、/docsでインタラクティブなSwagger UIを — すべてのルートに対するオプションのCORSプリフライト付き。
結果として、仕様が単一の信頼できる情報源になります。JSON内のパス、パラメーター、レスポンスコードを変更して再起動すれば、Delphiコードを再コンパイルすることなく、サーバーが新しい契約を採用します。
仕様ファースト: 既存のOpenAPI 3ファイルを読み込む
既にOpenAPI 3ドキュメント(たとえばAPIデザイナーからエクスポートしたpetstore.json)がある場合、配線は基本的に3行 — コンポーネントを作成し、仕様を読み込み、サーバーを接続するだけです。残りはすべて設定と、実際のレスポンスを生成するOnRequestハンドラーです。
uses
sgcWebSocket, sgcWebSocket_Classes,
sgcWebSocket_Server_API_OpenAPI,
sgcHTTP_OpenAPI_Server;
var
WSServer: TsgcWebSocketHTTPServer;
FOpenAPI: TsgcWSAPIServer_OpenAPI;
begin
WSServer := TsgcWebSocketHTTPServer.Create(nil);
WSServer.Port := 8080;
FOpenAPI := TsgcWSAPIServer_OpenAPI.Create(nil);
FOpenAPI.OnRequest := OnOpenAPIRequest;
FOpenAPI.OnBeforeRequest := OnOpenAPIBeforeRequest;
FOpenAPI.OnValidationError := OnOpenAPIValidationError;
// Configuration
FOpenAPI.OpenAPIOptions.Endpoint.ServeSpec := True;
FOpenAPI.OpenAPIOptions.Endpoint.ServeSwaggerUI := True;
FOpenAPI.OpenAPIOptions.CORS.Enabled := True;
FOpenAPI.OpenAPIOptions.Validation.ValidateRequest := True;
FOpenAPI.OpenAPIOptions.Validation.ValidateRequired := True;
FOpenAPI.OpenAPIOptions.Validation.ValidateRequestBody := True;
// Load spec and attach to server
FOpenAPI.LoadFromFile('petstore.json');
FOpenAPI.Server := WSServer;
WSServer.Active := True;
// Swagger UI: http://localhost:8080/docs
// Raw spec: http://localhost:8080/openapi.json
end;
OnRequestイベントはoperationIdごとに発火します — これはYAML/JSONの各オペレーションに記述した文字列です。オペレーションごとに1つの分岐を書き、コンテキストから入力を読み取ってレスポンスを返します:
procedure TForm1.OnOpenAPIRequest(Sender: TObject;
const aOperationId: string; const aContext: TsgcOpenAPIServerContext;
var Handled: Boolean);
var
vId: Int64;
vLimit: Integer;
begin
Handled := True;
if aOperationId = 'listPets' then
begin
vLimit := aContext.QueryParamAsInteger('limit', 100);
aContext.RespondJSON(200, BuildPetsJSON(vLimit));
end
else if aOperationId = 'getPetById' then
begin
vId := aContext.PathParamAsInteger('petId');
if FindPet(vId) then
aContext.RespondJSON(200, PetAsJSON(vId))
else
aContext.RespondError(404, 'Not Found',
Format('Pet %d not found', [vId]));
end
else
Handled := False;
end;
TsgcOpenAPIServerContextオブジェクトは、すべてのハンドラーで中心的な役割を果たします。パスパラメーターとクエリパラメーターを名前で公開し(PathParamAsString、PathParamAsInteger、QueryParamAsString、QueryParamAsInteger、QueryParamAsBoolean)、HeaderValueによるヘッダー参照、文字列としてのボディ(BodyAsString)または事前解析済みのJSON(BodyAsJSON)を提供します。さらに2つのレスポンスヘルパーが用意されており、通常のペイロード用のRespondJSON(code, content)と、RespondError(code, title, detail)はRFC 7807形式のapplication/problem+jsonボディを生成するため、クライアントは常に一貫したエラー形式を受け取れます。
コードファースト: Delphi属性から仕様を生成する
2つ目のモードは逆方向のフローです。属性で装飾したDelphiクラスとしてAPIを宣言し、スキャナーに実行時にOpenAPIドキュメントを生成させ、そのドキュメントを同じコンポーネントに渡します。JSONを手書きする必要はまったくありません。
uses
sgcHTTP_OpenAPI_Server_CodeFirst;
type
[sgcServiceContract('Task Manager API',
'A simple task manager', '1.0.0')]
[sgcRoute('/api/v1')]
TTaskService = class
public
[sgcHttpGet]
[sgcRoute('/tasks')]
[sgcSummary('List all tasks')]
[sgcResponse(200, 'A list of tasks')]
procedure ListTasks([sgcFromQuery] const status: string); virtual;
[sgcHttpGet]
[sgcRoute('/tasks/{taskId}')]
[sgcSummary('Get a task by ID')]
[sgcResponse(200, 'The requested task')]
[sgcResponse(404, 'Task not found')]
procedure GetTask([sgcFromPath][sgcRequired]
const taskId: Integer); virtual;
[sgcHttpPost]
[sgcRoute('/tasks')]
[sgcSummary('Create a new task')]
[sgcResponse(201, 'Task created')]
procedure CreateTask([sgcFromBody] const body: string); virtual;
end;
クラス自体に実際のメソッド本体は必要ありません — スキャナーはRTTI経由でクラスを読み取り、実際のロジックはOnRequestハンドラーに残ります。仕様の生成とサーバーの起動は数行で済みます:
var
oScanner: TsgcOpenAPICodeFirstScanner;
vSpec: string;
begin
oScanner := TsgcOpenAPICodeFirstScanner.Create;
try
vSpec := oScanner.GenerateSpec(TTaskService);
finally
oScanner.Free;
end;
FOpenAPI.LoadFromString(vSpec);
FOpenAPI.Server := WSServer;
WSServer.Active := True;
end;
属性セットは一般的なケースを網羅しています: ルーティング(sgcHttpGet、sgcHttpPost、sgcHttpPut、sgcHttpDelete、sgcHttpPatch、sgcRoute)、パラメーターバインディング(sgcFromPath、sgcFromQuery、sgcFromHeader、sgcFromBody)、検証(sgcRequired、sgcMinLength、sgcMaxLength、sgcRange、sgcPattern)、ドキュメンテーション(sgcSummary、sgcDescription、sgcTag、sgcResponse)。
設定の詳細
OpenAPIOptionsは3つの永続的なサブオブジェクトにグループ化されているため、すべてオブジェクトインスペクターで確認できます:
Endpoint—BasePathはすべてのルートおよび組み込みの/openapi.jsonと/docsエンドポイントにプレフィックスを付加します;SpecFileはLoadFromFileの代わりに設計時に使用できます;ServeSpecとServeSwaggerUIは2つの組み込みエンドポイントを切り替えます(どちらもデフォルトでオン)。Validation— マスタースイッチはValidateRequestです;ValidateRequired、ValidateQueryParams、ValidatePathParams、ValidateRequestBodyでチェック対象を絞り込めます。検証が失敗すると、OnValidationErrorが問題のリストとContinueフラグとともに発火します — これをFalseに設定するとリクエストを自動的に拒否します。CORS—Enabled := Trueを設定すると、サーバーはすべてのルートに対するプリフライトOPTIONSリクエストに応答し、レスポンスポリシーとしてAllowOrigins、AllowHeaders、AllowMethodsを使用します。
OnRequest以外にも、パイプラインに介入できる4つのイベントがあります: OnBeforeRequest(Accept := Falseを設定して短絡可能、レート制限やロギングに便利)、OnAfterRequest(レスポンス生成後の後処理)、OnAuthenticate(トークンやセッションを確認した後にAuthenticated := Trueを設定)、OnException(キャッチオールで、フレームワークがエラーボディを書き込む前にHTTPステータスを変更できます)。
標準搭載のSwagger UI
ServeSwaggerUI := Trueを設定すると、サーバーはBasePath + '/docs'にSwagger UIページを公開し、BasePath + '/openapi.json'から仕様を読み込みます。ブラウザでURLを開けば、標準のtry-it-out体験が得られ、独自に稼働中のサーバーから情報が供給されます — 別途ドキュメントをビルドしたり、静的にエクスポートする必要はありません。CORSと組み合わせることで、フロントエンドチームやAPI統合パートナーにバックエンドを最速で引き渡せます。
入手方法
このコンポーネントはsgcWebSockets Enterpriseエディションに含まれており、SGC OpenAPIパレットページに登録されています。完全なデモが2つ — 1つはPetstore JSONを使用した仕様ファースト、もう1つはタスクマネージャーサービスを使用したコードファースト — Demos\23.OpenAPIに同梱されています。最新ビルドはsgcWebSocketsダウンロードページから入手できます。
ご質問、ご意見、既存プロジェクトへの組み込みに関するサポートが必要ですか? お問い合わせください — コードを書いた開発者から直接返信が届きます。
