Net-Base Tímarit

23.05.2026

Reverse proxy með nginx og Delphi: hreint Forwarded-meðhöndlun, raunveruleg Client-IP og traustir URL-grunnar

Þegar Delphi-REST-þjónar keyra aftan við nginx brotna oft upplýsingar um Client-IP, HTTPS-uppgötvun og algildar vefslóðir. Þessi kóðabútur sýnir áreiðanlega meðhöndlun Forwarded-/X-Forwarded-hausanna (þ.m.t. Trust-Proxy-listu), dæmigerðar nginx-stillingar og leiðbeiningar um villuleit fyrir rekstur.

23.05.2026

Frá tímaritsþema til verkefnaframkvæmdar

Viðeigandi þjónustu- og tæknisíður fyrir greinina

Ein Reverse Proxy mit nginx und Delphi ist in der Praxis meist kein „nice to have“, sondern die saubere Trennung zwischen Internetkante und Applikation: TLS-Terminierung (HTTPS-Offloading), zentrale Header-/CORS-Regeln, Rate-Limits, einheitliche Logs, Blue/Green-Rollouts oder einfach das Hosting mehrerer Services unter einer Domain. Was dann gerne unterschätzt wird: Sobald nginx „fyrir framan“ sitzt, sieht der Delphi-Server nur noch die Proxy-IP, oft nur noch „http“ statt „https“ und generiert falsche absolute Links (Redirects, Callback-URLs, OpenAPI-Server-URL). Genau diese drei Punkte sorgen später für Debugging-Zeit im Betrieb.

Dieser Source-Schnipsel zeigt ein robustes Muster, wie Sie in Delphi Forwarded bzw. X-Forwarded-* sauber auswerten – inklusive Trust-Proxy-Liste (wichtig gegen Header-Spoofing) und einer konsistenten Request-Base-URL. Dazu gibt es praxistaugliche nginx-Konfigurationen und Hinweise zu Randfällen wie WebSockets, große Uploads und Timeouts.

Af hverju Reverse Proxy-uppsetningar rugla Delphi-þjóna

nginx talar sem Reverse Proxy við Delphi-þjónustuna yfirleitt ódulkóðað (HTTP) á innra neti eða á localhost, á meðan viðskiptavinurinn kemur utan með HTTPS. Án aukalegra headera veit Delphi ekkert um:

  • Upprunalegt schema (https vs. http) – mikilvægt fyrir Redirects og algildar URL-slóðir.
  • Upprunalegur Host (viðskiptavinssértæk lén, Port) – mikilvægt fyrir Multi-Tenant-uppsetningar, CORS og Callback-URLs.
  • Upprunaleg Client-IP – mikilvægt fyrir Audit, Rate-Limits, Geo-Checks og öryggisgreiningar.

nginx getur fært þessar upplýsingar í gegnum headera. Algengt er X-Forwarded-For, X-Forwarded-Proto og X-Forwarded-Host; staðlað er auk þess RFC-headerinn Forwarded. Mikilvægt: Þessir headerar eru úr sjónarhóli forritsins ekki sjálfkrafa traustsverðir, því viðskiptavinur getur sent þá sjálfur – þeir verða aðeins traustsverðir þegar þeir koma frá þekktum proxy.

nginx-Konfiguration: die minimal sinnvollen Proxy-Header

Traustur byrjunarpunktur (HTTP/1.1, Keep-Alive, Upgrade fyrir WebSockets) lítur svona út. Brotinu er meðvitað haldið hnitmiðuðu; þú bætir eftir umhverfi við HSTS, Rate-Limits og 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;
  }
}

