Server-Sent Events em JavaScript
Server-Sent Events, ou abreviado SSE, representa uma tecnologia de interação entre cliente e servidor que permite ao servidor enviar mensagens ao cliente. Vale destacar que, diferente dos WebSockets, a comunicação através de Server-Sent Events é unidirecional: as mensagens são entregues em uma única direção, do servidor para o cliente (como o navegador web do usuário). Isso os torna uma ótima escolha quando não é necessário enviar dados do cliente para o servidor. Por exemplo, Server-Sent Events podem ser utilizados para tratar coisas como atualização de status em redes sociais, feeds de notícias ou envio de dados para armazenamento no lado do cliente.
Na página web, no código JavaScript, para interagir com o servidor, utiliza-se a interface EventSource
. O objeto EventSource
basicamente representa um servidor que gera eventos ou envia mensagens. Para criar um objeto EventSource
, aplica-se o construtor:
new EventSource(url, options);
Como primeiro parâmetro obrigatório no construtor EventSource
, passa-se o endereço URL do recurso no servidor:
const evtSource = new EventSource("/events");
Opcionalmente, também é possível passar um parâmetro adicional que configura o objeto EventSource
. Este parâmetro representa um objeto com uma propriedade chamada withCredentials
. Esta propriedade indica se os cabeçalhos CORS devem ser incluídos para interação entre domínios. Por padrão, ela é false
.
Eventos do EventSource
Para gerenciar o estado da conexão no EventSource
, existe uma série de eventos definidos:
open
: disparado ao estabelecer a conexão. Para configurar o manipulador do evento, pode-se usar a propriedadeonopen
.error
: disparado quando ocorre um erro ao estabelecer a conexão. Para configurar o manipulador do evento, pode-se usar a propriedadeonerror
.message
: disparado ao receber dados do servidor. Para configurar o manipulador do evento, pode-se usar a propriedadeonmessage
.
Os manipuladores desses eventos recebem como parâmetro um objeto padrão do tipo Event
. Exemplo de configuração de manipuladores de eventos:
const evtSource = new EventSource("/events");
// usando addEventListener
evtSource.addEventListener("open", () => {
console.log("conexão estabelecida");
});
evtSource.addEventListener("error", () => {
console.log("Erro");
});
// usando propriedades
evtSource.onopen = () => {
console.log("conexão estabelecida");
};
evtSource.onerror = () => {
console.log("Erro");
};
Recebendo Dados
Quando os dados chegam do servidor, o objeto EventSource
dispara o evento message
. Para configurar o manipulador deste evento, pode-se usar a propriedade onmessage
ou o método addEventListener()
.
No manipulador do evento message
, é passado um objeto do tipo MessageEvent
. Este objeto fornece várias propriedades que permitem extrair os dados da resposta do servidor:
data
: retorna os dados recebidos.origin
: armazena o endereço do remetente.lastEventId
: armazena o identificador único do último evento como uma string.source
: retorna o objetoMessageEventSource
, que pode ser um objetoWindowProxy
,MessagePort
ouServiceWorker
, representando o remetente dos dados recebidos.ports
: retorna um array de objetosMessagePort
, que armazenam as portas usadas para o envio.
Exemplo de recebimento de dados:
const evtSource = new EventSource("/events");
evtSource.onmessage = (event) => {
console.log(event.data); // exibe os dados recebidos no console
};
Fechando a Conexão
Para fechar a conexão, usa-se o método close()
:
evtSource.close();
Exemplo de Interação entre Cliente e Servidor com Server-Sent Events
Vamos considerar um pequeno exemplo de interação entre cliente e servidor utilizando Server-Sent Events. O cliente será representado por um código JavaScript em uma página web e o servidor será implementado com Node.js.
Primeiro, vamos definir o código do servidor. Para isso, criaremos um arquivo server.js
com o seguinte código:
const http = require("http");
const fs = require("fs");
// dados para envio ao cliente
const messages = ["Olá", "Como vai?", "O que está fazendo?", "Você está dormindo?", "Adeus "];
http
.createServer(function (request, response) {
if (request.url == "/events") {
// se a requisição for SSE
if (request.headers.accept && request.headers.accept === "text/event-stream") {
sendEvent(response);
} else {
response.writeHead(400);
response.end("Bad Request");
}
} else {
// em outros casos, envia a página index.html
fs.readFile("index.html", (_, data) => response.end(data));
}
})
.listen(3000, () => console.log("Servidor iniciado em http://localhost:3000"));
// função para enviar mensagem ao cliente
function sendEvent(response) {
// configurar cabeçalhos
response.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const id = new Date().toLocaleTimeString(); // definir o identificador do último evento
// a cada 5 segundos enviar uma mensagem
setInterval(() => {
createServerSendEvent(response, id);
}, 5000);
}
// função para criar e enviar eventos ao cliente
function createServerSendEvent(response, id) {
// gerar número aleatório - índice para o array messages
const index = Math.floor(Math.random() * messages.length);
const message = messages[index];
response.write("id: " + id + "\n");
response.write("data: " + message + "\n\n");
}
Explicação do Código do Servidor
Primeiro, importamos os módulos necessários:
const http = require("http"); // para lidar com requisições HTTP
const fs = require("fs"); // para leitura de arquivos do disco
Em seguida, definimos um conjunto de mensagens que serão enviadas ao cliente:
const messages = ["Olá", "Como vai?", "O que está fazendo?", "Você está dormindo?", "Adeus "];
Para criar o servidor, usamos a função http.createServer()
, que recebe uma função de callback chamada sempre que uma requisição é feita ao servidor. Esta função tem dois parâmetros: request
(contendo os dados da requisição) e response
(utilizado para enviar a resposta).
Na função de callback, verificamos a URL da requisição usando request.url
. Se a URL for "/events", tratamos como uma requisição SSE:
if (request.url == "/events") {
// se a requisição for SSE
if (request.headers.accept && request.headers.accept === "text/event-stream") {
sendEvent(response);
} else {
response.writeHead(400);
response.end("Bad Request");
}
}
Para que a requisição seja tratada como SSE, o cabeçalho Accept
deve ter o valor text/event-stream
. Se esse cabeçalho estiver presente, chamamos a função sendEvent()
, passando o objeto response
. Caso contrário, enviamos um erro 400 (Bad Request).
Na função sendEvent()
, configuramos os cabeçalhos da resposta e iniciamos um intervalo que envia dados ao cliente a cada 5 segundos:
function sendEvent(response) {
response.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
const id = new Date().toLocaleTimeString(); // definir o identificador do último evento
setInterval(() => {
createServerSendEvent(response, id);
}, 5000);
}
A função createServerSendEvent()
gera um índice aleatório para selecionar uma mensagem do array messages
e envia essa mensagem ao cliente:
function createServerSendEvent(response, id) {
const index = Math.floor(Math.random() * messages.length);
const message = messages[index];
response.write("id: " + id + "\n");
response.write("data: " + message + "\n\n");
}
Se a URL da requisição não for "/events", enviamos o arquivo index.html
:
else {
fs.readFile("index.html", (_, data) => response.end(data));
}
Finalmente, iniciamos o servidor na porta 3000:
.listen(3000, () => console.log("Servidor iniciado em http://localhost:3000"));
Código do Cliente
Agora, vamos definir um arquivo simples index.html
na mesma pasta do servidor:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Programício</title>
</head>
<body>
<ul id="list"></ul>
<script>
const source = new EventSource("/events");
const list = document.getElementById("list");
source.addEventListener("message", (e) => {
const listItem = document.createElement("li");
listItem.textContent = e.data;
list.appendChild(listItem);
});
</script>
</body>
</html>
Neste arquivo, ao receber dados do servidor, adicionamos esses dados a uma lista na página web.
Executando o Servidor
Na linha de comando, navegue até a pasta do servidor e inicie-o com o comando:
node server.js
http://localhost:3000
Após iniciar o servidor, abra um navegador e acesse http://localhost:3000
. A página exibirá os dados recebidos do servidor em intervalos regulares de 5 segundos.