Net-Base Iris

23.05.2026

Reverse-Proxy le nginx agus Delphi: láimhseáil 'Forwarded' shlán, seoladh IP bhunaidh an chliaint agus bunúsacha URL chobhsaí

Nuair a bhíonn freastalaithe Delphi-REST ag rith taobh thiar de nginx, bíonn IP an chliaint, an aithint HTTPS agus URLanna iomlána go minic míchruinn. Taispeánann an slisnigh cód seo láimhseáil chobhsaí do Forwarded-/X-Forwarded (lena n-áirítear liosta Trust-Proxy), socruithe tipiciúla nginx agus leideanna dífhabhtaithe don oibriú.

23.05.2026

Is é Reverse Proxy le nginx agus Delphi sa phraitic de ghnáth ní mar „nice to have“, ach mar an scaradh glan idir imeall an idirlín agus an iarratas: deirmitiú TLS (HTTPS-Offloading), rialacha lárnacha Header-/CORS, teorainneacha rátaí, lógaí aonaid, Blue/Green-Rollouts nó fiú óstáil il-seirbhísí faoi aon fhearann. Rud a mheasann daoine go minic níos ísle: nuair a bhíonn nginx „os comhair“, feiceann freastalaí Delphi ach IP an phróicé, go minic ach „http“ seachas „https“ agus gintear naisc iomlána míchearta (Redirects, Callback-URLs, OpenAPI-Server-URL). Is iad na trí phointe sin go beacht a chruthaíonn am dífhabhtaithe sa lóistíocht oibriúcháin níos déanaí.

Leis an slisneal fhoinse seo taispeántar patrún dothuigthe conas in Delphi an Forwarded agus X-Forwarded-* a mheas go soiléir – lena n-áirítear liosta proxy iontaofa (tábhachtach in aghaidh Header-Spoofing) agus Request-Base-URL comhsheasmhach. Tá cumraíochtaí nginx praiticiúla ann freisin agus leideanna maidir le cásanna imeartha cosúil le WebSockets, uaslódálacha móra agus ama-amharc.

Cén fáth a chuireann socruithe Reverse Proxy mearbhall ar fhreastalaithe Delphi

Mar Reverse Proxy, labhraíonn nginx le seirbhís Delphi de ghnáth gan criptú (HTTP) sa líonra inmheánach nó ar localhost, fad is a thagann an cliant ó lasmuigh trí HTTPS. Gan na header breise, ní fios ag Delphi rud ar bith faoi:

  • Scéim bhunaidh (https vs. http) – ábhartha do Redirects agus do URLs iomlána.
  • Óstach bhunaidh (fearann sonrach custaiméara, calafort) – ábhartha do shocrúcháin Multi-Tenant, CORS agus Callback-URLs.
  • IP bhunaidh an chliaint – ábhartha le haghaidh iniúchta, teorainneacha rátaí, seiceálacha geo agus anailís slándála.

Is féidir le nginx an t-eolas seo a iompar trí Headers. Go coitianta úsáidtear X-Forwarded-For, X-Forwarded-Proto agus X-Forwarded-Host; tá an RFC-Header Forwarded caighdeánaithe freisin. Tábhachtach: ní bhíonn na Headers seo, ó dhearcadh na h-iarrata, inchreidte de réir réamhshocraithe, mar is féidir le cliant iad a sheoladh ceana féin – ní bhíonn siad inchreidte ach nuair a thagann siad ó phróicé aitheanta.

nginx-Konfiguration: die minimal sinnvollen Proxy-Header

Seo tús láidir (HTTP/1.1, Keep-Alive, Upgrade do WebSockets). Tá an slisneal gearr go cúramach; cuirfidh tú HSTS, teorainneacha rátaí agus logaí rochtana leis de réir an timpeallachta.

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;
  }
}

