Budowanie API REST na bazie TsgcHTTPServer zawsze było możliwe, ale kilka rzeczy trzeba było za każdym razem pisać ręcznie: odpowiedź na zapytanie wstępne CORS, punkt końcowy kondycji dla load balancera, punkt końcowy metryk dla systemu monitorowania oraz jakiś sposób na odróżnienie danych jednego klienta od danych drugiego. TsgcHTTPRESTServer to nowy komponent, który dostarcza to wszystko od razu.
Dziedziczy bezpośrednio po TsgcHTTPServer, więc wszystko, co już znasz, nadal obowiązuje: ten sam Port, te same SSLOptions, to samo Authentication, ta sama procedura obsługi OnCommandGet. TsgcHTTPServer pozostaje zwykłym serwerem HTTP i nie zmienia się. Dodatki żyją w klasie potomnej, a każdy z nich włącza się osobno.
Pierwsze kroki
Komponent znajduje się w module sgcHTTP_REST_Server i jest zarejestrowany na stronie palety SGC REST jako TsgcHTTPRESTServer. Upuszczenie go na formularzu i ustawienie Active daje działający serwer HTTP; ciekawa część to procedura obsługi żądań.
uses
sgcHTTP_REST_Server;
var
oServer: TsgcHTTPRESTServer;
begin
oServer := TsgcHTTPRESTServer.Create(nil);
oServer.Port := 8080;
oServer.OnCommandGet := OnServerCommandGet;
oServer.Active := True;
end;
Procedura obsługi korzysta ze standardowych obiektów żądania i odpowiedzi Indy, więc odpowiedź JSON to trzy przypisania:
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 odbiera GET i POST. Metody takie jak PUT, PATCH i DELETE trafiają do OnCommandOther, które ma dokładnie tę samą sygnaturę, więc zasób REST obsługujący pełny zestaw metod pisze się zazwyczaj jako jedną procedurę rozdzielającą, wywoływaną z obu zdarzeń.
procedure TForm1.OnServerCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Command = 'DELETE' then
begin
AResponseInfo.ResponseNo := 204;
AResponseInfo.ContentText := '';
end;
end;
CORS, domyślnie wyłączony i to celowo
Przeglądarka wywołująca Twoje API z innego źródła potrzebuje nagłówków Access-Control-* oraz odpowiedzi na zapytanie wstępne OPTIONS, zanim wyśle właściwe żądanie. CORSOptions obsługuje jedno i drugie.
Warto podkreślić jedną rzecz: Enabled ma domyślnie wartość False i powinno takie pozostać, dopóki naprawdę tego nie potrzebujesz. Serwer, który po aktualizacji po cichu zacząłby odpowiadać nagłówkiem Access-Control-Allow-Origin: *, byłby regresją bezpieczeństwa, dlatego CORS włącza się wyłącznie świadomie.
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';
Przy takiej konfiguracji zapytanie wstępne jest obsługiwane automatycznie odpowiedzią 204 z trzema nagłówkami, a każda zwykła odpowiedź otrzymuje te nagłówki. Nie piszesz gałęzi OPTIONS w swojej procedurze obsługi.
Zawsze, gdy API jest uwierzytelniane, wybieraj jawne źródło zamiast *. Źródło z symbolem wieloznacznym w połączeniu z poświadczeniami i tak jest odrzucane przez przeglądarki, a wypisanie źródeł, które faktycznie obsługujesz, jest bezpieczniejszym ustawieniem domyślnym.
Kondycja i metryki bez pisania kodu
Podłącz komponent TsgcHTTPServerStats do właściwości ServerStats, a serwer będzie mógł obsługiwać dwa punkty końcowe operacyjne. Oba są wyłączone, dopóki nie włączysz ich pojedynczo.
oStats := TsgcHTTPServerStats.Create(nil);
oStats.Endpoints.Health.Enabled := True;
oStats.Endpoints.Metrics.Enabled := True;
oServer.ServerStats := oStats;
/health zwraca niewielki dokument JSON, odpowiedni dla sondy load balancera:
{"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 zwraca dane w tekstowym formacie ekspozycji Prometheus, więc mogą być zbierane bez żadnego adaptera pośredniego:
# 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
Obie ścieżki można skonfigurować, jeśli /metrics i /health kolidują z Twoimi własnymi trasami:
oStats.Endpoints.Metrics.Path := '/internal/metrics';
oStats.Endpoints.Health.Path := '/internal/health';
Te punkty końcowe są obsługiwane po bramce uwierzytelniania, więc dziedziczą to uwierzytelnianie, które serwer już wymusza. Nigdy nie są publiczne, o ile publiczny nie jest sam serwer. Warto o tym pamiętać w obie strony: dzięki temu domyślnie pozostają prywatne, ale też kolektor monitorowania potrzebuje poświadczeń, gdy serwer ich wymaga.
Serwowanie treści statycznych obok API
DocumentRoot jest dziedziczony z TsgcHTTPServer i nadal działa, więc jeden serwer może hostować niewielki interfejs użytkownika oraz API, z którym ten interfejs się komunikuje. Wszystko, czego nie obsłuży Twoja procedura, trafia do katalogu dokumentów.
oServer.DocumentRoot := 'C:\www\app';
oServer.HTTPCompression.Enabled := True;
oServer.HTTPCompression.MinSize := 1024;
TLS
Nic się nie zmienia względem serwera bazowego. Ustaw SSL i wypełnij SSLOptions jak zwykle:
oServer.SSL := True;
oServer.SSLOptions.Port := 443;
oServer.Port := 443;
Co dalej
Komponent publikuje także właściwość Tenancy oraz właściwość Tenant tylko do odczytu, które zamieniają pojedynczy serwer w serwer obsługujący wielu klientów, a opcje Authentication przyjmują komponent magazynu użytkowników, dzięki czemu nie musisz trzymać poświadczeń w TStringList. Omawia je drugi artykuł, a trzeci pokazuje, jak postawić przed tym wszystkim kontrakt OpenAPI, tak aby trasy, walidacja i dokumentacja pochodziły z jednego pliku.
TsgcHTTPRESTServer jest już dostępny. Pobierz najnowszą wersję ze strony pobierania sgcWebSockets.
