Server-Sent Events waren früher der stille Cousin von WebSockets: eine einfache HTTP-Antwort, die nie endet, wobei der Server ein kleines Textereignis nach dem anderen schreibt. Plötzlich sind sie überall. Die gestreamten Antworten der LLM-APIs kommen als SSE an, MCP-Server sprechen es über Streamable HTTP, und Dashboards, Benachrichtigungen und Job-Fortschrittsfeeds nutzen es, weil es durch jeden Proxy geht, der HTTP durchlässt.
sgcWebSockets 2026.10 fügt TsgcSSEClient hinzu, einen EventSource-Client für Delphi, C++Builder und .NET. Er liest den Stream in einem Hintergrundthread, liefert Ihnen jedes Ereignis mit seinem Typ, seinen Daten und seiner ID, verbindet sich bei einem Verbindungsabbruch von selbst neu und setzt mit Last-Event-ID fort, sodass dazwischen nichts verloren geht. Die Komponente ist nicht Teil des aktuellen Downloads. Sie kommt mit dem Release 2026.10, in jeder Edition.
TsgcSSEClient empfängt typisierte Ereignisse, verliert die Verbindung und setzt mit Last-Event-ID fort. Auch auf YouTube.
Warum SSE jetzt wichtig ist
Ein SSE-Stream ist eine text/event-stream-Antwort aus kurzen Blöcken: einer optionalen event:-Zeile mit dem Typ, einer oder mehreren data:-Zeilen, einer optionalen id: und einer Leerzeile, die das Ereignis beendet. Ein Server kann auch retry: senden, um dem Client mitzuteilen, wie lange er vor einer Wiederverbindung warten soll. Das Format ist einfach, aber ein korrekter Client muss Fragmente verarbeiten, die eine Zeile an beliebiger Stelle teilen, mehrzeilige Daten, CR-, LF- und CRLF-Zeilenenden, und eine Wiederverbindung, die sich merkt, wo sie aufgehört hat. Genau das übernimmt TsgcSSEClient für Sie.
Schnellstart
Fügen Sie die Unit sgcHTTP_SSE_Client hinzu, setzen Sie die URL, behandeln Sie OnEvent und rufen Sie Open auf. Anfrage-Header wie ein Bearer-Token kommen in Headers, jeweils eine Zeile Name: Value, und sie werden bei jeder Verbindung gesendet, Wiederverbindungen eingeschlossen.
procedure TForm1.FormCreate(Sender: TObject);
begin
oSSE := TsgcSSEClient.Create(nil);
oSSE.URL := 'https://www.example.com/events';
oSSE.Headers.Add('Authorization: Bearer ' + FToken);
oSSE.OnEvent := OnSSEEvent;
oSSE.OnError := OnSSEError;
oSSE.Open;
end;
procedure TForm1.OnSSEEvent(Sender: TObject; const aEvent: TsgcSSEEvent);
begin
if aEvent.EventType = 'alert' then
ShowMessage(aEvent.Data)
else
Memo1.Lines.Add(aEvent.EventType + ' #' + aEvent.Id + ': ' + aEvent.Data);
end;
procedure TForm1.OnSSEError(Sender: TObject; const aError: string);
begin
Memo1.Lines.Add('error: ' + aError);
end;
Jedes Ereignis ist typisiert. EventType trägt den Namen aus event:, sodass ein einziger Handler alert, tick oder jeden von Ihrem Server erfundenen Typ weiterleiten kann. In einer VCL- oder FireMonkey-Anwendung werden die Ereignisse standardmäßig an den Hauptthread geliefert, sodass die Handler die Oberfläche direkt anfassen können. NotifyEvents ändert das. OnOpen und OnClose teilen Ihnen mit, wann der Stream beginnt und endet, und ReadyState meldet sseConnecting, sseOpen oder sseClosed.
Der Client reagiert auf den Server genauso wie das EventSource eines Browsers:
- Ein 200 mit
text/event-streamöffnet den Stream. Weiterleitungen werden befolgt. - Ein 204 No Content bedeutet, dass der Server möchte, dass der Client anhält. Er schließt ohne Fehler und verbindet sich nicht neu.
- Jeder andere Status oder ein falscher Content-Type löst
OnErrorund dannOnCloseaus, und der Client verbindet sich nicht neu. Einen 401 endlos zu wiederholen, hilft niemandem. - Ein Netzwerkfehler oder ein Lese-Timeout löst
OnErroraus, und dann verbindet sich der Client neu. Wenn der Server den Stream einfach beendet, verbindet sich der Client ebenfalls neu.
Mit Last-Event-ID fortsetzen
Wenn der Server seinen Ereignissen ein id:-Feld gibt, speichert der Client das letzte in LastEventId und sendet es bei jeder Wiederverbindung im Header Last-Event-ID. Der Server liest den Header und setzt nach diesem Ereignis fort, sodass eine abgebrochene Verbindung keine Nachricht kostet und keine wiederholt. Innerhalb einer Sitzung braucht das überhaupt keinen Code.
Um nach einem Neustart der Anwendung fortzusetzen, speichern Sie LastEventId beim Stoppen und stellen Sie es vor Open wieder her. Die Eigenschaft behält ihren Wert nach Close, und ein vor Open gesetzter Wert wird mit der allerersten Anfrage gesendet.
procedure TForm1.StartStream;
begin
if FileExists('sse_last_id.txt') then
oSSE.LastEventId := Trim(TFile.ReadAllText('sse_last_id.txt'));
oSSE.Open;
end;
procedure TForm1.StopStream;
begin
oSSE.Close;
TFile.WriteAllText('sse_last_id.txt', oSSE.LastEventId);
end;
Zum Fortsetzen muss der Server mitspielen: Er muss id: mit seinen Ereignissen senden und den Header Last-Event-ID beachten.
Eine Wiederverbindungsrichtlinie, die Sie steuern
Die Wiederverbindung ist standardmäßig aktiviert, mit einer Verzögerung von 3 Sekunden. ReconnectOptions macht daraus die Richtlinie, die Ihr Server verdient: exponentielles Backoff mit einem Multiplikator, eine Obergrenze für die Verzögerung, zufälliger Jitter, damit nicht tausend Clients in derselben Millisekunde zurückkommen, und ein Limit für die Versuche.
procedure TForm1.SetupReconnect;
begin
oSSE.ReconnectOptions.Interval := 1000;
oSSE.ReconnectOptions.Backoff := True;
oSSE.ReconnectOptions.BackoffMultiplier := 2.0;
oSSE.ReconnectOptions.MaxInterval := 30000;
oSSE.ReconnectOptions.Jitter := 0.2;
oSSE.ReconnectOptions.MaxAttempts := 10;
oSSE.OnReconnect := OnSSEReconnect;
end;
MaxAttempts zählt aufeinanderfolgende Fehlschläge, und der Zähler beginnt jedes Mal von neuem, wenn eine Verbindung geöffnet wird, sodass einem Client, der wochenlang mit dem gelegentlichen Netzwerkaussetzer läuft, nie die Versuche ausgehen. Der Server hat ebenfalls ein Wort mitzureden: Ein retry:-Feld ersetzt Interval für den Rest der Sitzung. Das letzte Wort haben Sie, in OnReconnect, das vor jedem Versuch mit der berechneten Verzögerung ausgelöst wird:
procedure TForm1.OnSSEReconnect(Sender: TObject; aAttempt: Integer;
var aDelay: Integer; var aCancel: Boolean);
begin
// stop after the fifth failed attempt, otherwise wait at least 2 seconds
if aAttempt > 5 then
aCancel := True
else if aDelay < 2000 then
aDelay := 2000;
end;
TLS, Proxy und Authentifizierung
Der Stream wird mit einem TsgcHTTP1Client gelesen, und OnBeforeConnect übergibt ihn Ihnen vor jedem Verbindungsversuch. Alles, was der HTTP-Client kann, kann auch der SSE-Client: TLS-Optionen, ein Proxy, Basisauthentifizierung oder ein längeres Lese-Timeout.
procedure TForm1.OnSSEBeforeConnect(Sender: TObject;
const aHTTP: TsgcHTTP1Client);
begin
aHTTP.TLSOptions.Version := tls1_2;
aHTTP.Proxy.Enabled := True;
aHTTP.Proxy.Host := '192.168.1.10';
aHTTP.Proxy.Port := 8080;
aHTTP.ReadTimeout := 60000;
end;
OnBeforeConnect und OnReconnect laufen immer im Hintergrundthread, halten Sie die Oberfläche also aus ihnen heraus.
Der Parser für LLM-Streams und MCP
Oft brauchen Sie gar kein langlebiges EventSource. Eine LLM-Chat-Vervollständigung mit stream: true ist ein POST, dessen Antwort SSE ist, und eine Streamable-HTTP-Antwort von MCP kann ebenfalls SSE sein. Dafür ist der Parser im Client öffentlich: TsgcSSEParser. Füttern Sie ihn mit rohen Bytes oder Text in Fragmenten beliebiger Größe, genau wie das Netzwerk sie liefert, und er löst OnEvent für jedes vollständige Ereignis und OnRetry für jedes gültige retry:-Feld aus. Ein Fragment kann mitten in einer Zeile oder mitten in einem UTF-8-Zeichen enden, und der Parser behält den Rest bis zum nächsten Aufruf.
procedure TForm1.ParseStream;
begin
FParser := TsgcSSEParser.Create;
FParser.OnEvent := OnParserEvent;
// chunks arrive as the network delivers them, split anywhere
FParser.Feed('event: content_block_delta'#10'data: {"delta":{"text":"Hel');
FParser.Feed('lo"}}'#10#10'event: message_stop'#10);
FParser.Feed(TEncoding.UTF8.GetBytes('data: {}'#10#10));
// start again for the next response
FParser.Reset;
end;
procedure TForm1.OnParserEvent(Sender: TObject; const aEvent: TsgcSSEEvent);
begin
Memo1.Lines.Add(aEvent.EventType + ' ' + aEvent.Data);
end;
Diese drei Fragmente erzeugen zwei Ereignisse: content_block_delta mit dem vollständigen JSON {"delta":{"text":"Hello"}}, und message_stop. Reset verwirft jedes halb gelesene Ereignis vor der nächsten Antwort, und LastEventId verrät Ihnen die letzte ID, die der Parser gesehen hat.
C# für .NET
Die .NET-Edition von sgcWebSockets hat dieselbe Komponente mit denselben Namen, im Namespace esegece.sgcWebSockets. Headers ist eine Liste von Name: Value-Zeichenfolgen, und die Ereignisse sind normale .NET-Ereignisse.
using esegece.sgcWebSockets;
string token = args.Length > 0 ? args[0] : "";
var sse = new TsgcSSEClient();
sse.URL = "https://www.example.com/events";
sse.Headers.Add("Authorization: Bearer " + token);
if (File.Exists("sse_last_id.txt"))
sse.LastEventId = File.ReadAllText("sse_last_id.txt").Trim();
sse.ReconnectOptions.Backoff = true;
sse.ReconnectOptions.MaxInterval = 30000;
sse.OnEvent += (sender, e) => Console.WriteLine($"{e.EventType} #{e.Id}: {e.Data}");
sse.OnError += (sender, error) => Console.WriteLine("error: " + error);
sse.Open();
Console.ReadLine();
sse.Close();
File.WriteAllText("sse_last_id.txt", sse.LastEventId);
TsgcSSEParser ist auch dort, mit Feed-Überladungen für ein byte[], einen Ausschnitt davon oder eine Zeichenfolge.
Die Demos ausprobieren
Beide Editionen enthalten eine Demo, die vollständig offline läuft. Sie hostet einen kleinen SSE-Server innerhalb derselben Anwendung auf 127.0.0.1, Port 5580, der nummerierte Ereignisse mit einer id: streamt, die Typen message, tick und alert abwechselt, ein retry:-Feld sendet und nach der empfangenen Last-Event-ID fortsetzt. Trennen Sie die Verbindung und sehen Sie zu, wie der Client sich neu verbindet und ab der nächsten ID fortsetzt, ohne Lücke und ohne Duplikat.
- Delphi:
Demos\20.HTTP_Protocol\17.SSE_Client_Component - .NET:
demos\20.HTTP_Protocol\17.SSE_Client_Component, das zusätzlich einen Schalter/selftesthat, der Reihenfolge, Duplikate und den Fortsetzungs-Header selbstständig prüft
Verfügbarkeit
TsgcSSEClient und TsgcSSEParser kommen in sgcWebSockets 2026.10, für Delphi, C++Builder und .NET, in jeder Edition. Sie sind nicht in der Version enthalten, die Sie heute herunterladen können. Wenn 2026.10 erscheint, wird es auf der Download-Seite stehen, und die Komponente wird in der SGC-HTTP-Palettenregisterkarte registriert.
Die vollständige Referenz, mit jeder Eigenschaft, jedem Ereignis und dem Leitfaden zum Fortsetzen, steht in der TsgcSSEClient-Hilfe. Für alles andere, was sgcWebSockets mit Server-Sent Events macht, siehe die SSE-Produktseite.
Weiterlesen
Fragen, Feedback oder Hilfe bei der Migration? Nehmen Sie Kontakt auf. Sie erhalten eine Antwort von den Leuten, die den Code geschrieben haben.
