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.
# (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.
Næsta skref
Þegar úr málinu verður raunverulegt verkefni ber að skoða arkitektúr, núverandi kerfi og rekstur snemma saman.
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, gagnaaðgangur, gáttir og innleiðing eru ekki skildir eftir til síðar.
- Það sést snemma hvaða leið er fjárhagslega og rekstrarlega sjálfbær.