TsgcHTTPRESTServer: the New REST Server Component | eSeGeCe Blog

TsgcHTTPRESTServer: the New REST Server Component

· Components
sgcWebSockets TsgcHTTPRESTServer component

Building a REST API on top of TsgcHTTPServer has always been possible, but a few things had to be written by hand every single time: the CORS preflight answer, a health endpoint for the load balancer, a metrics endpoint for the monitoring stack, and some way of telling one customer's data from another's. TsgcHTTPRESTServer is a new component that ships all of that in the box.

It descends directly from TsgcHTTPServer, so everything you already know still applies: the same Port, the same SSLOptions, the same Authentication, the same OnCommandGet handler. TsgcHTTPServer stays a plain HTTP server and does not change. The extras live in the descendant, and each one is opt in.

Getting started

The component lives in the sgcHTTP_REST_Server unit and is registered on the SGC REST palette page as TsgcHTTPRESTServer. Dropping it on a form and setting Active gives you a working HTTP server; the interesting part is the request handler.

uses
  sgcHTTP_REST_Server;

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

The handler uses the standard Indy request and response objects, so a JSON answer is three assignments:

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 receives GET and POST. Verbs such as PUT, PATCH and DELETE arrive in OnCommandOther, which has exactly the same signature, so a REST resource that supports the full set of verbs is usually written as one dispatch routine called from both events.

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

CORS, off by default on purpose

A browser calling your API from another origin needs the Access-Control-* headers, and it needs an answer to the OPTIONS preflight before it will send the real request. CORSOptions handles both.

The one thing worth stressing is that Enabled is False by default and is meant to stay that way unless you actually need it. A server that quietly started answering with Access-Control-Allow-Origin: * after an upgrade would be a security regression, so CORS is strictly opt 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';

With this in place the preflight is answered automatically with a 204 and the three headers, and every normal response gets the headers added. You do not write an OPTIONS branch in your handler.

Prefer an explicit origin over * whenever the API is authenticated. A wildcard origin and credentials are a combination browsers reject anyway, and listing the origins you actually serve is the safer default.

Health and metrics without writing them

Attach a TsgcHTTPServerStats component to the ServerStats property and the server can answer two operational endpoints. Both are disabled until you enable them individually.

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

/health answers a small JSON document suitable for a load balancer probe:

{"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 answers Prometheus text exposition format, so it can be scraped with no adapter in between:

# 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

Both paths are configurable if /metrics and /health clash with your own routes:

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

These endpoints are served after the authentication gate, so they inherit whatever authentication the server already enforces. They are never public unless the server itself is public. That is worth knowing in both directions: it keeps them private by default, and it means a monitoring scraper needs credentials when the server requires them.

Serving static content alongside the API

DocumentRoot is inherited from TsgcHTTPServer and still works, so a single server can host a small front end and the API it talks to. Anything your handler does not answer falls through to the document root.

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

TLS

Nothing changes from the base server. Set SSL and fill SSLOptions as usual:

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

What comes next

The component also publishes a Tenancy property and a read-only Tenant property, which turn a single server into a multi-customer one, and the Authentication options accept a user store component so you do not have to keep credentials in a TStringList. Those are covered in the second article, and the third shows how to put an OpenAPI contract in front of the whole thing so the routes, the validation and the documentation all come from one file.

TsgcHTTPRESTServer is available now. Download the latest build from the sgcWebSockets download page.