Markmið: Forritið fær Host-ið, Client‑IP‑ið og URL‑sniðið áreiðanlega áfram. Skilyrði: $proxy_add_x_forwarded_for bætir núverandi Proxy‑IP við mögulega fyrirliggjandi keðju; það er gott fyrir margfaldar proxy‑uppsetningar, en gerir rétta úrvinnslu á Delphi‑síðu enn mikilvægari. Hætta: Ef þú setur ekki Host-hausinn í nginx, sér Delphi hugsanlega aðeins upstream‑host‑ið (127.0.0.1), sem brýtur endurvísanir og upprunaathuganir.

Delphi Upprunabútur: Traust úrvinnsla á Forwarded/X-Forwarded (með traust-proxy-lista)

Fylgjandi kóði er meðvitað haldinn óháður ramma: Hann vinnur gegn einföldu viðmóti (Header + RemoteIP) og má aðlaga í WebBroker, RAD Server eða Horse. Kjarnatriði:

  • Forgangsregla: RFC Forwarded (ef til staðar) á undan X-Forwarded-*.
  • Traust: Forwarded‑hausar eru aðeins greindir ef beinn peer (RemoteIP) er þekktur Proxy.
  • Greining: Taka tillit til IPv6, gæsamerkja, porta og keðja í X-Forwarded-For.
  • Úttak: grunn‑URL sem hægt er að nota fyrir fullar slóðir, endurvísanir eða OpenAPI.
Delphi
unit Net-Base.ProxyForwarding;

interface

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

type
  // Lágmarks adapter-viðmót: útfærið það fyrir WebBroker/Horse o.s.frv.
  IHeaderReader = interface
    ['{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}']
    function GetHeaderValue(const AName: string): string;
    function GetRemoteIP: string; // beint TCP-mótstykki (oft nginx)
  end;

  TForwardedInfo = record
    ClientIP: string;
    Proto: string; // http/https
    Host: string;
    Port: Integer;
    function EffectiveScheme: string;
    function EffectiveHostPort: string;
    function BaseUrl: string; // t.d. 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 getur verið "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 í []: [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 (Athugið: við beran IPv6 án [] er þetta ekki áreiðanlegt)
  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
  // Dæmi: Forwarded: for=203.0.113.43;proto=https;host=api.example.com
  Parts: TArray;
  I: Integer;
  KV: TArray;
  K, V: string;
  FirstElement: string;
begin
  Result := False;
  ClientIP := '';
  Proto := '';
  Host := '';

  if ForwardedValue.Trim = '' then
    Exit;

  // Fjöldi þátta er aðskilinn með kommu; við tökum fyrsta hlutann (nær klientinum)
  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 getur verið IP eða "unknown"; IPv6 getur verið í []
      V := V.Trim;
      if SameText(V, 'unknown') then
        Continue;
      // for=1.2.3.4:5678 getur komið fyrir
      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.Create;
end;

destructor TTrustedProxyList.Destroy;
begin
  FSet.Free;
  inherited;
end;

class function TTrustedProxyList.NormalizeIp(const AIP: string): string;
begin
  // Fyrir raunverulega IPv6-normalíseringu þyrfti IP-greiningu; hér erum við meðvitað pragmatísk.
  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; // Varagildi: beint mótstykki

  // Einungis ef beint mótstykki er þekktur proxy lesum við Forwarded-header.
  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-* sem varagildi/viðbót
    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;

  // Ef allt bilar, taktu Host úr 'Host' hausnum (ef enginn proxy-haus er til staðar)
  if Result.Host = '' then
  begin
    Host := Req.GetHeaderValue('Host');
    if Host <> '' then
      SplitHostPort(Host, Result.Host, Result.Port);
  end;
end;

end.

Tilgangur: Kerfið fær úr hverri beiðni samræmda sýn á ClientIP, Proto og Host auk BaseUrl. Þessar upplýsingar má miðstýra fyrir skráningu, öryggisákvarðanir (t.d. IP-allowlist) og tenglasköpun.

