Net-Base Περιοδικό

23.05.2026

Reverse Proxy με nginx και Delphi: καθαρή διαχείριση του Forwarded, πραγματική διεύθυνση IP του πελάτη και ανθεκτικές βάσεις URL

Όταν διακομιστές Delphi-REST τρέχουν πίσω από nginx, συχνά αλλοιώνονται η Client‑IP, η ανίχνευση HTTPS και οι απόλυτες διευθύνσεις URL. Αυτό το απόσπασμα πηγαίου κώδικα δείχνει έναν στιβαρό χειρισμό των κεφαλίδων Forwarded/X-Forwarded (συμπεριλαμβανομένης της λίστας Trust-Proxy), τυπικές ρυθμίσεις nginx και συμβουλές αποσφαλμάτωσης για τη λειτουργία.

23.05.2026

Ένας Reverse Proxy με nginx und Delphi είναι στην πράξη συνήθως όχι «nice to have», αλλά ο σαφής διαχωρισμός μεταξύ της άκρης του Internet και της εφαρμογής: TLS-τερματισμός (HTTPS-Offloading), κεντρικοί κανόνες Header/CORS, όρια ρυθμού (Rate-Limits), ενιαία logs, Blue/Green-Rollouts ή απλά η φιλοξενία πολλαπλών υπηρεσιών κάτω από ένα domain. Αυτό που συχνά υποτιμάται: Μόλις ο nginx βρίσκεται «μπροστά», ο Delphi-server βλέπει μόνο τη διεύθυνση IP του proxy, συχνά μόνο «http» αντί για «https» και παράγει λανθασμένους απόλυτους συνδέσμους (Redirects, Callback-URLs, OpenAPI-Server-URL). Ακριβώς αυτά τα τρία σημεία προκαλούν αργότερα χρόνο για debugging σε λειτουργία.

Αυτό το απόσπασμα κώδικα δείχνει ένα ανθεκτικό πρότυπο για το πώς να αξιολογείτε στο Delphi το Forwarded ή τα X-Forwarded-* με ασφάλεια — συμπεριλαμβανομένης της Trust-Proxy-Liste (σημαντικό έναντι Header-Spoofing) και μιας συνεπούς Request-Base-URL. Επιπλέον παρέχονται πρακτικές ρυθμίσεις nginx και οδηγίες για οριακές περιπτώσεις όπως WebSockets, μεγάλα uploads και timeouts.

Γιατί ρυθμίσεις Reverse Proxy «μπερδεύουν» τους Delphi-server

Ο nginx λειτουργεί ως Reverse Proxy και επικοινωνεί με την υπηρεσία Delphi τυπικά χωρίς κρυπτογράφηση (HTTP) στο εσωτερικό δίκτυο ή στο localhost, ενώ ο client εξωτερικά έρχεται μέσω HTTPS. Χωρίς πρόσθετα headers το Delphi δεν γνωρίζει τα εξής:

  • Original-Schema (https vs. http) – σημαντικό για Redirects και απόλυτες διευθύνσεις URL.
  • Original-Host (προσαρμοσμένο domain πελάτη, Port) – σημαντικό για ρυθμίσεις Multi-Tenant, CORS και Callback-URLs.
  • Original-Client-IP – σημαντικό για Audit, Rate-Limits, Geo-Checks και αξιολογήσεις ασφάλειας.

Ο nginx μπορεί να μεταφέρει αυτές τις πληροφορίες μέσω headers. Συνήθεις είναι τα X-Forwarded-For, X-Forwarded-Proto και X-Forwarded-Host; επιπλέον έχει οριστεί πρότυπο ο RFC-header Forwarded. Σημαντικό: αυτά τα headers από την πλευρά της εφαρμογής δεν είναι αυτόματα αξιόπιστα, γιατί ένας client μπορεί να τα στείλει μόνος του — γίνονται αξιόπιστα μόνον όταν προέρχονται από έναν γνωστό proxy.

nginx-Konfiguration: die minimal sinnvollen Proxy-Header

Ένα σταθερό σημείο εκκίνησης (HTTP/1.1, Keep-Alive, Upgrade για WebSockets) μοιάζει ως εξής. Το snippet είναι σκόπιμα σύντομο· συμπληρώνετε ανάλογα με το περιβάλλον HSTS, Rate-Limits και 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;
  }
}

