Server-Sent Events costumava ser o primo quieto dos WebSockets: uma resposta HTTP simples que nunca termina, com o servidor escrevendo um pequeno evento de texto após outro. De repente, está em toda parte. As respostas em stream das APIs de LLM chegam como SSE, servidores MCP falam esse protocolo por Streamable HTTP, e dashboards, notificações e feeds de progresso de tarefas o usam porque ele passa por qualquer proxy que deixe o HTTP passar.
O sgcWebSockets 2026.10 adiciona o TsgcSSEClient, um cliente EventSource para Delphi, C++Builder e .NET. Ele lê o stream em uma thread em segundo plano, entrega cada evento com seu tipo, dados e id, reconecta sozinho quando a conexão cai, e retoma com Last-Event-ID para que nada se perca no meio do caminho. O componente não faz parte da versão disponível para download atualmente. Ele chega com o lançamento 2026.10, em todas as edições.
O TsgcSSEClient recebendo eventos tipados, perdendo a conexão e retomando com Last-Event-ID. Também no YouTube.
Por que o SSE importa agora
Um stream SSE é uma resposta text/event-stream composta por blocos curtos: uma linha opcional event: com o tipo, uma ou mais linhas data:, um id: opcional, e uma linha em branco que encerra o evento. Um servidor também pode enviar retry: para dizer ao cliente quanto tempo esperar antes de reconectar. O formato é simples, mas um cliente correto precisa lidar com blocos que dividem uma linha em qualquer ponto, dados em várias linhas, quebras de linha CR, LF e CRLF, e uma reconexão que lembra onde parou. É essa parte que o TsgcSSEClient faz por você.
Início Rápido
Adicione a unit sgcHTTP_SSE_Client, defina a URL, trate OnEvent e chame Open. Cabeçalhos de requisição, como um token bearer, vão em Headers, cada um como uma linha Nome: Valor, e são enviados em toda conexão, incluindo reconexões.
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;
Cada evento é tipado. EventType carrega o nome de event:, de modo que um único handler pode rotear alert, tick ou qualquer tipo que o seu servidor inventar. Em uma aplicação VCL ou FireMonkey, os eventos são entregues à thread principal por padrão, então os handlers podem tocar a interface diretamente. NotifyEvents muda isso. OnOpen e OnClose avisam quando o stream começa e termina, e ReadyState informa sseConnecting, sseOpen ou sseClosed.
O cliente reage ao servidor da mesma forma que o EventSource de um navegador:
- Um 200 com
text/event-streamabre o stream. Redirecionamentos são seguidos. - Um 204 No Content significa que o servidor quer que o cliente pare. Ele fecha sem erro e não reconecta.
- Qualquer outro status, ou um tipo de conteúdo errado, dispara
OnErrore depoisOnClose, e o cliente não reconecta. Tentar novamente um 401 para sempre não ajuda ninguém. - Um erro de rede ou um timeout de leitura dispara
OnError, e então o cliente reconecta. Quando o servidor simplesmente encerra o stream, o cliente também reconecta.
Retomando com Last-Event-ID
Quando o servidor dá aos seus eventos um campo id:, o cliente guarda o último em LastEventId e o envia no cabeçalho Last-Event-ID em toda reconexão. O servidor lê o cabeçalho e continua depois daquele evento, de modo que uma conexão perdida não custa nenhuma mensagem e não repete nenhuma. Dentro de uma única sessão, isso não exige nenhum código.
Para retomar depois que a aplicação reiniciar, salve LastEventId quando parar e restaure-o antes de Open. A propriedade mantém seu valor depois de Close, e um valor definido antes de Open é enviado já na primeira requisição.
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;
Retomar exige a cooperação do servidor: ele precisa enviar id: com seus eventos e respeitar o cabeçalho Last-Event-ID.
Uma política de reconexão que você controla
A reconexão vem ativada por padrão, com um atraso de 3 segundos. ReconnectOptions transforma isso na política que o seu servidor merecer: backoff exponencial com um multiplicador, um teto para o atraso, jitter aleatório para que mil clientes não voltem no mesmo milissegundo, e um limite de tentativas.
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 conta falhas consecutivas, e o contador recomeça sempre que uma conexão é aberta, então um cliente que roda por semanas com um ou outro soluço de rede nunca fica sem tentativas. O servidor também tem voz ativa: um campo retry: substitui Interval pelo resto da sessão. A última palavra é sua, em OnReconnect, que dispara antes de cada tentativa com o atraso calculado:
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 e autenticação
O stream é lido com um TsgcHTTP1Client, e OnBeforeConnect o entrega a você antes de cada tentativa de conexão. Tudo que o cliente HTTP pode fazer, o cliente SSE também pode: opções de TLS, um proxy, autenticação básica ou um timeout de leitura mais longo.
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 e OnReconnect sempre rodam na thread em segundo plano, então mantenha a interface fora deles.
O parser para streams de LLM e MCP
Muitas vezes você nem precisa de um EventSource de vida longa. Uma chat completion de LLM com stream: true é um POST cuja resposta é SSE, e uma resposta MCP Streamable HTTP também pode ser SSE. Para esses casos, o parser dentro do cliente é público: TsgcSSEParser. Alimente-o com bytes brutos ou texto em blocos de qualquer tamanho, exatamente como a rede os entrega, e ele dispara OnEvent para cada evento completo e OnRetry para cada campo retry: válido. Um bloco pode terminar no meio de uma linha ou de um caractere UTF-8, e o parser guarda o restante até a próxima chamada.
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;
Esses três blocos produzem dois eventos: content_block_delta com o JSON completo {"delta":{"text":"Hello"}}, e message_stop. Reset descarta qualquer evento lido pela metade antes da próxima resposta, e LastEventId informa o último id que o parser viu.
C# para .NET
A edição .NET do sgcWebSockets tem o mesmo componente com os mesmos nomes, no namespace esegece.sgcWebSockets. Headers é uma lista de strings Nome: Valor e os eventos são eventos .NET comuns.
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 também está lá, com sobrecargas de Feed para um byte[], um trecho dele, ou uma string.
Experimente as demos
As duas edições vêm com uma demo que roda totalmente offline. Ela hospeda um pequeno servidor SSE dentro da mesma aplicação em 127.0.0.1, porta 5580, que envia eventos numerados em stream com um id:, alterna os tipos message, tick e alert, envia um campo retry: e retoma depois do Last-Event-ID que recebe. Derrube a conexão e veja o cliente reconectar e continuar a partir do próximo id, sem lacuna e sem duplicata.
- Delphi:
Demos\20.HTTP_Protocol\17.SSE_Client_Component - .NET:
demos\20.HTTP_Protocol\17.SSE_Client_Component, que também tem uma opção/selftestque verifica sozinha a ordenação, duplicatas e o cabeçalho de retomada
Disponibilidade
TsgcSSEClient e TsgcSSEParser chegam no sgcWebSockets 2026.10, para Delphi, C++Builder e .NET, em todas as edições. Eles não estão na versão que você pode baixar hoje. Quando o 2026.10 sair, ele estará na página de download, e o componente será registrado na aba SGC HTTP da paleta.
A referência completa, com cada propriedade, evento e o guia de retomada, está na ajuda do TsgcSSEClient. Para tudo mais que o sgcWebSockets faz com Server-Sent Events, veja a página do produto SSE.
Leia também
Dúvidas, feedback ou ajuda com a migração? Entre em contato. Você receberá uma resposta das pessoas que escreveram o código.
