Net-Base Revistë

23.05.2026

Reverse Proxy me nginx dhe Delphi: përpunim i saktë i header-it Forwarded, IP reale e klientit dhe baza të qëndrueshme të URL-ve

Kur serverët Delphi-REST janë të vendosur pas nginx, shpesh humbasin adresa IP e klientit, zbulimi i HTTPS dhe URL-të absolute. Ky fragment kodi tregon një përpunim të robust të header-eve Forwarded/X-Forwarded (përfshirë listën Trust-Proxy), konfigurime tipike për nginx dhe udhëzime debug për operim.

23.05.2026

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.

Delphi
# (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.
Delphi
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ë:

  1. 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ë.
  2. 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…“).
  3. 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.

Ndaje postimin

Shpërndaj këtë postim drejtpërdrejt

LinkedIn, X, XING, Facebook, WhatsApp dhe E‑Mail janë menjëherë të disponueshme. Për Instagram po përgatitim menjëherë lidhjen dhe tekstin e shkurtër.

Postë elektronike

Instagram hapet në një skedë të re. Linku dhe teksti i shkurtër kopjohen më parë në memorjen e kopjimit.