Σκοπός: Η εφαρμογή λαμβάνει αξιόπιστα το Host, την Client-IP και το Schema. Περιορισμός: $proxy_add_x_forwarded_for προσθέτει την τρέχουσα διεύθυνση IP του proxy σε μια πιθανώς υπάρχουσα αλυσίδα· αυτό είναι χρήσιμο για Multi-Proxy-Setups, αλλά καθιστά τη σωστή αξιολόγηση στην πλευρά του Delphi ακόμη πιο κρίσιμη. Παγίδα: Εάν στο nginx δεν ορίσετε τον Host-Header, το Delphi ενδέχεται να βλέπει μόνο τον Upstream-Host (127.0.0.1), κάτι που διακόπτει Redirects και Origin-Checks.

Delphi Απόσπασμα πηγαίου κώδικα: Forwarded/X-Forwarded ανθεκτική αξιολόγηση (με Trust-Proxy-Liste)

Ο ακόλουθος κώδικας είναι σκόπιμα ανεξάρτητος από framework: λειτουργεί πάνω σε ένα ελάχιστο interface (Header + RemoteIP) και μπορεί να προσαρμοστεί σε WebBroker, RAD Server ή Horse. Κύρια σημεία:

  • Προτεραιότητα: RFC Forwarded (εφόσον υπάρχει) έναντι X-Forwarded-*.
  • Trust: Να αξιολογούνται τα Forwarded-Header μόνο εάν ο άμεσος peer (RemoteIP) είναι γνωστός proxy.
  • Parsing: Λήψη υπόψη IPv6, εισαγωγικά, θύρες και αλυσίδες στο X-Forwarded-For.
  • Output: μια Base-URL την οποία μπορείτε να χρησιμοποιήσετε για απόλυτους συνδέσμους, Redirects ή OpenAPI.
Delphi
unit Net-Base.ProxyForwarding;

interface

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

type
  // Ελάχιστο interface προσαρμογέα: υλοποιήστε το για WebBroker/Horse κ.λπ.
  IHeaderReader = interface
    ['{C2D2E5B9-2C2E-4D37-9D73-3CDB5A7E7EEA}']
    function GetHeaderValue(const AName: string): string;
    function GetRemoteIP: string; // άμεσο TCP peer (συνήθ. nginx)
  end;

  TForwardedInfo = record
    ClientIP: string;
    Proto: string; // http/https
    Host: string;
    Port: Integer;
    function EffectiveScheme: string;
    function EffectiveHostPort: string;
    function BaseUrl: string; // π.χ. 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"
  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 (ΠΡΟΣΟΧΗ: με γυμνό IPv6 χωρίς [] δεν είναι αξιόπιστο)
  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
  // Παράδειγμα: 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;

  // Πολλαπλά στοιχεία διαχωρίζονται με κόμμα· παίρνουμε το πρώτο (πλησιέστερο στον 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 μπορεί να είναι IP ή "unknown"; το IPv6 μπορεί να είναι σε []
      V := V.Trim;
      if SameText(V, 'unknown') then
        Continue;
      // εμφανίζεται και το for=1.2.3.4:5678
      if V.Contains(':') and (not V.StartsWith('[')) then
        V := FirstCsvToken(V); // προληπτικά
      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
  // Για πραγματική κανονικοποίηση IPv6 θα χρειαζόταν parsing IP· εδώ σκόπιμα πιο πρακτικό.
  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; // Εφεδρική τιμή: άμεση TCP peer

  // Μόνο αν η άμεση αντίπαλος είναι γνωστός proxy, επεξεργαζόμαστε τα Forwarded headers.
  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-* ως fallback/συμπλήρωμα
    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;

  // Αν χρειαστεί πάρτε το Host από το header "Host" (αν δεν υπάρχουν proxy headers)
  if Result.Host = '' then
  begin
    Host := Req.GetHeaderValue('Host');
    if Host <> '' then
      SplitHostPort(Host, Result.Host, Result.Port);
  end;
end;

end.

Σκοπός: Από κάθε αίτημα λαμβάνετε μια συνεπή εικόνα για ClientIP, Proto και Host καθώς και για το BaseUrl. Αυτές οι πληροφορίες μπορούν να χρησιμοποιηθούν κεντρικά για καταγραφή (Logging), αποφάσεις ασφάλειας (π.χ. IP-Allowlist) και γεννήτρια συνδέσμων.

