nginx ve Delphi ile bir Reverse Proxy uygulamada genellikle bir „nice to have“ değil, internet kenarı ile uygulama arasındaki temiz ayrımdır: TLS sonlandırma (HTTPS-Offloading), merkezi Header-/CORS kuralları, Rate-Limits, tutarlı loglar, Blue/Green roll-out’lar veya basitçe bir alan altında birden fazla servisin barındırılması. Sıkça hafife alınan nokta şudur: nginx öne geçtiğinde, Delphi sunucusu artık yalnızca proxy IP’sini görür, çoğu zaman artık „https“ yerine sadece „http“ görür ve yanlış mutlak linkler üretir (Redirects, Callback-URL’leri, OpenAPI-Server-URL). Bu üç nokta işletmede sonradan uzun debug sürelerine yol açar.
Bu kaynak kesiti, Delphi içinde Forwarded veya X-Forwarded-* başlıklarını nasıl sağlam şekilde değerlendireceğinize dair dayanıklı bir desen gösterir – Trust-Proxy-Liste (Header-Spoofing’e karşı önemli) ve tutarlı bir Request-Base-URL dahil. Buna ek olarak pratik nginx konfigürasyonları ve WebSockets, büyük yüklemeler ve timeouts gibi kenar durumlara ilişkin notlar içerir.
Neden Reverse Proxy kurulumları Delphi sunucularını „kandırır“
nginx, Reverse Proxy olarak Delphi servisiyle tipik olarak iç ağda veya localhost üzerinde şifrelenmemiş (HTTP) olarak konuşur; oysa dışarıdaki istemci HTTPS ile gelir. Ek başlıklar olmadan Delphi şu konulardan habersizdir:
- Original-Schema (https vs. http) – Redirects ve mutlak URL’ler için önemli.
- Original-Host (müşteri-özel domain, port) – Multi-Tenant kurulumlar, CORS ve Callback-URL’leri için önemli.
- Original-Client-IP – Audit, Rate-Limits, Geo-Check’ler ve güvenlik analizleri için önemli.
nginx bu bilgileri Header’lar aracılığıyla taşıyabilir. Yaygın olanlar X-Forwarded-For, X-Forwarded-Proto ve X-Forwarded-Host’tur; ayrıca standartlaştırılmış RFC Header Forwarded vardır. Önemli: Bu Header’lar uygulama açısından otomatik olarak güvenilir değildir, çünkü bir istemci bunları kendisi de gönderebilir – ancak bilinen bir proxy’den geliyorlarsa güvenilir kabul edilirler.
nginx-Konfiguration: die minimal sinnvollen Proxy-Header
Sağlam bir başlangıç noktası (HTTP/1.1, Keep-Alive, WebSockets için Upgrade) şu şekildedir. Snippet kasıtlı olarak kısa tutulmuştur; ortama bağlı olarak HSTS, Rate-Limits ve Access-Logs eklersiniz.
# (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;
}
}
Amaç: Uygulama Host, Client-IP ve şemayı güvenilir şekilde iletilmiş olarak alır. Koşul: $proxy_add_x_forwarded_for mevcut proxy IP’sini varsa var olan bir zincire ekler; bu çoklu proxy kurulumları için uygundur, ancak Delphi tarafında doğru değerlendirmeyi daha kritik hale getirir. Uyarı: nginx içinde Host-başlığını ayarlamazsanız, Delphi muhtemelen yalnızca Upstream-Host (127.0.0.1) görür; bu da yönlendirmeleri ve Origin kontrollerini bozabilir.
Delphi Kaynak-Schnipsel: Forwarded/X-Forwarded güvenli şekilde değerlendirme (Trust-Proxy-Liste ile)
Aşağıdaki kod kasıtlı olarak çerçeveden bağımsız tutulmuştur: Minimal bir arayüz (Header + RemoteIP) üzerinden çalışır ve WebBroker, RAD Server veya Horse’a uyarlanabilir. Temel noktalar:
- Öncelik: RFC Forwarded (varsa) X-Forwarded-*‚den önce.
- Güven: Forwarded-Header’ları yalnızca doğrudan eş (RemoteIP) bilinen bir proxy ise değerlendirin.
- Ayrıştırma: IPv6, tırnak işaretleri, portlar ve X-Forwarded-For içindeki zincirleri dikkate alın.
- Çıktı: mutlak bağlantılar, yönlendirmeler veya OpenAPI için kullanabileceğiniz bir temel URL.
unit Net-Base.ProxyForwarding;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections, System.NetEncoding;
type
// Minimal bir adaptör arayüzü: WebBroker/Horse/etc. için bunu uygulayın.
IHeaderReader = interface
['{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}']
function GetHeaderValue(const AName: string): string;
function GetRemoteIP: string; // doğrudan TCP karşı uç (çoğunlukla nginx)
end;
TForwardedInfo = record
ClientIP: string;
Proto: string; // http/https
Host: string;
Port: Integer;
function EffectiveScheme: string;
function EffectiveHostPort: string;
function BaseUrl: string; // örn. 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 "client, proxy1, proxy2" biçiminde olabilir
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 köşeli parantez içinde: [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 (Dikkat: köşeli parantezsiz çıplak IPv6'da güvenilir değil)
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
// Örnek: 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;
// Birden çok öğe virgülle ayrılır; ilkini (istemciye en yakın olanı) alıyoruz
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 bir IP veya "unknown" olabilir; IPv6 köşeli parantez içinde olabilir
V := V.Trim;
if SameText(V, 'unknown') then
Continue;
// for=1.2.3.4:5678 görülebilir
if V.Contains(':') and (not V.StartsWith('[')) then
V := FirstCsvToken(V); // tedbir amaçlı
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
// Gerçek IPv6 normalizasyonu için bir IP ayrıştırması gerekir; burada kasıtlı olarak pragmatik.
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; // Yedek: doğrudan karşı uç
// Sadece doğrudan karşı uç bilinen bir proxy ise Forwarded başlıklarını değerlendiriyoruz.
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-* yedek/tamamlayıcı olarak
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;
// Gerekirse Host'u "Host" başlığından al (proxy başlığı yoksa)
if Result.Host = '' then
begin
Host := Req.GetHeaderValue('Host');
if Host <> '' then
SplitHostPort(Host, Result.Host, Result.Port);
end;
end;
end.
Amaç: Her istekte ClientIP, Proto ve Host ile bir BaseUrl hakkında tutarlı bir görünüm elde edersiniz. Bu bilgileri merkezi olarak günlükleme, güvenlik kararları (ör. IP-Allowlist) ve bağlantı oluşturma için kullanabilirsiniz.
Trust-Proxy listesinin gerekliliği: Güven denetimi olmadan bir saldırgan doğrudan Delphi-portunuza erişebilir (yanlış yapılandırma, dahili yönlendirme, VPN) ve basitçe X-Forwarded-For: 127.0.0.1 gönderebilir. Böylece audit-trail’ler, rate-limit’ler veya “sadece dahiliye açık” uç noktalar saldırıya açık hale gelir. Forwarded başlıklarına yalnızca doğrudan karşı uç (RemoteIP) sizin kontrolünüzde olan bir proxy ise güvenin (ör. 127.0.0.1, Load-Balancer-IP, Kubernetes-Ingress).
Dikkat edilmesi gerekenler: Köşeli parantezsiz IPv6, Host:port notasyonunda belirsizdir. HTTP Host başlığında IPv6 genellikle [] içinde yazılır; buna uyun. Karmaşık IP aralıkları (CIDR) için Trust listesini genişletmeniz gerekir (örn. gerçek bir IP ayrıştırması ile).
WebBroker/Horse/RAD Server ile entegrasyon: kodun „andockt“ olduğu yer
WebBroker (TWebRequest) içinde header’lar tipik olarak ContentFields veya GetFieldByName üzerinden gelir; Remote-IP sunucu backend’ine bağlıdır. Horse’ta (veya diğer HTTP-framework’lerde) genellikle Req.Headers ve bir Remote-IP özelliği bulunur. Önemli ilke şudur: RemoteIP, TCP karşı tarafı olmalıdır, herhangi bir header değeri değil.
Pratikte kanıtlanmış yaklaşım: Servis başlatılırken konfigürasyondan (INI/ENV) bir TTrustedProxyList oluşturun; örn. yerel nginx kurulumları için „127.0.0.1“ veya Load Balancer IP’niz. Ardından her istek için ResolveForwardedInfo çağırın ve alanları yapılandırılmış loglamanıza yazın (JSON-Log, Syslog veya Windows Event Log).
İşletmede hata ayıklama: hataları saatler yerine dakikalar içinde bulma
İstekler “tuhaf” görünüyorsa, bunun nedeni nadiren Delphi-HTTP’in kendisidir; daha ziyade proxy başlıkları, yönlendirme mantığı ve zaman aşımlarının bir kombinasyonudur. Günlük kullanımda işe yarayan üç debug kontrolü:
- Header-Dump (hedefli): 4xx/5xx durumlarında ayrıca Host, Forwarded, X-Forwarded-For, X-Forwarded-Proto, User-Agent ve Request-URI değerlerini loglayın. Ancak yalnızca hatalarda — aksi takdirde log maliyetli ve karmaşık hale gelir.
- Base-URL kontrolü: Yönlendirmeler veya callback-URL’leri başarısız olduğunda ForwardedInfo.BaseUrl değerini loglayın. Birçok hata hemen görünür („http://127.0.0.1“ yerine „https://api…“).
- Zaman aşımı korelasyonu: Proxy’den gelen bir 504, bir Delphi-zaman aşımı ile aynı değildir. nginx proxy_read_timeout ile Delphi tarafındaki Idle-/Read-timeout’ların uyumlu olması gerekir.
Kenar durumları: WebSockets, streaming ve büyük istekler
nginx arkasındaki WebSockets
WebSockets için nginx’in Upgrade ve Connection başlıklarını doğru ayarlaması gerekir. Ek olarak backend bağlantıyı „çok erken“ kapatmamalıdır. Delphi tarafında önemli olan, WebSocket bileşeninizin (veya bir SSE/Streaming uç noktasının) reverse proxy’lerle başa çıkabilmesi ve Heartbeats/Keep-Alives’in düzgün uygulanmış olmasıdır.
Büyük yüklemeler ve 413 hatası
Klasik durum: Delphi bir yüklemeyi kabul eder, ancak nginx önceden 413 Request Entity Too Large ile engeller. Bunu açıkça client_max_body_size ile yönetin ve Delphi tarafında istek limitlerini ayarlayın. Belge veya görsel verilerle çalışan süreç-odaklı yazılım çözümleri için bu bir istisna değil, normal işletimdir.
HTTPS-Offloading und „Secure Cookies“
Eğer Delphi servisi oturum çerezleri (Session-Cookies) oluşturuyorsa, bu çerezler dışarıdan gelen HTTPS bağlantılarında genellikle Secure olarak işaretlenmelidir. Uygulamanızın bunu yapıp yapmadığı çoğu zaman orijinal isteğin HTTPS olduğunu „bildiğine“ bağlıdır. Tam da bu noktada X-Forwarded-Proto/Forwarded başlıklarının tutarlı değerlendirilmesi yardımcı olur.
Ne zaman çabaya değer – ve nerede tersine dönebilir
Gösterilen yaklaşım, Delphi servisi artık LAN içinde „yalın“ halde durmayıp üretim kenarının bir parçası olduğunda her zaman işe yarar: birden fazla domain, SSO/SAML ara yüzleri, Public APIs, çoklu kiracılık veya daha sıkı denetim gereksinimleri. Yaklaşım, Forwarded başlıklarına körü körüne güvenildiğinde veya proxy topolojileri belgelenmediğinde (birden fazla Ingress katmanı, Cloud-LB artı nginx artı Sidecar) işlevini yitirir. Bu durumda istemci IP’si ve şema hızla „herhangi bir şey“ olur.
Kesin bir sınır: Eğer karmaşık güven kurallarına (CIDR, IPv6 ağları, dinamik LB IP’leri) ihtiyacınız varsa, güven doğrulamasını genişletmelisiniz (gerçek IP-parsing, ağ maskeleri) veya altyapıyı yalnızca tanımlı bir proxy’nin Delphi portuna erişebileceği şekilde tasarlamalısınız (Firewall/Security Groups). Sonuçta bu genellikle daha sağlam bir işletme kararıdır.
Fazit: nginx ile Reverse Proxy ve Delphi’yi düzgün işletmek, “Forwarded”ı doğru yapmak demektir
nginx ile bir Reverse Proxy, Delphi-REST-Server için iyi bir standart yapı taşıdır – ancak işletmede kurulumu stabil kılan şey Forwarded ve X-Forwarded-* başlıklarının doğru işlenmesidir. Özü basittir: Başlıkları yalnızca güvenilir proxy’lerden kabul edin, istemci IP’si/şema/host’u tutarlı biçimde türetin ve bu temeli yönlendirmelerde, loglamada ve güvenlik kontrollerinde uygulayın. Yukarıdaki snippet ile bu amaç için WebBroker, Horse veya kendi HTTP sunucularınıza entegre edilebilen temiz, legacy-uyumlu bir temel elde edersiniz.
Eğer mevcut bir Delphi backend’i nginx arkasında konsolide etmek veya açık işletme sınırına sahip bir Delphi REST-API und REST-Server yönünde modernize etmek istiyorsanız, proxy zincirinin ve başlık değerlendirmesinin teknik bir incelemesi genellikle en hızlı etkiyi sağlayan adımdır. Kısa bir teknik değerlendirme için Net-Base ile iletişime geçin.
Uzmanlık alanında, entegrasyonlar, veri akışları ve yazılımın ileriye dönük geliştirilmesi temiz şekilde iç içe geçmesi gerektiğinde Nginx Reverse Proxy ve Forwarded başlıkları da önemli bir rol oynar.