Cuspóir: Faigheann an bhfeidhmchlár an Host, IP an chliaint agus an scéim curtha ar aghaidh go muiníneach. Coinníoll teoranta: $proxy_add_x_forwarded_for ceanglaíonn IP an phróicisé reatha le slabhra atá ann cheana; tá sé seo oiriúnach do shuiteálacha ilpróicé, ach déanann sé anailís cheart ar thaobh Delphi níos tábhachtaí fós. Riosca: Má nach socraíonn tú an ceannlínte Host i nginx, b’fhéidir go bhfeicfeadh Delphi ach an Upstream-Host (127.0.0.1), rud a bhriseann athsheoltaí agus seiceálacha Origin.

Delphi Sleamhnán foinse: Forwarded/X-Forwarded a anailís chobhsaí (le liosta Trust-Proxy)

Tá an cód seo thíos i gceist a bheith neodrach ó thaobh framework de: oibríonn sé thar comhéadan íosta (Header + RemoteIP) agus is féidir é a choigeartú do WebBroker, RAD Server nó Horse. Príomhphointí:

  • Tosaíocht: RFC Forwarded (má tá sé ann) roimh X-Forwarded-*.
  • Iontaobhas: Déan an Forwarded-header a anailísiú amháin má tá an piar díreach (RemoteIP) ar eolas mar phróicis.
  • Parsáil: Féach go leor le IPv6, na comharthaí luachála (quotes), calafoirt agus slabhraí i X-Forwarded-For.
  • Aschur: Base-URL ar féidir leat a úsáid do naisc iomlána, athsheoltaí nó OpenAPI.
Delphi
unit Net-Base.ProxyForwarding;

interface

uses
  System.SysUtils, System.Classes, System.Generics.Collections, System.NetEncoding;

type
  // Comhéadan adapter íosta: cuir i bhfeidhm é do WebBroker/Horse/etc.
  IHeaderReader = interface
    ['{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}']
    function GetHeaderValue(const AName: string): string;
    function GetRemoteIP: string; // comhpháirt TCP dhíreach (de ghnáth nginx)
  end;

  TForwardedInfo = record
    ClientIP: string;
    Proto: string; // http/https
    Host: string;
    Port: Integer;
    function EffectiveScheme: string;
    function EffectiveHostPort: string;
    function BaseUrl: string; // m.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
  // Is féidir le X-Forwarded-For a bheith "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 i []: [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 (Rabhadh: ní iontaofa le IPv6 lom gan [])
  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
  // Sampla: 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;

  // Tá roinnt eilimintí scartha le camóga; glacaimid an chéad cheann (an ceann is gaire don chliaint)
  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
      // Féadfaidh for IP nó "unknown" a bheith; d'fhéadfadh IPv6 a bheith i []
      V := V.Trim;
      if SameText(V, 'unknown') then
        Continue;
      // Bíonn for=1.2.3.4:5678 le feiceáil
      if V.Contains(':') and (not V.StartsWith('[')) then
        V := FirstCsvToken(V); // go cosanta
      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
  // Don normalú IPv6 iomlán bheadh gá le parsáil IP; anseo úsáidtear cur chuige pragmatach.
  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; // Réamhshocrú: comhpháirt dhíreach

  // Déanaimid na teidil Forwarded a mheastóireacht ach amháin má tá an comhpháirt dhíreach mar proxy muiníneach.
  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-* mar forlíonadh/reamhshocrú
    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;

  // I gcás éigeandála glacaimid an Host ón ceannlitir "Host" (má tá aon cheannlitir proxy ar fáil)
  if Result.Host = '' then
  begin
    Host := Req.GetHeaderValue('Host');
    if Host <> '' then
      SplitHostPort(Host, Result.Host, Result.Port);
  end;
end;

end.

Cuspóir: Faightear ó gach Request radharc comhsheasmhach ar ClientIP, Proto agus Host, chomh maith le BaseUrl. Is féidir leat an t-eolas seo a úsáid go lárnach le haghaidh logála, cinntí slándála (m.sh. IP-Allowlist) agus giniúna nascanna.