Γιατί χρειάζεται η Trust-Proxy-Liste: Χωρίς έλεγχο εμπιστοσύνης, ένας επιτιθέμενος θα μπορούσε να φτάσει απευθείας στη θύρα Delphi σας (κακή διαμόρφωση, εσωτερική δρομολόγηση, VPN) και απλώς να στείλει X-Forwarded-For: 127.0.0.1. Έτσι θα τεθούν σε κίνδυνο τα Audit-Trails, τα Rate-Limits ή endpoints που είναι «επιτρεπτά μόνο εσωτερικά». Εμπιστεύεστε τα Forwarded-Header μόνο όταν ο άμεσος peer (RemoteIP) είναι proxy που ελέγχετε (π.χ. 127.0.0.1, Load-Balancer-IP, Kubernetes-Ingress).

Προβλήματα προς προσοχή: Το IPv6 χωρίς αγκύλες [] δεν είναι σαφές στη σύνταξη Host:port. Στον HTTP-Host-Header το IPv6 συνήθως σημειώνεται με [] — τηρείτε αυτό. Για σύνθετα εύρη IP (CIDR) πρέπει να επεκτείνετε τη λίστα Trust (π.χ. με πραγματική ανάλυση διευθύνσεων IP).

Ενσωμάτωση σε WebBroker/Horse/RAD Server: πού συνδέεται ο κώδικας

Σε WebBroker (TWebRequest) οι header συνήθως προέρχονται από ContentFields ή GetFieldByName, ενώ η Remote-IP εξαρτάται από το server-backend. Σε Horse (ή άλλα HTTP-frameworks) υπάρχει συνήθως Req.Headers και μια ιδιότητα Remote-IP. Σημαντική αρχή: η RemoteIP πρέπει να είναι η TCP-αντίπαλη πλευρά, όχι κάποια τιμή από header.

Στην πράξη αποδεδειγμένο: Δημιουργήστε κατά την εκκίνηση της υπηρεσίας μια TTrustedProxyList από τη διαμόρφωση (INI/ENV), π.χ. „127.0.0.1“ για τοπικές nginx-ρυθμίσεις ή την IP του Load Balancer σας. Στη συνέχεια καλέστε ResolveForwardedInfo για κάθε αίτημα και γράψτε τα πεδία στο δομημένο σας logging (JSON-Log, Syslog ή Windows Event Log).

Debugging σε λειτουργία: πώς να βρείτε σφάλματα σε λεπτά αντί για ώρες

