TsgcHTTPServer の上に REST API を構築することは以前から可能でしたが、毎回手作業で書かなければならないものがいくつかありました。CORS プリフライトへの応答、ロードバランサー用のヘルスエンドポイント、監視スタック用のメトリクスエンドポイント、そしてある顧客のデータを別の顧客のデータと区別する仕組みです。TsgcHTTPRESTServer は、それらすべてを最初から備えた新しいコンポーネントです。
このコンポーネントは TsgcHTTPServer を直接継承しているため、すでに知っている内容はそのまま通用します。同じ Port、同じ SSLOptions、同じ Authentication、同じ OnCommandGet ハンドラーです。TsgcHTTPServer はこれまでどおり素の HTTP サーバーのままで、変更はありません。追加機能は派生クラス側にあり、それぞれがオプトインです。
はじめに
コンポーネントは sgcHTTP_REST_Server ユニットにあり、SGC REST パレットページに TsgcHTTPRESTServer として登録されています。フォームに配置して Active を設定すれば、動作する HTTP サーバーになります。興味深いのはリクエストハンドラーの部分です。
uses
sgcHTTP_REST_Server;
var
oServer: TsgcHTTPRESTServer;
begin
oServer := TsgcHTTPRESTServer.Create(nil);
oServer.Port := 8080;
oServer.OnCommandGet := OnServerCommandGet;
oServer.Active := True;
end;
ハンドラーは標準の Indy のリクエストオブジェクトとレスポンスオブジェクトを使うため、JSON の応答は 3 つの代入で書けます。
procedure TForm1.OnServerCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Document = '/api/status' then
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := '{"status":"running"}';
end
else
begin
AResponseInfo.ResponseNo := 404;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := '{"error":"not found"}';
end;
end;
OnCommandGet は GET と POST を受け取ります。PUT、PATCH、DELETE などのメソッドは OnCommandOther に届きます。シグネチャはまったく同じなので、すべてのメソッドをサポートする REST リソースは、通常は両方のイベントから呼び出される 1 つのディスパッチルーチンとして書きます。
procedure TForm1.OnServerCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Command = 'DELETE' then
begin
AResponseInfo.ResponseNo := 204;
AResponseInfo.ContentText := '';
end;
end;
CORS は意図的にデフォルトで無効
別のオリジンから API を呼び出すブラウザーには Access-Control-* ヘッダーが必要であり、実際のリクエストを送る前に OPTIONS プリフライトへの応答も必要です。CORSOptions はその両方を処理します。
強調しておきたいのは、Enabled がデフォルトで False であり、実際に必要になるまではそのままにしておくべきだという点です。アップグレード後にサーバーが黙って Access-Control-Allow-Origin: * を返し始めるとしたら、それはセキュリティの後退です。そのため CORS は厳密にオプトインになっています。
oServer.CORSOptions.Enabled := True;
oServer.CORSOptions.AllowOrigins := 'https://app.example.com';
oServer.CORSOptions.AllowMethods := 'GET, POST, PUT, DELETE, OPTIONS';
oServer.CORSOptions.AllowHeaders := 'Content-Type, Authorization';
これを設定すると、プリフライトには 204 と 3 つのヘッダーで自動的に応答し、通常のレスポンスにもすべてヘッダーが追加されます。ハンドラーに OPTIONS の分岐を書く必要はありません。
API に認証がある場合は、* ではなく明示的なオリジンを指定してください。ワイルドカードのオリジンと認証情報の組み合わせは、いずれにせよブラウザーが拒否します。実際に配信するオリジンを列挙するほうが安全なデフォルトです。
自分で書かずに済むヘルスとメトリクス
TsgcHTTPServerStats コンポーネントを ServerStats プロパティに割り当てると、サーバーは 2 つの運用エンドポイントに応答できるようになります。どちらも個別に有効にするまでは無効のままです。
oStats := TsgcHTTPServerStats.Create(nil);
oStats.Endpoints.Health.Enabled := True;
oStats.Endpoints.Metrics.Enabled := True;
oServer.ServerStats := oStats;
/health は、ロードバランサーのプローブに適した小さな JSON ドキュメントを返します。
{"status":"ok","uptime":3600,"connections":12,"requests":48120,
"responses":{"1xx":0,"2xx":47800,"3xx":10,"4xx":300,"5xx":10},
"latency":{"min":0,"avg":4,"max":180}}
/metrics は Prometheus のテキスト公開形式で応答するため、間にアダプターを挟むことなくスクレイプできます。
# HELP sgc_server_requests_total Total requests served
# TYPE sgc_server_requests_total counter
sgc_server_requests_total 48120
# HELP sgc_server_request_duration_ms_avg Average request duration
# TYPE sgc_server_request_duration_ms_avg gauge
sgc_server_request_duration_ms_avg 4
/metrics と /health が自分のルートと衝突する場合は、どちらのパスも変更できます。
oStats.Endpoints.Metrics.Path := '/internal/metrics';
oStats.Endpoints.Health.Path := '/internal/health';
これらのエンドポイントは認証ゲートの後で処理されるため、サーバーがすでに適用している認証をそのまま引き継ぎます。サーバー自体が公開されていない限り、これらが公開されることはありません。この点は両方向で知っておく価値があります。デフォルトで非公開に保たれる一方で、サーバーが認証を要求する場合は監視のスクレイパーにも認証情報が必要になります。
API と並べて静的コンテンツを配信する
DocumentRoot は TsgcHTTPServer から継承されており、これまでどおり動作します。そのため 1 台のサーバーで、小さなフロントエンドとそれが呼び出す API の両方をホストできます。ハンドラーが応答しなかったものは、そのままドキュメントルートに引き渡されます。
oServer.DocumentRoot := 'C:\www\app';
oServer.HTTPCompression.Enabled := True;
oServer.HTTPCompression.MinSize := 1024;
TLS
基底のサーバーから変わる点はありません。これまでどおり SSL を設定し、SSLOptions を指定してください。
oServer.SSL := True;
oServer.SSLOptions.Port := 443;
oServer.Port := 443;
次に続くもの
このコンポーネントは Tenancy プロパティと読み取り専用の Tenant プロパティも公開しており、1 台のサーバーを複数顧客向けのサーバーに変えられます。また Authentication のオプションはユーザーストアコンポーネントを受け付けるため、認証情報を TStringList に保持する必要はありません。これらは2 番目の記事で扱います。3 番目の記事では、全体の前面に OpenAPI コントラクトを置き、ルーティングも検証もドキュメントもすべて 1 つのファイルから得る方法を紹介します。
TsgcHTTPRESTServer はすでに利用できます。最新のビルドは sgcWebSockets のダウンロードページから入手してください。