Af hverju Trust-Proxy-listinn er nauðsynlegur: Án Trust-prófunar gæti árásarmaður náð beint á portið ykkar á Delphi (villa í stillingum, innra routing, VPN) og sent einfaldlega X-Forwarded-For: 127.0.0.1. Þá yrðu audit‑keðjur, rate‑limits eða „aðeins innra leyft“-endapunktar í hættu. Treystu aðeins Forwarded‑hausum ef bein mótaðili (RemoteIP) er proxy sem þið stjórnið (t.d. 127.0.0.1, Load-Balancer-IP, Kubernetes-Ingress).

Fallstrik: IPv6 án ferkantaðra sviga ([]) er ekki ótvírætt í Host:port-ritun. Í HTTP-Host-hausnum er IPv6 venjulega skráð í []; fylgið því. Fyrir flóknar IP-svið (CIDR) þarf að stækka Trust‑listann (t.d. með raunverulegri IP‑greiningu).

Integration in WebBroker/Horse/RAD Server: wo der Code „andockt“

Í WebBroker (TWebRequest) koma hausar venjulega í gegnum ContentFields eða GetFieldByName, Remote‑IP fer eftir server‑backend. Í Horse (eða öðrum HTTP‑ramma) er yfirleitt Req.Headers og Remote‑IP‑eiginleiki. Mikilvægt er meginreglan: RemoteIP verður að vera TCP‑mótaðili, ekki eitthvert hausgildi.

Praktískt reynslusnið: Búið til við þjónustuhýsingu TTrustedProxyList úr stillingum (INI/ENV), t.d. „127.0.0.1“ fyrir staðbundin nginx‑uppsetning eða IP hjá Load Balancer. Þá körið þið ResolveForwardedInfo fyrir hverja beiðni og skrifið reitina í uppbyggða skráningu (JSON‑logg, Syslog eða Windows Event Log).

Debugging im Betrieb: so finden Sie Fehler in Minuten statt Stunden