Όταν τα αιτήματα φαίνονται «περίεργα», σπάνια οφείλεται στο Delphi-HTTP καθεαυτό, αλλά σε συνδυασμό από proxy-headers, redirect-λογική και timeouts. Τρεις έλεγχοι debugging που αξίζουν στην καθημερινή λειτουργία:

  1. Header-Dump (στοχευμένο): Καταγράψτε σε log σε περιπτώσεις 4xx/5xx επιπλέον Host, Forwarded, X-Forwarded-For, X-Forwarded-Proto, User-Agent και Request-URI. Όμως μόνο σε σφάλματα — αλλιώς το log γίνεται ακριβό και δυσδιάκριτο.
  2. Έλεγχος Base-URL: Όταν τα redirects ή οι callback-URLs αποτυγχάνουν, καταγράψτε το ForwardedInfo.BaseUrl. Πολλά σφάλματα γίνονται αμέσως εμφανή („http://127.0.0.1“ αντί για „https://api…“).
  3. Συσχέτιση Timeouts: Ένα 504 από τον proxy δεν είναι το ίδιο με ένα Delphi-timeout. Το nginx proxy_read_timeout και οι Delphi-πλευρικές Idle-/Read-Timeouts πρέπει να ταιριάζουν.

Οριακές περιπτώσεις: WebSockets, Streaming και μεγάλα αιτήματα

WebSockets πίσω από nginx

Για WebSockets το nginx χρειάζεται σωστές ρυθμίσεις για Upgrade και Connection. Επιπλέον το backend δεν πρέπει να κλείνει «πολύ νωρίς». Από την πλευρά του Delphi είναι σημαντικό ότι η WebSocket-συλλογή σας (ή ένας SSE/streaming endpoint) μπορεί να χειριστεί reverse proxies και ότι τα Heartbeats/Keep-Alives είναι υλοποιημένα σωστά.

Μεγάλα uploads και 413-σφάλματα

Κλασικό παράδειγμα: το Delphi αποδέχεται ένα upload, αλλά το nginx το μπλοκάρει πρώτα με 413 Request Entity Too Large. Ελέγξτε αυτό ρητά μέσω client_max_body_size και προσαρμόστε από την πλευρά του Delphi τα όρια αιτήσεων. Για λύσεις λογισμικού που δουλεύουν κοντά στις διεργασίες με έγγραφα ή εικόνες, αυτό δεν είναι εξαίρεση αλλά κανονική λειτουργία.

HTTPS-Offloading und „Secure Cookies“

Εάν η υπηρεσία Delphi ορίζει session-cookies, αυτά σε εξωτερικό HTTPS συνήθως πρέπει να επισημαίνονται ως Secure. Το αν η εφαρμογή σας το κάνει αυτό εξαρτάται συχνά από το αν «γνωρίζει» ότι το αρχικό request ήταν HTTPS. Εδώ ακριβώς βοηθά η συνεπής αξιολόγηση του X-Forwarded-Proto/Forwarded.

Πότε αξίζει η προσπάθεια – και πού μπορεί να αποτύχει

Η παρουσιαζόμενη προσέγγιση αξίζει όποτε η υπηρεσία Delphi δεν βρίσκεται απευθείας στο LAN αλλά είναι μέρος ενός παραγωγικού edge: πολλαπλοί domain, SSO/SAML διεπαφές, δημόσια APIs, υποστήριξη πολλαπλών πελατών ή αυστηρότερες απαιτήσεις audit. Αποτυγχάνει εκεί που εμπιστεύεστε τυφλά τα Forwarded headers ή όταν οι τοπολογίες proxy δεν είναι τεκμηριωμένες (πολλαπλά στάδια ingress, Cloud-LB μαζί με nginx και sidecar). Τότε η client-IP και το scheme γίνονται σύντομα μη αξιόπιστα.

Μια σαφής οριοθέτηση: Αν χρειάζεστε σύνθετους κανόνες εμπιστοσύνης (CIDR, IPv6 δίκτυα, δυναμικές IPs του LB), θα πρέπει να επεκτείνετε τον έλεγχο εμπιστοσύνης (πραγματική ανάλυση IP, μάσκες δικτύου) ή να σχεδιάσετε την υποδομή έτσι ώστε μόνο ένας καθορισμένος proxy να μπορεί να φτάσει τη θύρα του Delphi (Firewall/Security Groups). Στο τέλος αυτή είναι συνήθως η πιο ανθεκτική λειτουργική απόφαση.

Συμπέρασμα: Η σωστή λειτουργία Reverse Proxy με nginx και Delphi σημαίνει «Forwarded σωστά»

Ένας Reverse Proxy με nginx είναι για Delphi-REST-Server ένα καλό πρότυπο δομικό στοιχείο – αλλά μόνο η σωστή μεταχείριση των Forwarded και X-Forwarded-* καθιστά το setup σταθερό στην παραγωγή. Ο πυρήνας είναι απλός: αποδεχθείτε headers μόνο από αξιόπιστους proxies, εξάγετε συνεπή Client-IP/Scheme/Host και εφαρμόστε αυτή τη βάση σε redirects, logging και ελέγχους ασφαλείας. Με το snippet παραπάνω έχετε μια καθαρή, κατάλληλη για legacy βάση, που μπορεί να ενσωματωθεί σε WebBroker, Horse ή ιδιόκτητους HTTP-servers.

Εάν θέλετε να ενοποιήσετε ένα υπάρχον backend Delphi πίσω από nginx ή να εκσυγχρονίσετε προς Delphi REST-API και REST-Server με σαφή γραμμή λειτουργίας, μια τεχνική ανασκόπηση της αλυσίδας proxy και της αξιολόγησης headers είναι συχνά ο ταχύτερος μοχλός. Επικοινωνήστε με Net-Base για μια σύντομη τεχνική κατάταξη.

Σε τεχνικό επίπεδο, ο nginx Reverse Proxy και τα Forwarded headers παίζουν επίσης σημαντικό ρόλο όταν πρέπει να συνεργάζονται καθαρά ενσωματώσεις, ροές δεδομένων και περαιτέρω ανάπτυξη.

Συζητήστε ένα έργο ή σχέδιο εκσυγχρονισμού με Net-Base.

Κοινοποίηση δημοσίευσης

Μοιραστείτε αυτήν την ανάρτηση απευθείας

LinkedIn, X, XING, Facebook, WhatsApp und E‑Mail είναι άμεσα διαθέσιμα. Για το Instagram ετοιμάζουμε άμεσα τον σύνδεσμο και το σύντομο κείμενο.

Ηλεκτρονικό ταχυδρομείο

Το Instagram ανοίγει σε μια νέα καρτέλα. Ο σύνδεσμος και το σύντομο κείμενο αντιγράφονται πρώτα στο πρόχειρο.