İlk makale TsgcHTTPRESTServer bileşenini ve istek işlemeyi tanıtmıştı. Bu makale, çalışan bir uç noktayı gerçekten müşterilerin önüne koyabileceğiniz bir şeye dönüştüren üç yardımcı bileşeni ele alıyor: bir kullanıcı deposu, çok kiracılılık ve operasyon ekibinin daha ilk gün isteyeceği metrikler.
Üçü de ayrı bileşenlerdir. İhtiyaç duyduklarınızı oluşturup sunucuya atarsınız ve atamadan bıraktığınız her bileşenin maliyeti istek başına tek bir işaretçi kontrolüdür.
TStringList yerine bir kullanıcı deposu
TsgcHTTPServer_Users, hesapları tuzlanmış ve yinelemeli parola karma değerleriyle saklar. Önemli olan, ne kadar az bağlantı gerektirdiğidir: bileşeni sunucunun kimlik doğrulama seçeneklerine atayın, HTTP Basic kapısı kimlik bilgilerini kendiliğinden bu depoya karşı çözümlesin.
uses
sgcHTTP_REST_Server, sgcHTTP_REST_Server_Users;
FUsers := TsgcHTTPServer_Users.Create(self);
FServer := TsgcHTTPRESTServer.Create(self);
FServer.Authentication.Users := FUsers;
Hepsi bu kadar. Basic kimlik doğrulamasının çalışması için bir OnAuthentication olayına ihtiyacınız yoktur, aramayı ve karma karşılaştırmasını kapı yapar.
Hesaplar, oluşturulan kullanıcı kimliğini döndüren AddUser ile eklenir; kullanıcı adı boşsa veya zaten alınmışsa boş bir dize döner:
FUsers.AddUser('alice', 'secret123', 'admin,reader');
FUsers.AddUser('bob', 'secret456', 'reader');
Yüzeyin geri kalanı beklediğiniz gibidir ve her arama iş parçacığı güvenlidir:
if FUsers.ValidateCredentials('alice', 'secret123') then
...
FUsers.SetPassword('bob', 'newsecret');
FUsers.EnableUser('bob', False);
FUsers.DeleteUser('bob');
Roller
Roller, hesap üzerinde virgülle ayrılmış bir listedir; okumak ve test etmek için yardımcılar vardır. Bir işleyicideki rol kapısı tek bir çağrıdır:
FUsers.AddRole('bob', 'admin');
if not FUsers.UserHasRole(vUser, 'admin') then
begin
AResponseInfo.ResponseNo := 403;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := '{"error":"forbidden"}';
Exit;
end;
Karma alma
Parolalar varsayılan olarak 10000 yineleme üzerinden SHA-512 ile karma alınır ve her hesap kendi rastgele tuzunu taşır. Hesap başına bu tuz, aynı iki parolanın zaten farklı karma değerlere sahip olmasını sağlayan şeydir, bu yüzden buna nadiren dokunmanız gerekir:
FUsers.Hashing.Algorithm := uhaSHA512;
FUsers.Hashing.Iterations := 10000;
Salt özelliği bambaşka bir şeydir ve dikkatle okunmayı hak eder. Hesap başına tuzun üzerine, her karma değere karıştırılan isteğe bağlı bir biber değeridir ve bilinçli olarak varsayılan boştur: oluşturma anında üretilen bir değer bir sonraki çalıştırmada farklı olurdu ve o zaman her doğru parola, açıklayacak hiçbir hata olmadan reddedilirdi. Bunu yalnızca kullanıcı deposunun kendisi dışında bir yerden gelebildiğinde ayarlayın, örneğin bir ortam değişkeninden veya bir anahtar kasasından. Koruduğu karma değerlerin yanında tutulan bir biber hiçbir şey katmaz. Değeri değiştirmek, daha önce saklanan her parolayı geçersiz kılar.
Kalıcılık
Depo varsayılan olarak bellektedir. Bir dosyaya yönlendirin, yeniden başlatmayı atlatsın:
FUsers.Storage.StorageType := ustFile;
FUsers.Storage.FileName := 'sgcRESTUsers.dat';
FUsers.Storage.EncryptAtRest := True;
FUsers.Storage.EncryptionKey := GetKeyFromEnvironment;
FUsers.Storage.AutoSaveSeconds := 30;
FUsers.LoadUsers;
AutoSaveSeconds ayarlandığında depo kendini düzenli aralıklarla diske yazar; aksi halde size uyduğunda SaveUsers veya SaveToFile çağırın. Zaten kendi veritabanınızda bulunan bir depo için StorageType özelliğini ustCustom olarak ayarlayın ve bunun yerine olayları yanıtlayın; burada OnValidateCredentials belirleyici hale gelir:
procedure TForm1.UsersValidateCredentials(Sender: TObject;
const aUsername, aPassword: string; var Valid: Boolean);
begin
Valid := MyDatabase.CheckLogin(aUsername, aPassword);
end;
Bir yönetim ekranı oluşturuyorsanız önemli olan bir ayrıntı: GetUserByIndex, kaydı geri vermeden önce PasswordHash ve Salt alanlarını boşaltır, böylece bir listeleme rotası kazara bile olsa bir kimlik bilgisini sızdıramaz. FindUser bunu yapmaz. Kaydı, kimlik bilgisi alanları dahil olmak üzere depolandığı biçimde aynen döndürür, çünkü kimlik doğrulama kapısının kendisinin kullandığı arama budur. Ondan yalnızca ihtiyaç duyduğunuz alanları okuyun ve kaydın tamamını asla bir yanıt gövdesine serileştirmeyin.
Çok kiracılılık
TsgcHTTPServer_Tenancy istek başına tek bir soruyu yanıtlar: bu istek hangi müşteri için? İşleyiciniz çalışmadan önce bir kiracı dizesi çözümler ve sunucu bunu salt okunur Tenant özelliğinde sunar.
FTenancy := TsgcHTTPServer_Tenancy.Create(self);
FTenancy.Resolution := trHeader;
FTenancy.HeaderName := 'X-Tenant-Id';
FTenancy.DefaultTenant := 'public';
FServer.Tenancy := FTenancy;
Beş çözümleme modu vardır:
| Çözümleme | Kaynak | Yapılandırıldığı özellik |
|---|---|---|
trNone | kapalı, Tenant her zaman boştur | — |
trHost | ana bilgisayar adı | HostSuffix |
trPath | istek yolunun bir segmenti | PathSegmentIndex |
trHeader | bir istek başlığı | HeaderName |
trJWTClaim | taşıyıcı belirtecin bir talebi | ClaimName |
trHost ile ve HostSuffix değeri .example.com olarak ayarlandığında, acme.example.com adresine gelen bir istek acme olarak çözümlenir. trPath ile ve PathSegmentIndex değeri 0 olduğunda, /acme/api/orders aynı sonucu verir. Yapılandırılan kaynak hiçbir şey üretmediğinde DefaultTenant kullanılır.
Bunu bir işleyicide okumak yalnızca bir özellik erişimidir:
procedure TForm1.ServerCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := '{"tenant":"' + FServer.Tenant + '"}';
end;
Tenant özelliği OnBeforeCommand, OnCommandGet ve OnCommandOther içinde geçerlidir ve iş parçacığına özeldir, dolayısıyla aynı anda birçok kiracıya hizmet veren yoğun bir sunucu bunları asla birbirine karıştırmaz.
trJWTClaim konusunda açık olunması gereken bir nokta var. Talep, belirteç yükünden imza doğrulanmadan okunur, çünkü kiracı yalnızca bir yönlendirme ipucudur. İmza, etkinleştirdiğinizde JWT kimlik doğrulaması tarafından yine de kontrol edilir. Kiracıyı tek başına bir kimlik kanıtı olarak görmeyin.
Beş moddan hiçbiri uymuyorsa, OnResolveTenant tüm kaynakları aynı anda size verir ve kararı size bırakır:
procedure TForm1.TenancyResolveTenant(Sender: TObject;
const aHost, aPath, aHeaderValue, aJWTPayload: string; var aTenant: string);
begin
aTenant := LookupTenantForHost(aHost);
end;
Metrikler ve sağlık
TsgcHTTPServerStats, sunucunun ne yaptığını sayar ve bunu iki uç noktada yayımlayabilir. Her ikisi de siz tek tek etkinleştirene kadar kapalıdır, böylece bileşeni forma bıraktınız diye hiçbir şey erişilebilir hale gelmez.
FStats := TsgcHTTPServerStats.Create(self);
FStats.Endpoints.Metrics.Enabled := True;
FStats.Endpoints.Health.Enabled := True;
FServer.ServerStats := FStats;
Sayaçlar koddan da okunabilir; bu, dahili bir durum ekranı için kullanışlıdır:
lblRequests.Caption := IntToStr(FStats.TotalRequests);
lblErrors.Caption := IntToStr(FStats.Status5xx);
lblLatency.Caption := IntToStr(FStats.LatencyAvgMs) + ' ms';
lblUptime.Caption := IntToStr(FStats.UptimeSeconds) + ' s';
/metrics doğrudan Prometheus metin sunum biçiminde yanıt verir, arada bir dışa aktarıcı yoktur. Uç nokta başına sayaçlar da dahildir; kardinalite 256 farklı yolla sınırlıdır, böylece içinde bir kimlik bulunan bir rota seri sayısını şişiremez. Sınırın ötesindeki her şey other altında gruplanır.
# HELP sgc_server_endpoint_requests_total Requests per endpoint
# TYPE sgc_server_endpoint_requests_total counter
sgc_server_endpoint_requests_total{endpoint="/api/orders"} 3120
sgc_server_endpoint_requests_total{endpoint="/api/users"} 845
Güvenlik duvarı, hız sınırlayıcı, devre kesici veya API anahtarı yöneticisi bileşenleri bağlıysa, onların kendi metrikleri de aynı çıktıya eklenir:
FStats.RateLimiter := FRateLimiter;
FStats.Firewall := FFirewall;
/health kompakt bir JSON belgesiyle yanıt verir. status alanı ok okunur veya bir devre kesici bağlıysa ve açık kesiciler varsa degraded okunur; bu da onu doğrudan bir yük dengeleyici yoklaması olarak kullanılabilir kılar.
Son olarak, sayıları kendiniz bir yere göndermeyi tercih ederseniz OnStats olayı istatistik nesnesinin tamamıyla tetiklenir:
procedure TForm1.StatsEvent(Sender: TObject;
const aStats: TsgcHTTPServerStats);
begin
MyTelemetry.Send(aStats.TotalRequests, aStats.LatencyAvgMs);
end;
Hepsini bir araya getirmek
Üçünün de bağlı olduğu bir sunucu yaklaşık on iki satırdır ve her bileşen diğerlerinden bağımsız kalır:
FStats := TsgcHTTPServerStats.Create(self);
FStats.Endpoints.Health.Enabled := True;
FStats.Endpoints.Metrics.Enabled := True;
FTenancy := TsgcHTTPServer_Tenancy.Create(self);
FTenancy.Resolution := trHeader;
FUsers := TsgcHTTPServer_Users.Create(self);
FUsers.Storage.StorageType := ustFile;
FUsers.Storage.FileName := 'users.dat';
FUsers.LoadUsers;
FServer := TsgcHTTPRESTServer.Create(self);
FServer.ServerStats := FStats;
FServer.Tenancy := FTenancy;
FServer.Authentication.Users := FUsers;
FServer.Authentication.Enabled := True;
FServer.Port := 5876;
FServer.Active := True;
Sonraki makale bu sunucunun önüne bir OpenAPI sözleşmesi koyuyor, böylece yönlendirme, doğrulama ve belgelerin tamamı spesifikasyondan geliyor.
En son derlemeyi sgcWebSockets indirme sayfasından indirin.