Ef beiðnir virka „skrýtnar“ þá stafar það sjaldan af Delphi‑HTTP sjálfu, heldur af samspili proxy‑hausar, redirect‑rökfræði og timeouts. Þrjár debugging‑athuganir sem gagnast í daglegum rekstri:

  1. Header‑Dump (gezielt): Skráið við 4xx/5xx aukalega Host, Forwarded, X-Forwarded-For, X-Forwarded-Proto, User-Agent og Request-URI. En aðeins við villur – annars verður loggið dýrt og óskipulegt.
  2. Base‑URL prüfen: Þegar redirectar eða callback‑URLir mistakast, skrifið ForwardedInfo.BaseUrl í loggið. Margar villur sjást strax („http://127.0.0.1“ í stað „https://api…“).
  3. Timeout‑Korrelation: 504 frá proxy er ekki það sama og Delphi‑timeout. nginx proxy_read_timeout og Delphi‑hliðar Idle/Read‑timeouts þurfa að samræmast.

Randfälle: WebSockets, Streaming und große Requests

WebSockets hinter nginx

Fyrir WebSockets þarf nginx að hafa Upgrade og Connection rétt sett. Ennfremur má bakendinn ekki loka „of snemma“. Á Delphi‑hlið er mikilvægt að WebSocket‑hlutinn ykkar (eða SSE/streaming‑endapunktur) geti tekist á við reverse proxies og að Heartbeats/Keep‑Alives séu hreint útfærð.

Große Uploads und 413-Fehler

Klassískur vandi: Delphi samþykkir upphleðslu en nginx blokkir hana fyrirfram með 413 Request Entity Too Large. Stjórnið þetta sérstaklega með client_max_body_size og stillið Delphi‑hliðar takmörk fyrir beiðnir. Fyrir ferlamiðuð hugbúnaðarlausn með skjala‑ eða mynda‑gögnum er þetta ekki undantekning heldur venjulegur rekstur.

HTTPS-Offloading und „Secure Cookies“

Ef Delphi-þjónustan ykkar stillir session-cookies, þarf þær yfirleitt að merkja sem Secure þegar um ytri HTTPS-samskipti er að ræða. Hvort forritið ykkar gerir það ræðst oft af því hvort það „viti“ að upphaflegu beiðnin var HTTPS. Hér kemur samstæð úrvinnsla á X-Forwarded-Proto/Forwarded að gagni.

Hvenær vinnan borgar sig – og hvar hún getur brugðist

Hugmyndin sem sýnd er réttlætir sig þegar Delphi-þjónustan er ekki lengur „nakin“ í LANinu heldur hluti af framleiðslujaðri: margar lénaskrár, SSO/SAML-viðmót, Public APIs, margleigufærni eða strangari kröfur um endurskoðun. Hún bilar þar sem menn treysta Forwarded-headerum blindandi eða lýsa ekki Proxy-topólógíunni (margar Ingress-stig, Cloud-LB plus nginx plus Sidecar). Þá verða Client-IP og scheme fljótt „eitthvað“.

Skýr mörk: Ef þið þurfið flókin traustreglur (CIDR, IPv6-net, dynamískar LB-IPs) ættuð þið að auka traustprófunna (raunverulegt IP-parsing, netmaskar) eða hanna innviði þannig að aðeins skilgreindur proxy geti náð Delphi-portinu (Firewall/Security Groups). Að lokum er það yfirleitt stöðugri rekstrarleg ákvörðun.

Fyrirgreining: Reverse Proxy með nginx og Delphi áreiðanlega reka þýðir „gera Forwarded rétt“

Reverse Proxy með nginx er fyrir Delphi-REST-Server góður staðlaður byggingarhluti – en einungis rétt meðhöndlun Forwarded og X-Forwarded-* gerir uppsetninguna í rekstri stöðuga. Kjarninn er einfaldur: samþykkja aðeins headera frá traustum Proxies, leiða Client-IP/Scheme/Host af á samræmdan hátt og láta þessa grunnreglu ná yfir redirects, skráningu og öryggisskoðanir. Með snippetrinu hér að ofan hafið þið hreint, legacy-viðeigandi grunnlag sem má samþætta í WebBroker, Horse eða eigin HTTP-þjóna.

Ef þið ætlið að sameina núverandi Delphi-backend aftan við nginx eða nútímavæða í átt að Delphi REST-API und REST-Server með skýrri rekstrarlínu, er tæknilegt yfirvarp á proxy-keðjunni og header-úrvinnslunni oft hraðasta leiðin til árangurs. Hafðu samband við Net-Base fyrir stutta tæknilega staðsetningu.

Í faglegu samhengi gegna einnig Nginx Reverse Proxy og Forwarded-header mikilvægri stöðu þegar samþættingar, gagnastreymi og áframhaldandi þróun þurfa að spila hreint saman.

Ræddu verkefni eða nútímavæðingarverkefni með Net-Base.

Næsta skref

Ef efnið verður að raunverulegu verkefni, ætti snemma að skoða kerfisarkitektúr, núverandi kerfi og rekstur í sameiningu.

Við styðjum ekki aðeins við einstakar spurningar, heldur einnig þegar úr kóðabútum, eldri kerfum eða gáttahugmyndum þarf að verða traust fyrirtækjaverkefni.

  • Núverandi staða, markmynd og tæknileg áhætta eru metin saman.
  • REST, aðgangur að gögnum, gáttir og innleiðing verða ekki flutt til síðari tíma sem afleiðingar.
  • Þú sérð snemma hvaða leið er efnahagslega og rekstrarlega framkvæmanleg.

Deila færslu

Deila þessari færslu beint

LinkedIn, X, XING, Facebook, WhatsApp og tölvupóstur eru strax í boði. Fyrir Instagram undirbúum við tengil og stuttan texta strax.

Tölvupóstur

Instagram opnast í nýjum flipa. Tengill og stuttur texti eru afritaðir í klippiborðið á undan.