Një Reverse Proxy me nginx dhe Delphi në praktikë shpesh nuk është një „nice to have“, por ndarja e pastër midis skajit të internetit dhe aplikacionit: terminimi i TLS (HTTPS-Offloading), rregulla qendrore për Header/CORS, Rate-Limits, logje të unifikuara, Blue/Green-Rollouts ose thjesht hostim i disa shërbimeve nën një domen. Ajo që shpesh nënvlerësohet: sapo nginx të jetë „para“ tij, serveri Delphi sheh vetëm IP-në e proxy-t, shpesh vetëm „http“ në vend të „https“ dhe gjeneron lidhje absolute të gabuara (Redirects, Callback-URL, OpenAPI-Server-URL). Pikërisht këto tre pika shkaktojnë më vonë kohë debug-u në operim.
Kjo copë kodi tregon një model të fortë se si të analizoni saktë në Delphi header-at Forwarded dhe X-Forwarded-* – përfshirë një listë Trust-Proxy (e rëndësishme kundër Header-Spoofing) dhe një Request-Base-URL konsistente. Për më tepër ka konfigurime nginx të përshtatshme për përdorim praktik dhe vëzhgime për raste kufitare si WebSockets, ngarkesa të mëdha dhe Timeouts.
Pse konfigurimet me Reverse Proxy «ngatërrojnë» serverat Delphi
nginx, si Reverse Proxy, zakonisht komunikon me shërbimin Delphi të pambrojtur (HTTP) në rrjetin e brendshëm ose në localhost, ndërsa klienti vjen nga jashtë me HTTPS. Pa header-e shtesë, Delphi nuk di asgjë për:
- Skemën origjinale (https vs. http) – e rëndësishme për Redirects dhe URL-të absolute.
- Host-in origjinal (domeni i klientit, porti) – i rëndësishëm për konfigurime multi-tenant, CORS dhe Callback-URL-të.
- IP-në origjinale të klientit – e rëndësishme për auditim, Rate-Limits, kontrolle gjeografike dhe analiza të sigurisë.
nginx mund të përcjellë këto informacione përmes header-eve. Të zakonshme janë X-Forwarded-For, X-Forwarded-Proto dhe X-Forwarded-Host; standardi RFC shton edhe header-in Forwarded. E rëndësishme: këto header-e nga perspektiva e aplikacionit nuk janë automatikisht të besueshme, sepse një klient mund t’i dërgojë vetë – ato bëhen të besueshme vetëm kur vijnë nga një proxy i njohur.
nginx-Konfiguration: die minimal sinnvollen Proxy-Header
Një pikënisje e fortë (HTTP/1.1, Keep-Alive, Upgrade për WebSockets) duket kështu. Snippet-i është qëllimisht i shkurtër; sipas mjedisit plotësoni HSTS, Rate-Limits dhe Access-Logs.
# (nginx-Konfiguration, kein Delphi)
server {
listen 443 ssl http2;
server_name api.example.com;
# ssl_certificate ...; ssl_certificate_key ...;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# optional, aber praktisch für absolute URLs
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# WebSockets / Upgrade
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://127.0.0.1:8080;
# Timeouts passend zu Delphi-Backend (lange Reports/Exports)
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# große Uploads explizit steuern
client_max_body_size 50m;
}
}
Qëllimi: Aplikacioni merr me besueshmëri Host, IP-në e klientit dhe skemën. Kusht kufizues: $proxy_add_x_forwarded_for shton IP-në aktuale të proxy-t në një zinxhir ekzistues; kjo është e dobishme për konfigurime me shumë proxy, por e bën analizën e saktë në anën e Delphi edhe më të rëndësishme. Kujdes: Nëse në nginx nuk vendosni Header-in Host, Delphi mund të shohë vetëm host-in e upstream (127.0.0.1), gjë që prish ridrejtimet dhe kontrollet e origjinës.
Delphi Fragment kodi burimor: Vlerësim i robust i Forwarded/X-Forwarded (me listën e proxy-ve të besuara)
Kodi vijues është qëllimisht i pavarur nga framework: Ai punon mbi një ndërfaqe minimale (Header + RemoteIP) dhe mund të adaptohet në WebBroker, RAD Server ose Horse. Pikat kryesore:
- Prioriteti: RFC Forwarded (nëse ekziston) para X-Forwarded-*.
- Besimi: Forwarded-Header vlerësohen vetëm nëse peer-i i drejtëpërdrejtë (RemoteIP) është një proxy i njohur.
- Analiza: Merrni parasysh IPv6, thonjëzat, portet dhe zinxhirët në X-Forwarded-For.
- Dalja: një Base-URL që mund ta përdorni për lidhje absolute, ridrejtime ose OpenAPI.
unit Net-Base.ProxyForwarding;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections, System.NetEncoding;
type
// Ndërfaqe minimale e adapterit: implementojeni për WebBroker/Horse/etc.
IHeaderReader = interface
['{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}']
function GetHeaderValue(const AName: string): string;
function GetRemoteIP: string; // palë TCP direkte (zakonisht nginx)
end;
TForwardedInfo = record
ClientIP: string;
Proto: string; // http/https
Host: string;
Port: Integer;
function EffectiveScheme: string;
function EffectiveHostPort: string;
function BaseUrl: string; // p.sh. https://api.example.com
end;
TTrustedProxyList = class
private
FSet: TDictionary;
class function NormalizeIp(const AIP: string): string; static;
public
constructor Create;
destructor Destroy; override;
procedure Add(const AIP: string);
function Contains(const AIP: string): Boolean;
end;
function ResolveForwardedInfo(const Req: IHeaderReader; const TrustedProxies: TTrustedProxyList): TForwardedInfo;
implementation
function StripBrackets(const S: string): string;
begin
Result := S.Trim;
if (Result.StartsWith('[')) and (Result.EndsWith(']')) then
Result := Result.Substring(1, Result.Length - 2);
end;
function RemoveQuotes(const S: string): string;
var
T: string;
begin
T := S.Trim;
if (T.Length >= 2) and (((T[1] = '"') and (T[T.Length] = '"')) or ((T[1] = '''') and (T[T.Length] = ''''))) then
Result := T.Substring(1, T.Length - 2)
else
Result := T;
end;
function FirstCsvToken(const S: string): string;
var
P: Integer;
begin
// X-Forwarded-For mund të jetë "client, proxy1, proxy2"
P := S.IndexOf(',');
if P >= 0 then
Result := S.Substring(0, P).Trim
else
Result := S.Trim;
end;
procedure SplitHostPort(const HostPort: string; out Host: string; out Port: Integer);
var
S: string;
P: Integer;
begin
Host := '';
Port := 0;
S := HostPort.Trim;
// IPv6 në []: [2001:db8::1]:443
if S.StartsWith('[') then
begin
P := S.IndexOf(']');
if P >= 0 then
begin
Host := StripBrackets(S.Substring(0, P + 1));
if (P + 1 < S.Length) and (S[P + 2] = ':') then
Port := StrToIntDef(S.Substring(P + 2), 0);
Exit;
end;
end;
// IPv4/Host: host:port (Vërejtje: me IPv6 të papërpunuar pa [] nuk është i besueshëm)
P := S.LastIndexOf(':');
if (P > 0) and (S.IndexOf(':') = P) then
begin
Host := S.Substring(0, P);
Port := StrToIntDef(S.Substring(P + 1), 0);
end
else
Host := S;
end;
function ParseForwardedHeader(const ForwardedValue: string; out ClientIP, Proto, Host: string): Boolean;
var
// Shembull: Forwarded: for=203.0.113.43;proto=https;host=api.example.com
Parts: TArray<string>;
I: Integer;
KV: TArray<string>;
K, V: string;
FirstElement: string;
begin
Result := False;
ClientIP := '';
Proto := '';
Host := '';
if ForwardedValue.Trim = '' then
Exit;
// Elemente të shumta ndahen me presje; marrim të parin (më afër klientit)
FirstElement := FirstCsvToken(ForwardedValue);
Parts := FirstElement.Split([';']);
for I := 0 to High(Parts) do
begin
KV := Parts[I].Split(['='], 2);
if Length(KV) <> 2 then
Continue;
K := KV[0].Trim.ToLower;
V := RemoveQuotes(KV[1]);
if K = 'for' then
begin
// for mund të jetë IP ose "unknown"; IPv6 mund të jetë në []
V := V.Trim;
if SameText(V, 'unknown') then
Continue;
// for=1.2.3.4:5678 mund të ndodhë
if V.Contains(':') and (not V.StartsWith('[')) then
V := FirstCsvToken(V); // defensiv
ClientIP := StripBrackets(V.Split([':'])[0]);
end
else if K = 'proto' then
Proto := V.Trim.ToLower
else if K = 'host' then
Host := V.Trim;
end;
Result := (ClientIP <> '') or (Proto <> '') or (Host <> '');
end;
{ TForwardedInfo }
function TForwardedInfo.EffectiveScheme: string;
begin
if Proto <> '' then
Exit(Proto);
Result := 'http';
end;
function TForwardedInfo.EffectiveHostPort: string;
begin
if Host = '' then
Exit('');
if (Port > 0) and not ((EffectiveScheme = 'https') and (Port = 443)) and not ((EffectiveScheme = 'http') and (Port = 80)) then
Result := Host + ':' + Port.ToString
else
Result := Host;
end;
function TForwardedInfo.BaseUrl: string;
var
HP: string;
begin
HP := EffectiveHostPort;
if HP = '' then
Exit('');
Result := EffectiveScheme + '://' + HP;
end;
{ TTrustedProxyList }
constructor TTrustedProxyList.Create;
begin
inherited Create;
FSet := TDictionary<string, Boolean>.Create;
end;
destructor TTrustedProxyList.Destroy;
begin
FSet.Free;
inherited;
end;
class function TTrustedProxyList.NormalizeIp(const AIP: string): string;
begin
// Për normalizim të saktë të IPv6 kërkohet analizë e adresës IP; këtu jemi qëllimisht pragmatikë.
Result := AIP.Trim;
Result := StripBrackets(Result);
end;
procedure TTrustedProxyList.Add(const AIP: string);
begin
FSet.AddOrSetValue(NormalizeIp(AIP), True);
end;
function TTrustedProxyList.Contains(const AIP: string): Boolean;
begin
Result := FSet.ContainsKey(NormalizeIp(AIP));
end;
function ResolveForwardedInfo(const Req: IHeaderReader; const TrustedProxies: TTrustedProxyList): TForwardedInfo;
var
RemoteIP: string;
Fwd, XFF, XProto, XHost, XPort: string;
ClientIP, Proto, Host: string;
Port: Integer;
begin
Result := Default(TForwardedInfo);
RemoteIP := Req.GetRemoteIP;
Result.ClientIP := RemoteIP; // Rezervë: palë direkte
// Vetëm nëse palëja direkte është një proxy i njohur, analizojmë header-at Forwarded.
if (TrustedProxies <> nil) and TrustedProxies.Contains(RemoteIP) then
begin
Fwd := Req.GetHeaderValue('Forwarded');
if ParseForwardedHeader(Fwd, ClientIP, Proto, Host) then
begin
if ClientIP <> '' then Result.ClientIP := ClientIP;
if Proto <> '' then Result.Proto := Proto;
if Host <> '' then
begin
SplitHostPort(Host, Result.Host, Result.Port);
end;
end;
// X-Forwarded-* si rezervë/shtesë
XFF := Req.GetHeaderValue('X-Forwarded-For');
if (Result.ClientIP = RemoteIP) and (XFF.Trim <> '') then
Result.ClientIP := StripBrackets(FirstCsvToken(XFF));
XProto := Req.GetHeaderValue('X-Forwarded-Proto');
if (Result.Proto = '') and (XProto.Trim <> '') then
Result.Proto := FirstCsvToken(XProto).ToLower;
XHost := Req.GetHeaderValue('X-Forwarded-Host');
if (Result.Host = '') and (XHost.Trim <> '') then
Result.Host := FirstCsvToken(XHost);
XPort := Req.GetHeaderValue('X-Forwarded-Port');
Port := StrToIntDef(FirstCsvToken(XPort), 0);
if (Result.Port = 0) and (Port > 0) then
Result.Port := Port;
end;
// Si zgjidhje e fundit, marrim Host nga header-i "Host" (nëse nuk ka header proxy)
if Result.Host = '' then
begin
Host := Req.GetHeaderValue('Host');
if Host <> '' then
SplitHostPort(Host, Result.Host, Result.Port);
end;
end;
end.
Qëllimi: Merrni nga çdo kërkesë një pamje të qëndrueshme mbi ClientIP, Proto dhe Host si dhe një BaseUrl. Këto informacione mund t’i përdorni qendrorisht për logging, vendime sigurie (p.sh. lista e lejuar IP) dhe gjenerimin e lidhjeve.
Pse është e nevojshme lista e proxy-ve të besuar: Pa verifikimin e besimit, një sulmues mund të arrijë drejtpërdrejt portin tuaj Delphi (konfigurim i gabuar, routing i brendshëm, VPN) dhe thjesht të dërgojë X-Forwarded-For: 127.0.0.1. Kështu, audit-trails, rate-limits ose endpoint-et vetëm për përdorim të brendshëm do të ishin të cenueshëm. Besoni vetëm header-at Forwarded kur peer-i i drejtpërdrejtë (RemoteIP) është një proxy që kontrolloni (p.sh. 127.0.0.1, IP e load balancer-it, Kubernetes-Ingress).
Rreziqet e zakonshme: IPv6 pa kllapa katrore nuk është i qartë në notacionin Host:port. Në HTTP-Host-Header IPv6 zakonisht shënohet me []; respektojeni. Për rango IP komplekse (CIDR) do t’ju duhet të zgjeroni listën e besimit (p.sh. me një parsing real të IP-së).
Integrimi në WebBroker/Horse/RAD Server: ku kodi „andockt“
Në WebBroker (TWebRequest) header-at vijnë zakonisht përmes ContentFields ose GetFieldByName, Remote-IP në varësi të backend-it të serverit. Në Horse (ose në framework-e të tjera HTTP) zakonisht ka Req.Headers dhe një pronë Remote-IP. Rëndësi ka parimi: RemoteIP duhet të jetë peer-i TCP, jo ndonjë vlerë e header-it.
I provuar praktikisht: Gjatë nisjes së shërbimit krijoni një TTrustedProxyList nga konfigurimi (INI/ENV), p.sh. „127.0.0.1“ për konfigurime lokale me nginx ose IP-në e load balancer-it tuaj. Më pas thërrisni ResolveForwardedInfo për çdo request dhe shkruani fushat në logging-un tuaj të strukturuar (JSON-Log, Syslog ose Windows Event Log).
Debugimi gjatë operimit: si të gjeni gabimet në minuta në vend të orëve
Kur request-et duken „të çuditshme“, rrallë është fajt i Delphi-HTTP vetë, por i një kombinimi të header-ave të proxy-së, logjikës së redirect-it dhe timeouts. Tre kontrolle debug që ia vlen të bëhen në praktikë:
- Header-Dump (i synuar): Regjistroni në log në rastin e 4xx/5xx gjithashtu Host, Forwarded, X-Forwarded-For, X-Forwarded-Proto, User-Agent dhe Request-URI. Por vetëm për gabime – përndryshe logu bëhet i shtrenjtë dhe i paqartë.
- Kontrolloni Base-URL: Nëse redirects ose callback-URL dështojnë, regjistroni ForwardedInfo.BaseUrl. Shumë gabime bëhen të dukshme menjëherë („http://127.0.0.1“ në vend të „https://api…“).
- Korelacioni i timeout-eve: Një 504 nga proxy nuk është i njëjtë me një Delphi-timeout. nginx proxy_read_timeout dhe timeout-et Idle-/Read- nga ana e Delphi duhet të përputhen.
Raste të veçanta: WebSockets, Streaming dhe request-e të mëdha
WebSockets pas nginx
Për WebSockets nginx kërkon që Upgrade dhe Connection të jenë korrekte. Shtesë, backend-i nuk duhet të mbyllet „shumë herët“. Nga ana e Delphi është e rëndësishme që komponenta juaj WebSocket (ose një endpoint SSE/Streaming) të mund të trajtojë Reverse Proxies dhe të ketë implementuar si duhet Heartbeats/Keep-Alives.
Ngarkime të mëdha dhe gabimi 413
Një klasik: Delphi pranon një upload, por nginx e bllokon më parë me 413 Request Entity Too Large. Kontrollojeni këtë në mënyrë eksplicite me client_max_body_size dhe përshtatni limitet e request-eve nga ana e Delphi. Për zgjidhje software që operojnë afër procesit me dokumente ose të dhëna imazhi kjo nuk është një rast i veçantë, por operim normal.
HTTPS-Offloading und „Secure Cookies“
Nëse shërbimi juaj Delphi vendos cookie-t e sesionit, ato zakonisht duhet të shënohen si Secure kur HTTPS vjen nga jashtë. Nëse aplikacioni juaj e bën këtë, shpesh varet nga fakti nëse ai „di“ që kërkesa fillestare ishte HTTPS. Pikërisht këtu ndihmon vlerësimi i qëndrueshëm i X-Forwarded-Proto/Forwarded.
Kur ia vlen përpjekja – dhe ku mund të dështojë
Qasja e treguar ia vlen sa herë që shërbimi Delphi nuk qëndron më „i pambuluar“ në LAN, por është pjesë e një kante produktive: disa domene, ndërfaqe SSO/SAML, API publike, multitanencë ose kërkesa më të rrepta të auditimit. Ajo dështon aty ku header-et Forwarded i besohet verbërisht ose topologjitë e proxy-ve nuk dokumentohen (shtresa të shumta Ingress, Cloud-LB plus nginx plus Sidecar). Në këto raste adresa IP e klientit dhe skema shndërrohen shpejt në „çfarëdolloj“.
Një kufi i qartë: Nëse ju nevojiten rregulla komplekse të besimit (CIDR, IPv6‑rrjete, IP‑të dinamike të LB), duhet të zgjeroni verifikimin e besimit (parsing i saktë i IP‑së, maskat e rrjetit) ose të dizajnoni infrastrukturën në mënyrë që vetëm një proxy i përcaktuar të mund të arrijë portin Delphi (Firewall/Security Groups). Në fund të ditës kjo zakonisht është vendimi më i qëndrueshëm për operacionin.
Përfundim: Operimi i pastër i Reverse Proxy me nginx dhe Delphi do të thotë „trajtoni Forwarded siç duhet“
Një Reverse Proxy me nginx është një bllok standard i mirë për Delphi-REST-Server – por vetëm trajtimi i saktë i Forwarded dhe X-Forwarded-* e bën konfigurimin të qëndrueshëm në operacion. The core is simple: Pranoni header vetëm nga proxy të besueshëm, nxirrni në mënyrë konsistente IP‑në e klientit / skemën / hostin dhe zbatoni këtë bazë në ridrejtime, regjistrim dhe kontrolle sigurie. Me snippet‑in e mësipërm keni një themel të pastër, i përshtatshëm për legacy, që mund të integrohet në WebBroker, Horse ose në serverat tuaj të HTTP.
Nëse dëshironi të konsolidoni një backend ekzistues Delphi pas nginx ose ta modernizoni drejt Delphi REST-API dhe REST-Server me një linjë operative të qartë, një rishikim teknik i zinxhirit të proxy‑ve dhe i vlerësimit të header‑eve shpesh është levëja më e shpejtë. Kontaktoni Net-Base për një vlerësim teknik të shkurtër.
Në kontekstin profesional, Nginx Reverse Proxy dhe header‑et Forwarded luajnë një rol të rëndësishëm kur integrimet, rrjedhat e të dhënave dhe zhvillimi i mëtejshëm duhet të punojnë së bashku në mënyrë të pastër.
Diskutoni projektin ose iniciativën e modernizimit me Net-Base.