Cén fáth go bhfuil an liosta Trust-Proxy riachtanach: Gan seiceáil Trust, d’fhéadfadh ionsaitheoir do phort Delphi a bhaint amach go díreach (m.sh. míshocrú, routing inmheánach, VPN) agus díreach X-Forwarded-For: 127.0.0.1 a sheoladh. Bheadh ionfhabhtaithe sin ina n-ionsaí ar audit-trails, teorainneacha ráta nó deigheanna “ach inmheánach ceadaithe”. Muinín a bheith agat as Forwarded-headers ach amháin nuair is proxy é an comhpháirt dhíreach (RemoteIP) atá faoi do smacht (m.sh. 127.0.0.1, Load-Balancer-IP, Kubernetes-Ingress).

Fadhbanna coitianta: Tá IPv6 gan lúibíní cearnacha neamhchinnte i bhnotáil Host:port. San HTTP-Host-Header is gnách go bhfuil IPv6 i [] scríofa; cloí leis sin. Maidir le raonta IP níos casta (CIDR) beidh ort an liosta Trust a leathnú (m.sh. trí pharsáil IP fhíor).

Ionchuimsiú i WebBroker/Horse/RAD Server: cá ’ndéanann‘ an cód nascadh

I WebBroker (TWebRequest) tagann headers go ginearálta trí ContentFieldsGetFieldByName, agus an Remote-IP de réir an server-backend. I Horse (nó i bhfrámaí HTTP eile) bíonn de ghnáth Req.Headers agus airíonna Remote-IP. Tá an prionsabal tábhachtach: caithfidh RemoteIP a bheith an chomhpháirt TCP iargúlta, ní luach aon header.

Go praiticiúil: Cruthaigh ag tosú an tseirbhíse TTrustedProxyList ó chumraíocht (INI/ENV), m.sh. “127.0.0.1” do shocruithe nginx áitiúla nó IP do Load Balancer. Ansin glaoigh ar ResolveForwardedInfo do gach Request agus scríobh na réimsí isteach i do logáil struchtúrtha (log JSON, Syslog nó Windows Event Log).

Debugáil i mbun oibre: conas earráidí a aimsiú i nóiméid in ionad uaireanta

