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.
# (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.
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:
- 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.
- 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…“).
- 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.