Costruire una API REST sopra TsgcHTTPServer è sempre stato possibile, ma alcune cose andavano scritte a mano ogni singola volta: la risposta al preflight CORS, un endpoint di health per il load balancer, un endpoint di metriche per lo stack di monitoraggio, e un modo qualsiasi per distinguere i dati di un cliente da quelli di un altro. TsgcHTTPRESTServer è un nuovo componente che porta tutto questo già pronto.
Discende direttamente da TsgcHTTPServer, quindi tutto quello che già conosci resta valido: la stessa Port, le stesse SSLOptions, la stessa Authentication, lo stesso handler OnCommandGet. TsgcHTTPServer resta un semplice server HTTP e non cambia. Le funzionalità aggiuntive vivono nel discendente, e ognuna va abilitata esplicitamente.
Per iniziare
Il componente si trova nell'unit sgcHTTP_REST_Server ed è registrato nella pagina della palette SGC REST come TsgcHTTPRESTServer. Basta rilasciarlo su una form e impostare Active per avere un server HTTP funzionante; la parte interessante è il gestore delle richieste.
uses
sgcHTTP_REST_Server;
var
oServer: TsgcHTTPRESTServer;
begin
oServer := TsgcHTTPRESTServer.Create(nil);
oServer.Port := 8080;
oServer.OnCommandGet := OnServerCommandGet;
oServer.Active := True;
end;
Il gestore usa gli oggetti request e response standard di Indy, quindi una risposta JSON sono tre assegnazioni:
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 riceve GET e POST. Verbi come PUT, PATCH e DELETE arrivano in OnCommandOther, che ha esattamente la stessa firma, quindi una risorsa REST che supporta l'insieme completo dei verbi si scrive di solito come un'unica routine di dispatch chiamata da entrambi gli eventi.
procedure TForm1.OnServerCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Command = 'DELETE' then
begin
AResponseInfo.ResponseNo := 204;
AResponseInfo.ContentText := '';
end;
end;
CORS, disattivato di default e non per caso
Un browser che chiama la tua API da un'altra origine ha bisogno degli header Access-Control-*, e ha bisogno di una risposta al preflight OPTIONS prima di inviare la richiesta vera. CORSOptions gestisce entrambe le cose.
L'unico punto su cui vale la pena insistere è che Enabled è False di default e dovrebbe restare tale a meno che non serva davvero. Un server che dopo un aggiornamento iniziasse silenziosamente a rispondere con Access-Control-Allow-Origin: * sarebbe una regressione di sicurezza, quindi CORS va abilitato in modo strettamente esplicito.
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';
Con questa configurazione il preflight riceve automaticamente una risposta 204 con i tre header, e a ogni risposta normale gli header vengono aggiunti. Non devi scrivere un ramo OPTIONS nel tuo gestore.
Preferisci un'origine esplicita a * ogni volta che l'API è autenticata. Un'origine wildcard insieme alle credenziali è comunque una combinazione che i browser rifiutano, ed elencare le origini che servi davvero è l'impostazione più sicura.
Health e metriche senza scriverle
Collega un componente TsgcHTTPServerStats alla proprietà ServerStats e il server può rispondere a due endpoint operativi. Entrambi sono disattivati finché non li abiliti singolarmente.
oStats := TsgcHTTPServerStats.Create(nil);
oStats.Endpoints.Health.Enabled := True;
oStats.Endpoints.Metrics.Enabled := True;
oServer.ServerStats := oStats;
/health risponde con un piccolo documento JSON adatto a una sonda di load balancer:
{"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 risponde nel formato di esposizione testuale di Prometheus, quindi può essere raccolto senza alcun adattatore intermedio:
# 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
Entrambi i percorsi sono configurabili se /metrics e /health vanno in conflitto con le tue rotte:
oStats.Endpoints.Metrics.Path := '/internal/metrics';
oStats.Endpoints.Health.Path := '/internal/health';
Questi endpoint vengono serviti dopo il controllo di autenticazione, quindi ereditano qualsiasi autenticazione il server già imponga. Non sono mai pubblici a meno che non lo sia il server stesso. È bene saperlo in entrambe le direzioni: li mantiene privati di default, e significa che uno scraper di monitoraggio ha bisogno delle credenziali quando il server le richiede.
Servire contenuto statico accanto all'API
DocumentRoot è ereditata da TsgcHTTPServer e funziona ancora, quindi un singolo server può ospitare un piccolo front end e l'API con cui dialoga. Tutto ciò a cui il tuo gestore non risponde ricade sul document root.
oServer.DocumentRoot := 'C:\www\app';
oServer.HTTPCompression.Enabled := True;
oServer.HTTPCompression.MinSize := 1024;
TLS
Non cambia nulla rispetto al server base. Imposta SSL e compila SSLOptions come al solito:
oServer.SSL := True;
oServer.SSLOptions.Port := 443;
oServer.Port := 443;
Cosa arriva dopo
Il componente pubblica anche una proprietà Tenancy e una proprietà a sola lettura Tenant, che trasformano un singolo server in uno multi-cliente, e le opzioni di Authentication accettano un componente di archiviazione utenti così non devi più tenere le credenziali in una TStringList. Questi argomenti sono trattati nel secondo articolo, mentre il terzo mostra come mettere un contratto OpenAPI davanti a tutto l'insieme, così che le rotte, la validazione e la documentazione arrivino da un unico file.
TsgcHTTPRESTServer è disponibile da subito. Scarica l'ultima build dalla pagina di download di sgcWebSockets.