Nuair a bhíonn Requests “aisteach”, is annamh a hionann sin le Delphi-HTTP féin; is minic go mbíonn meascán de headers proxy, loighic redirect agus timeouts i gceist. Trí sheiceáil debug a bhíonn tairbheach sa chleachtas laethúil:

  1. Header-Dump (spriocdhírithe): Déan logáil i gcás 4xx/5xx breise ar Host, Forwarded, X-Forwarded-For, X-Forwarded-Proto, User-Agent agus Request-URI. Ach amháin i gcás earráidí — más rud é nach bhfuil, éireoidh an log costasach agus míthreorach.
  2. Seiceáil Base-URL: Má theipeann ar redirects nó ar Callback-URLs, déan logáil ar ForwardedInfo.BaseUrl. Feictear go leor earráidí láithreach (“http://127.0.0.1” in ionad “https://api…”).
  3. Correlú Timeouts: Níl 504 ón proxy mar an gcéanna le Delphi-timeout. Ní mór go n-oireann nginx proxy_read_timeout agus na timeouts Idle/Read ar thaobh Delphi lena chéile.

Cásanna imeallacha: WebSockets, Streaming agus Requests móra

WebSockets taobh thiar de nginx

Do WebSockets éilíonn nginx go mbeidh Upgrade agus Connection curtha i gcéill i gceart. Ina theannta sin, níor cheart don backend „dúnadh ró-luath“. Tá sé tábhachtach ar thaobh Delphi go mbeidh do chomhpháirt WebSocket (nó endpoint SSE/Streaming) in ann déileáil le Reverse Proxies agus go bhfuil Heartbeats/Keep-Alives curtha i bhfeidhm go glan.

Uaslódálacha móra agus earráidí 413

Clasaiceach: glacann Delphi uaslódáil, ach bíonn nginx ag blocáil roimhe seo le 413 Request Entity Too Large. Bainistigh seo go soiléir trí client_max_body_size agus coigeartaigh teorainneacha Request ar thaobh Delphi. Maidir le réitigh bogearraí proiseas-ghar le sonraí doiciméad nó íomhá, níl sé seo mar chás speisialta ach mar ghnáthobair.

Offloading HTTPS agus „Secure Cookies“

Má chuireann do sheirbhís Delphi fianáin seisiúin, caithfidh siad, i gcás HTTPS seachtrach, de ghnáth a bheith marcáilte mar Secure. An ndéanann do fheidhmchlár é sin a dhéanamh braitheann sé go minic ar an eolas atá aige go raibh an t-iarratas bunaidh HTTPS. Tá léirmhíniú comhsheasmhach ar X-Forwarded-Proto/Forwarded úsáideach díreach anseo.

Cén uair a bhíonn an iarracht i gceart — agus cá bhféadfadh sí teip

An cur chuige atá taispeántha anseo a bhaineann leasa i gcónaí nuair nach maireann an tseirbhís Delphi níos mó “nocht” sa LAN, ach mar chuid d’imeall táirgthe: il-dhomainí, comhéadan SSO/SAML, APIs poiblí, cumas il-mhánaigh nó riachtanais iniúchta níos déine. Tá sé leochaileach i gcásanna ina gcuirtear muinín dall i Forwarded-headers nó nach ndéantar topaicí próca a dhoiciméadú (mehrere Ingress-Stufen, Cloud-LB plus nginx plus Sidecar). Ansin éiríonn Client-IP agus an scéim go tapa “éigin”.

Srian shoiléir: Má theastaíonn rialacha muiníne casta uait (CIDR, IPv6-láin, IPanna LB dinimiciúla), ba chóir duit an t-iniúchadh muiníne a leathnú (anailís IP fhíor, mascanna líonra) nó an bonneagar a dhearadh ionas nach mbeidh ach próca socraithe in ann an calafort Delphi a bhaint amach (Firewall/Security Groups). Faoi dheireadh is gnách gurb é sin an cinneadh oibríochta níos láidre.

Conclúid: Reverse Proxy mit nginx und Delphi sauber betreiben heißt „Forwarded richtig machen“

Is comhpháirt caighdeánach maith é Reverse Proxy le nginx do Delphi-REST-Server — ach is í an chóireáil cheart ar Forwarded agus X-Forwarded-* a dhéanann an socrú seasmhach sa ghnó. Tá an croílár simplí: Header a ghlacadh ach ó Proxies iontaofa, Client-IP/Scheme/Host a bhaint go comhsheasmhach agus an bonn seo a chur tríd chuig Redirects, Logging agus Security-Checks. Leis an snippet thuas tá bunús glan, oiriúnach do chásanna legacy agat, is féidir é a chomhtháthú i WebBroker, Horse nó freastalaithe HTTP saincheaptha.

Mura bhfuil tú ag comhdhlúthú backend Delphi reatha taobh thiar de nginx nó má tá tú ag nuachóiriú i dtreo Delphi REST-API agus REST-Server le líne oibríochta soiléir, is minic gurb é athbhreithniú teicniúil ar an slabhra próca agus ar an léirmhíniú header an bealach is tapúla. Déan teagmháil le Net-Base le haghaidh suirbhé teicniúil gearr.

I gcomhthéacs ghairmiúil, bíonn ról tábhachtach ag Nginx Reverse Proxy agus Forwarded-headers freisin má tá sé riachtanach go n-oibreoidh comhtháthuithe, sreafaí sonraí agus forbairt leanúnach go glan le chéile.

Plé tionscadal nó tionscnamh nuachóirithe le Net-Base.

Roinn an post

Roinn an t-alt seo go díreach

Tá LinkedIn, X, XING, Facebook, WhatsApp agus ríomhphost ar fáil láithreach. Do Instagram ullmhaímid nasc agus téacs gairid láithreach.

Ríomhphost

Osclaítear Instagram i gcluaisín nua. Cóipeáiltear an nasc agus an téacs gairid roimh ré isteach sa ghearrthaisce.