Eventos enviados pelo servidor (SSEs) enviam atualizações automáticas a um cliente usando um servidor com uma uma conexão com a Internet. Depois que a conexão é estabelecida, os servidores podem iniciar transmissão.
É possível usar SSEs para enviar notificações push do seu app da Web. Os SSEs enviam informações em uma única direção. Por isso, você não vai receber atualizações do para o cliente.
Você já conhece o conceito de SSEs. Um app da Web "se inscreve" a um fluxo geradas por um servidor e, sempre que ocorre um novo evento, uma notificação é enviada ao cliente. Mas, para realmente entender os eventos enviados pelo servidor, compreender as limitações de seus antecessores de AJAX. Isso inclui:
Pesquisa: o aplicativo pesquisa repetidamente um servidor em busca de dados. Essa técnica é usado pela maioria dos aplicativos AJAX. Com o protocolo HTTP, buscar em torno de um formato de solicitação e resposta. O cliente faz uma solicitação e espera a resposta do servidor com os dados. Se nenhum estiver disponível, um valor será retornada. A sondagem adicional cria maior sobrecarga de HTTP.
Pesquisa longa (Suspensão de GET / COMET): se o servidor não tiver dados disponível, o servidor mantém a solicitação aberta até que novos dados sejam disponibilizados. Por isso, essa técnica é frequentemente chamada de "Suspensão GET". Quando fica disponível, o servidor responde, encerra a conexão, e o processo se repete. Assim, o servidor está constantemente respondendo com novos dados. Para configurar isso, os desenvolvedores costumam usar truques como anexar as tags de script para uma instância "infinita" iframe.
Os eventos enviados pelo servidor foram projetados do zero para serem eficientes. Ao se comunicar com SSEs, um servidor pode enviar dados para seu o app sempre que quiser, sem precisar fazer uma solicitação inicial. Em outras palavras, as atualizações podem ser transmitidas de servidor para cliente à medida que acontecem. SSEs abrem um único canal unidirecional entre servidor e cliente.
A principal diferença entre os eventos enviados pelo servidor e a pesquisa longa é que os SSEs são tratados diretamente pelo navegador, e o usuário só precisa ouvir as mensagens.
Eventos enviados pelo servidor versus WebSockets
Por que você escolheria eventos enviados pelo servidor em vez de WebSockets? Boa pergunta.
O WebSockets tem um protocolo avançado com bidirecional e full-duplex. Um canal bidirecional é melhor para jogos, aplicativos de mensagens e qualquer caso de uso em que você precisa de atualizações quase em tempo real em ambas as direções.
No entanto, às vezes você só precisa da comunicação unidirecional de um servidor.
Por exemplo, quando um amigo atualiza o status, as ações da bolsa, os feeds de notícias ou
outros mecanismos automatizados de push de dados. Em outras palavras,
uma atualização em um banco de dados Web SQL ou um repositório de objetos IndexedDB do lado do cliente.
Se você precisar enviar dados a um servidor, XMLHttpRequest
será sempre um amigo.
Os SSEs são enviados por HTTP. Não há nenhum protocolo ou servidor especial para que a implementação funcione. Os WebSockets exigem full-duplex e novos servidores WebSocket para lidar com o protocolo.
Além disso, os eventos enviados pelo servidor têm uma variedade de recursos que os WebSockets não têm desde a concepção, incluindo reconexão automática, IDs de eventos e a capacidade de enviar eventos arbitrários.
Criar uma EventSource com JavaScript
Para se inscrever em um fluxo de eventos, crie um objeto EventSource
e transmita a ele o
URL da sua transmissão:
const source = new EventSource('stream.php');
Em seguida, configure um gerenciador para o evento message
. Também é possível
ouvir open
e error
:
source.addEventListener('message', (e) => {
console.log(e.data);
});
source.addEventListener('open', (e) => {
// Connection was opened.
});
source.addEventListener('error', (e) => {
if (e.readyState == EventSource.CLOSED) {
// Connection was closed.
}
});
Quando as atualizações são enviadas pelo servidor, o gerenciador onmessage
é acionado
e novos dados são disponibilizados na propriedade e.data
. A parte mágica é
que, sempre que a conexão for encerrada, o navegador se reconecta automaticamente ao
após cerca de 3 segundos. A implementação do seu servidor pode até mesmo ter controle sobre
tempo limite de reconexão.
É isso. Agora seu cliente pode processar eventos de stream.php
.
Formato de stream de eventos
O envio de um fluxo de eventos da origem é uma questão de construir um
resposta de texto simples, veiculada com um Content-Type text/event-stream
,
que segue o formato SSE.
Na forma básica, a resposta precisa conter uma linha data:
, seguida pelo
seguida de dois "\n" caracteres para encerrar a transmissão:
data: My message\n\n
Dados de várias linhas
Se a mensagem for mais longa, divida-a usando várias linhas data:
.
Duas ou mais linhas consecutivas que começam com data:
são tratadas como uma
uma única parte dos dados, o que significa que apenas um evento message
é disparado.
Cada linha deve terminar em um único "\n" (exceto a última, que deve terminar
com dois). O resultado transmitido ao gerenciador message
é uma única string
concatenados por caracteres de nova linha. Exemplo:
data: first line\n
data: second line\n\n</pre>
Isso produz "first line\nsecond line" em e.data
. É possível usar
e.data.split('\n').join('')
para recriar a mensagem sem "\n" caracteres.
Enviar dados JSON
O uso de várias linhas ajuda você a enviar JSON sem quebrar a sintaxe:
data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n
E possível código do lado do cliente para processar esse stream:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.id, data.msg);
});
Associar um código a um evento
É possível enviar um ID exclusivo com um evento de fluxo incluindo uma linha que começa com
id:
:
id: 12345\n
data: GOOG\n
data: 556\n\n
Definir um ID permite que o navegador acompanhe o último evento disparado para que, se o
conexão com o servidor for eliminada, um cabeçalho HTTP especial (Last-Event-ID
) será
com a nova solicitação. Isso permite que o navegador determine qual evento deve ser acionado.
O evento message
contém uma propriedade e.lastEventId
.
Controlar o tempo limite de reconexão
O navegador tenta se reconectar à origem por aproximadamente três segundos
após o encerramento de cada conexão. Para alterar esse tempo limite, inclua um
linha que começa com retry:
, seguida pelo número de milissegundos
esperar antes de tentar se reconectar.
O exemplo a seguir tenta a reconexão após 10 segundos:
retry: 10000\n
data: hello world\n\n
Especifique um nome de evento
Uma única fonte de eventos pode gerar diferentes tipos de eventos incluindo um
nome do evento. Se houver uma linha que começa com event:
,
seguido por um nome exclusivo, o evento é associado a esse nome.
No cliente, um listener de eventos pode ser configurado para detectar esse evento específico.
Por exemplo, a saída do servidor a seguir envia três tipos de eventos: uma "mensagem" genérica event, "userlogon" e "update" evento:
data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n
Com os listeners de eventos configurados no cliente:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.msg);
});
source.addEventListener('userlogon', (e) => {
const data = JSON.parse(e.data);
console.log(`User login: ${data.username}`);
});
source.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} is now ${data.emotion}`);
};
Exemplos de servidor
Veja a seguir uma implementação básica do servidor em PHP:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>
Veja uma implementação semelhante no Node JS usando uma Gerenciador Express:
app.get('/events', (req, res) => {
// Send the SSE header.
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Sends an event to the client where the data is the current date,
// then schedules the event to happen again after 5 seconds.
const sendEvent = () => {
const data = (new Date()).toLocaleTimeString();
res.write("data: " + data + '\n\n');
setTimeout(sendEvent, 5000);
};
// Send the initial event immediately.
sendEvent();
});
sse-node.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
const source = new EventSource('/events');
source.onmessage = (e) => {
const content = document.createElement('div');
content.textContent = e.data;
document.body.append(content);
};
</script>
</body>
</html>
Cancelar um stream de eventos
Normalmente, o navegador se reconecta automaticamente à origem do evento quando a conexão é fechado, mas esse comportamento pode ser cancelado pelo cliente ou pelo servidor.
Para cancelar um stream do cliente, chame:
source.close();
Para cancelar uma transmissão do servidor, responda com um que não seja text/event-stream
Content-Type
ou retornar um status HTTP diferente de 200 OK
(por exemplo, 404 Not Found
).
Os dois métodos impedem que o navegador restabeleça a conexão.
Sobre segurança
As solicitações geradas por EventSource estão sujeitas às políticas de mesma origem que outras APIs de rede, como Busca. Se você precisar que o endpoint SSE no servidor seja acessíveis de diferentes origens, leia como ativar Compartilhamento de recursos entre origens (CORS, na sigla em inglês):