Un Reverse Proxy amb nginx i Delphi no és en la pràctica un «nice to have», sinó la separació neta entre la vora d’internet i l’aplicació: terminació TLS (HTTPS-offloading), regles centrals d’headers/CORS, rate-limits, logs unificats, blue/green-rollouts o, simplement, allotjament de diversos serveis sota un mateix domini. El que sovint es subestima: tan bon punt nginx actua «al davant», el servidor Delphi només veu la IP del proxy, sovint només «http» en comptes de «https» i genera enllaços absoluts incorrectes (redireccions, URL de callback, OpenAPI-Server-URL). Precisament aquests tres punts provoquen més tard temps de depuració en funcionament.
Aquest fragment de codi mostra un patró robust de com a Delphi interpretar correctament Forwarded i X-Forwarded-* — incloent-hi una llista de proxies de confiança (important contra l’header-spoofing) i una Request-Base-URL consistent. A més, s’inclouen configuracions nginx pràctiques i indicacions sobre casos límit com WebSockets, pujades grans i timeouts.
Per què les configuracions de reverse proxy confonen els servidors Delphi
nginx, com a reverse proxy, sol parlar amb el servei Delphi de forma no xifrada (HTTP) a la xarxa interna o al localhost, mentre que el client extern arriba per HTTPS. Sense headers addicionals, Delphi no sap res de:
- Esquema original (https vs. http) – rellevant per a redireccions i URLs absolutes.
- Host original (domini específic del client, port) – rellevant per a entorns multi-tenant, CORS i URLs de callback.
- IP original del client – rellevant per a auditoria, rate-limits, comprovacions geogràfiques i anàlisis de seguretat.
nginx pot transportar aquesta informació mitjançant headers. Sovint s’utilitzen X-Forwarded-For, X-Forwarded-Proto i X-Forwarded-Host; estàndard addicional és l’RFC-header Forwarded. Important: aquests headers no són automàticament de confiança des de la perspectiva de l’aplicació, perquè un client els pot enviar per si mateix — només es tornen fiables quan provenen d’un proxy conegut.
Configuració nginx: els headers de proxy mínims i útils
Un punt de partida sòlid (HTTP/1.1, Keep-Alive, Upgrade per a WebSockets) es veu així. L’exemple és intencionadament curt; en funció de l’entorn cal afegir HSTS, rate-limits i access-logs.
# (configuració nginx, no 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;
# opcional, però pràctic per a URLs absolutes
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 adequats per al backend de Delphi (informes/exportacions llargs)
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# controlar explícitament pujades grans
client_max_body_size 50m;
}
}
Objectiu: L’aplicació rep el Host, la IP del client i l’esquema de manera fiable. Condició: $proxy_add_x_forwarded_for afegeix la IP del proxy actual a una cadena existent (si n’hi ha); això és bo per a configuracions amb múltiples proxies, però fa que l’avaluació correcta a la banda de Delphi sigui encara més important. Punt crític: Si no configura el capçal Host en nginx, Delphi podria veure només l’host de l’upstream (127.0.0.1), cosa que trenca les redireccions i les comprovacions d’origen.
Delphi Fragment de codi font: avaluació robusta de Forwarded/X-Forwarded (amb llista de proxies de confiança)
El codi següent s’ha mantingut deliberadament agnòstic respecte del framework: treballa sobre una interfície mínima (Header + RemoteIP) i es pot adaptar a WebBroker, RAD Server o Horse. Punts clau:
- Prioritat: l’RFC Forwarded (si existeix) abans que X-Forwarded-*.
- Confiança: només avaluar els headers Forwarded si el peer directe (RemoteIP) és un proxy conegut.
- Parsing: tenir en compte IPv6, cometes, ports i cadenes a X-Forwarded-For.
- Sortida: una Base-URL que pot utilitzar per a enllaços absoluts, redireccions o OpenAPI.
unit Net-Base.ProxyForwarding;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections, System.NetEncoding;
type
// Interfície mínima d’adaptador: implementeu-la per a WebBroker/Horse/etc.
IHeaderReader = interface
[‚{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}‘]
function GetHeaderValue(const AName: string): string;
function GetRemoteIP: string; // extrem TCP directe (normalment nginx)
end;
TForwardedInfo = record
ClientIP: string;
Proto: string; // http/https
Host: string;
Port: Integer;
function EffectiveScheme: string;
function EffectiveHostPort: string;
function BaseUrl: string; // p. ex. 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 pot ser „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 entre []: [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 (Atenció: amb IPv6 sense [] no és fiable)
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
// Exemple: 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;
// Diversos elements estan separats per comes; prenem el primer (el més proper al client)
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 pot ser una IP o „unknown“; l’IPv6 pot estar entre []
V := V.Trim;
if SameText(V, ‚unknown‘) then
Continue;
// for=1.2.3.4:5678 pot aparèixer
if V.Contains(‚:‘) and (not V.StartsWith(‚[‚)) then
V := FirstCsvToken(V); // per precaució
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
end;
destructor TTrustedProxyList.Destroy;
begin
FSet.Free;
inherited;
end;
class function TTrustedProxyList.NormalizeIp(const AIP: string): string;
begin
// Per a una normalització IPv6 completa caldria un parser d’IP; aquí s’opta deliberadament per una solució pragmàtica.
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; // Valor per defecte: extrem directe
// Només si l’extrem directe és un proxy conegut, avaluem els headers 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-* com a complement/alternativa
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 cal, obtenir Host de l’encapçalament „Host“ (si no hi ha cap header de proxy)
if Result.Host = “ then
begin
Host := Req.GetHeaderValue(‚Host‘);
if Host <> “ then
SplitHostPort(Host, Result.Host, Result.Port);
end;
end;
end.
Objectiu: Rebreu per a cada request una vista consistent de ClientIP, Proto i Host així com una BaseUrl. Aquesta informació la podeu utilitzar de manera centralitzada per al registre, per a decisions de seguretat (p. ex. llista d’IPs permeses) i per a la generació d’enllaços.
Per què cal la llista de Trust-Proxy: Sense verificar el proxy com a de confiança, un atacant podria accedir directament al port Delphi (mala configuració, enrutament intern, VPN) i enviar simplement X-Forwarded-For: 127.0.0.1. Això faria vulnerables els audit-trails, els rate-limits o els endpoints marcats com a «només interns». Confieu en els capçaleres Forwarded només si el peer directe (RemoteIP) és un proxy que controleu (p. ex. 127.0.0.1, IP del load balancer, Kubernetes-Ingress).
Riscos a tenir en compte: IPv6 sense claudàtors [] no és inequívoc en la notació Host:port. En el capçal HTTP Host, IPv6 normalment s’indica amb []; seguiu-ho. Per a rangs d’IP complexes (CIDR) caldrà ampliar la llista de confiança (p. ex. mitjançant un parsing d’IP real).
Integració en WebBroker/Horse/RAD Server: on s’«enganxa» el codi
A WebBroker (TWebRequest) els headers solen arribar via ContentFields o GetFieldByName, la Remote-IP depèn del backend del servidor. En Horse (o altres frameworks HTTP) acostuma a haver-hi Req.Headers i una propietat de Remote-IP. El principi important és: RemoteIP ha de ser la contraparte TCP, no un valor qualsevol d’un capçal.
Pràctica recomanada: creeu a l’inici del servei una TTrustedProxyList a partir de la configuració (INI/ENV), p. ex. «127.0.0.1» per a setups locals amb nginx o la IP del vostre load balancer. Després crideu ResolveForwardedInfo per cada request i escriviu els camps al vostre registre estructurat (JSON-Log, Syslog o Windows Event Log).
Debugging en producció: com trobar errors en minuts en comptes d’hores
Si les requests semblen «estranyes», rarament és culpa de Delphi-HTTP en si, sinó d’una combinació de capçaleres del proxy, lògica de redireccions i timeouts. Tres comprovacions de debugging que val la pena fer en el dia a dia:
- Volcat de capçaleres (dirigit): Registreu, en cas de 4xx/5xx, també Host, Forwarded, X-Forwarded-For, X-Forwarded-Proto, User-Agent i Request-URI. Però només en errors — altrament el log es fa car i difícil de consultar.
- Comprovar la Base-URL: Si les redireccions o les URLs de callback fallen, registreu ForwardedInfo.BaseUrl. Molts errors es veuen de seguida («http://127.0.0.1» en lloc de «https://api…»).
- Correlació de timeouts: Un 504 des del proxy no és el mateix que un timeout de Delphi. nginx proxy_read_timeout i els timeouts Idle/Read del costat de Delphi han de coincidir.
Casos límit: WebSockets, streaming i peticions grans
WebSockets darrere de nginx
Per a WebSockets nginx necessita els capçaleres Upgrade i Connection correctament configurats. A més, el backend no ha de tancar «massa aviat». En el costat Delphi és rellevant que la vostra component WebSocket (o un endpoint SSE/streaming) pugui gestionar reverse proxies i que els heartbeats/keep-alives estiguin implementats de manera robusta.
Pujades grans i errors 413
Un clàssic: Delphi accepta una pujada, però nginx la bloqueja abans amb 413 Request Entity Too Large. Controleu-ho explícitament amb client_max_body_size i ajusteu els límits de request en el costat Delphi. Per a solucions de programari amb processament de documents o imatges això no és una excepció, sinó operació normal.
HTTPS-Offloading i «Secure Cookies»
Si el vostre Delphi-Service estableix cookies de sessió, aquestes normalment han d’estar marcades com a Secure quan l’accés extern és via HTTPS. Que la vostra aplicació ho faci sovint depèn de si «sap» que la sol·licitud original era HTTPS. Precisament aquí ajuda l’avaluació consistent de X-Forwarded-Proto/Forwarded.
Quan val la pena l’esforç – i on pot fallar
L’enfocament mostrat val la pena sempre que el Delphi-Service ja no estigui «desprotegit» a la LAN, sinó que formi part d’una capa de perímetre de producció: diverses domains, interfícies SSO/SAML, Public APIs, multitenància o requisits d’auditoria més estrictes. Fallarà quan es confiï a cegues en els encapçalaments Forwarded o quan no estiguin documentades les topologies de proxy (diverses etapes d’ingress, Cloud-LB més nginx més sidecar). En aquests casos la Client-IP i l’esquema ràpidament passen a ser indeterminats.
Un límit clar: si necessiteu regles de confiança complexes (CIDR, xarxes IPv6, IPs d’un LB dinàmic), hauríeu d’ampliar la comprovació de confiança (anàlisi real d’IP, màscares de xarxa) o dissenyar la infraestructura de manera que només un proxy definit pugui accedir al port del Delphi (firewall/Security Groups). Al final aquesta solució sol ser la decisió operativa més robusta.
Conclusió: Operar netament un Reverse Proxy amb nginx i Delphi significa «fer bé Forwarded»
Un Reverse Proxy amb nginx és per a Delphi-REST-Server un bon component estàndard – però només el tractament correcte de Forwarded i X-Forwarded-* fa que la configuració sigui estable en producció. El nucli és simple: acceptar encapçalaments només de proxies de confiança, derivar de manera consistent la Client-IP, l’esquema i l’Host i aplicar aquesta base a redireccions, logging i comprovacions de seguretat. Amb el snippet anterior disposeu d’un fonament net i compatible amb entorns legacy, que es pot integrar en WebBroker, Horse o servidors HTTP propis.
Si voleu consolidar un backend Delphi existent darrere de nginx o modernitzar-lo cap a una Delphi REST-API i REST-Server amb una línia d’operació clara, sovint una revisió tècnica de la cadena de proxies i de l’avaluació dels encapçalaments és la palanca més ràpida. Contacteu amb Net-Base per a una breu avaluació tècnica.
En l’entorn funcional, el Nginx Reverse Proxy i els encapçalaments Forwarded també juguen un paper important quan cal que integracions, fluxos de dades i evolució funcionin conjuntament de manera neta.
Parleu del projecte o de la iniciativa de modernització amb Net-Base.