TsgcHTTPRESTServer: de nieuwe REST-servercomponent | eSeGeCe Blog

TsgcHTTPRESTServer: de nieuwe REST-servercomponent

· Componenten
sgcWebSockets TsgcHTTPRESTServer component

Een REST API bouwen bovenop TsgcHTTPServer was altijd al mogelijk, maar een paar dingen moest u telkens weer met de hand schrijven: het antwoord op de CORS-preflight, een health-endpoint voor de load balancer, een metrics-endpoint voor de monitoringstack, en een manier om de gegevens van de ene klant van die van de andere te onderscheiden. TsgcHTTPRESTServer is een nieuwe component die dat allemaal kant-en-klaar meelevert.

De component erft rechtstreeks van TsgcHTTPServer, dus alles wat u al kent blijft gelden: dezelfde Port, dezelfde SSLOptions, dezelfde Authentication, dezelfde OnCommandGet-handler. TsgcHTTPServer blijft een gewone HTTP-server en verandert niet. De extra's zitten in de afgeleide klasse, en elk daarvan schakelt u zelf in.

Aan de slag

De component zit in de unit sgcHTTP_REST_Server en is geregistreerd op de palettepagina SGC REST als TsgcHTTPRESTServer. Zet hem op een formulier, stel Active in en u hebt een werkende HTTP-server; het interessante deel is de request-handler.

uses
  sgcHTTP_REST_Server;

var
  oServer: TsgcHTTPRESTServer;
begin
  oServer := TsgcHTTPRESTServer.Create(nil);
  oServer.Port := 8080;
  oServer.OnCommandGet := OnServerCommandGet;
  oServer.Active := True;
end;

De handler gebruikt de standaard request- en response-objecten van Indy, dus een JSON-antwoord bestaat uit drie toewijzingen:

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 ontvangt GET en POST. Methoden zoals PUT, PATCH en DELETE komen binnen in OnCommandOther, dat exact dezelfde signatuur heeft, dus een REST-resource die de volledige set methoden ondersteunt schrijft u meestal als één dispatch-routine die vanuit beide events wordt aangeroepen.

procedure TForm1.OnServerCommandOther(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  if ARequestInfo.Command = 'DELETE' then
  begin
    AResponseInfo.ResponseNo := 204;
    AResponseInfo.ContentText := '';
  end;
end;

CORS, met opzet standaard uit

Een browser die uw API vanaf een andere origin aanroept heeft de Access-Control-*-headers nodig, en heeft een antwoord op de OPTIONS-preflight nodig voordat hij het echte request verstuurt. CORSOptions regelt beide.

Het enige dat de nadruk verdient is dat Enabled standaard False is en dat ook hoort te blijven tenzij u het echt nodig hebt. Een server die na een upgrade stilletjes zou gaan antwoorden met Access-Control-Allow-Origin: * zou een beveiligingsregressie zijn, dus CORS schakelt u strikt zelf in.

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';

Hiermee wordt de preflight automatisch beantwoord met een 204 en de drie headers, en bij elk normaal antwoord worden de headers toegevoegd. U schrijft geen OPTIONS-tak in uw handler.

Geef de voorkeur aan een expliciete origin boven * zodra de API geauthenticeerd is. Een wildcard-origin met credentials is een combinatie die browsers toch weigeren, en de origins opsommen die u werkelijk bedient is de veiligere standaard.

Health en metrics zonder ze te schrijven

Koppel een TsgcHTTPServerStats-component aan de eigenschap ServerStats en de server kan twee operationele endpoints beantwoorden. Beide staan uit totdat u ze afzonderlijk inschakelt.

oStats := TsgcHTTPServerStats.Create(nil);
oStats.Endpoints.Health.Enabled := True;
oStats.Endpoints.Metrics.Enabled := True;
oServer.ServerStats := oStats;

/health antwoordt met een klein JSON-document dat geschikt is als probe voor een 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 antwoordt in het Prometheus text exposition-formaat, zodat het zonder tussenliggende adapter gescrapet kan worden:

# 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

Beide paden zijn instelbaar als /metrics en /health botsen met uw eigen routes:

oStats.Endpoints.Metrics.Path := '/internal/metrics';
oStats.Endpoints.Health.Path := '/internal/health';

Deze endpoints worden na de authenticatiepoort bediend, dus ze erven de authenticatie die de server al afdwingt. Ze zijn nooit publiek tenzij de server zelf publiek is. Dat is in beide richtingen goed om te weten: het houdt ze standaard privé, en het betekent dat een monitoring-scraper credentials nodig heeft wanneer de server die vereist.

Statische content naast de API bedienen

DocumentRoot wordt geërfd van TsgcHTTPServer en werkt nog steeds, dus één server kan een kleine front-end hosten samen met de API waarmee die praat. Alles wat uw handler niet beantwoordt valt door naar de document root.

oServer.DocumentRoot := 'C:\www\app';
oServer.HTTPCompression.Enabled := True;
oServer.HTTPCompression.MinSize := 1024;

TLS

Ten opzichte van de basisserver verandert er niets. Stel SSL in en vul SSLOptions zoals gebruikelijk:

oServer.SSL := True;
oServer.SSLOptions.Port := 443;
oServer.Port := 443;

Wat er hierna komt

De component publiceert ook een Tenancy-eigenschap en een alleen-lezen Tenant-eigenschap, die van één server een server voor meerdere klanten maken, en de Authentication-opties accepteren een user store-component zodat u credentials niet in een TStringList hoeft te bewaren. Die komen aan bod in het tweede artikel, en het derde laat zien hoe u een OpenAPI-contract voor het geheel plaatst zodat de routes, de validatie en de documentatie allemaal uit één bestand komen.

TsgcHTTPRESTServer is nu beschikbaar. Download de nieuwste build van de sgcWebSockets-downloadpagina.