Sistemas Distribuídos - Lab Prático Server-Sent Events

7 minute read

Published:

1. Objetivo do Laboratório

Neste laboratório, você irá desenvolver uma aplicação web simples capaz de receber atualizações em tempo real enviadas pelo servidor.

Ao final da atividade, você deverá ser capaz de:

  • Criar um servidor SSE usando Python e FastAPI.
  • Enviar eventos contínuos do servidor para o navegador.
  • Consumir eventos SSE no cliente usando HTML e JavaScript.
  • Exibir dados recebidos em tempo real em uma página web.
  • Compreender quando SSE pode ser utilizado em aplicações web reais.

2. Pré-requisitos

Antes de iniciar, você deve ter instalado:

  • Python 3.10 ou superior.
  • Editor de código, como VS Code.
  • Navegador web atualizado.
  • Conhecimento básico de HTML, JavaScript e rotas HTTP.

Também é recomendável criar um ambiente virtual:

python -m venv sse
source sse/bin/activate

3. Conceitos Envolvidos

Server-Sent Events

Server-Sent Events (SSE) permite ao servidor enviar dados continuamente para o cliente por meio de uma conexão HTTP persistente.

Diferentemente do modelo tradicional da Web, em que o cliente precisa solicitar novas informações repetidamente, no SSE o servidor pode enviar atualizações sempre que houver novos dados disponíveis.

Comunicação Unidirecional

No SSE, a comunicação ocorre principalmente em uma direção:

Servidor → Cliente

4. Estrutura do Projeto

Crie uma pasta chamada:

laboratorio-sse-fastapi

Dentro dela, crie os seguintes arquivos:

laboratorio-sse-fastapi/
│
├── main.py
└── index.html

5. Instalação das Dependências

Instale o FastAPI e o Uvicorn:

pip install fastapi uvicorn

FastAPI é um framework web moderno, escrito em Python, projetado especificamente para construir APIs (Application Programming Interfaces) de maneira rápida e robusta. Ele não é apenas uma biblioteca; ele é um conjunto completo de ferramentas que simplificam drasticamente o desenvolvimento backend. O FastAPI cuida da estrutura e da segurança dos seus endpoints. Ele permite que você se concentre na lógica de negócio, sabendo que o framework cuidará da validação e do formato das respostas.

Uvicorn é um servidor web (Web Server Gateway Interface - WSGI/ASGI). Ele não é uma biblioteca de código; ele é a máquina que executa o seu código Python. Quando você escreve uma aplicação Web em Python, ele precisa ser executado por algum motor. O FastAPI define as regras, mas alguém precisa rodar essas regras no tempo real. É aí que entra o servidor.

ASGI (Asynchronous Server Gateway Interface): Este é o conceito mais importante. Uvicorn implementa o padrão ASGI. Enquanto servidores antigos (como os baseados em WSGI) eram síncronos (eles processavam uma requisição por vez, esperando ela terminar), o ASGI permite que o servidor seja assíncrono.

6. Passo a Passo

Passo 1 — Criar o Servidor FastAPI

No arquivo main.py, adicione o seguinte código:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
import time
import json
from datetime import datetime

app = FastAPI()

# Middleware de CORS (obrigatório para testes locais com HTML)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Em produção, restrinja a origem específica
    allow_credentials=True,
    allow_methods=["GET"],
    allow_headers=["Content-Type"],
)


def gerar_eventos():
    contador = 0

    while True:
        contador += 1

        dados = {
            "contador": contador,
            "mensagem": "Evento recebido do servidor",
            "horario": datetime.now().strftime("%H:%M:%S")
        }

        # Formato SSE obrigatório: "data: <conteúdo>\n\n"
        evento = f"data: {json.dumps(dados)}\n\n"

        yield evento

        time.sleep(2)


@app.get("/eventos")
def eventos():
    return StreamingResponse(
        gerar_eventos(),
        media_type="text/event-stream"
    )

Explicação do Código

  • FastAPI() cria a aplicação web.
  • StreamingResponse permite enviar dados em fluxo contínuo.
  • A função gerar_eventos() gera eventos repetidamente.
  • O comando yield envia cada evento para o cliente sem encerrar a conexão.
  • O tipo text/event-stream informa ao navegador que a resposta será um fluxo SSE.

Passo 2 — Executar o Servidor

No terminal, dentro da pasta do projeto, execute:

uvicorn main:app --reload

Se tudo estiver correto, o servidor será iniciado em:

http://127.0.0.1:8000

Teste a rota SSE acessando:

http://127.0.0.1:8000/eventos

Você deverá observar eventos sendo enviados continuamente.

Importante: Houve reports de problemas em iniciar o servidor uvicorn com comando acima. No caso, o erro produzido foi ERROR: Error loading ASGI app. Could not import module "main". Se isso ocorrer, você pode adicionar o código de inicalização do uvicorn diretamete no final arquivo app.py:

if __name__ == "__main__":
    uvicorn.run("__main__:app", host="0.0.0.0", port=8000, reload=True, workers=2)

Passo 3 — Criar o Cliente HTML

No arquivo index.html, adicione o seguinte código:

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>Laboratório SSE com FastAPI</title>

    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 40px;
            background-color: #f5f5f5;
        }

        h1 {
            color: #333;
        }

        #status {
            font-weight: bold;
            margin-bottom: 20px;
        }

        .evento {
            background-color: white;
            border-left: 5px solid #2f80ed;
            padding: 12px;
            margin-bottom: 10px;
            border-radius: 4px;
        }
    </style>
</head>

<body>
    <h1>Cliente SSE com HTML e JavaScript</h1>

    <p id="status">Conectando ao servidor...</p>

    <h2>Eventos recebidos</h2>

    <div id="eventos"></div>

    <script>
        const status = document.getElementById("status");
        const eventos = document.getElementById("eventos");

        const fonteEventos = new EventSource("http://127.0.0.1:8000/eventos");

        fonteEventos.onopen = function() {
            status.textContent = "Conectado ao servidor SSE.";
        };

        fonteEventos.onmessage = function(event) {
            const dados = JSON.parse(event.data);

            const novoEvento = document.createElement("div");
            novoEvento.className = "evento";

            novoEvento.innerHTML = `
                <strong>Evento ${dados.contador}</strong><br>
                Mensagem: ${dados.mensagem}<br>
                Horário: ${dados.horario}
            `;

            eventos.prepend(novoEvento);
        };

        fonteEventos.onerror = function() {
            status.textContent = "Erro na conexão com o servidor SSE.";
        };
    </script>
</body>
</html>

Passo 4 — Abrir o Cliente no Navegador

Abra o arquivo index.html diretamente no navegador.

Você deverá visualizar:

  • Uma mensagem de conexão.
  • Novos eventos sendo adicionados automaticamente à página.
  • Um novo evento a cada 2 segundos.

7. Teste de Funcionamento

Verifique se:

  • O servidor está rodando com uvicorn.
  • O navegador consegue acessar a rota /eventos.
  • A página HTML recebe novos eventos sem precisar ser recarregada.
  • Os dados aparecem em ordem decrescente, com o evento mais recente no topo.

8. Exercícios

Exercício 1: Alterar o Intervalo dos Eventos

Altere o servidor para enviar eventos a cada 1 segundo em vez de 2 segundos.

Dica:

time.sleep(1)

Exercício 2: Enviar Temperatura Simulada

Modifique o servidor para enviar uma temperatura aleatória entre 20 e 40 graus.

Dica:

import random

temperatura = random.randint(20, 40)

O JSON enviado pelo servidor deve ter o seguinte formato:

{
    "contador": 1,
    "temperatura": 28,
    "horario": "14:30:10"
}

Exercício 3: Criar Alerta de Temperatura

No cliente HTML, exiba uma mensagem de alerta quando a temperatura for maior que 35 graus.

Exemplo de saída esperada:

Alerta: temperatura elevada!

Exercício Extra

Você deverá simular um sistema de monitoramento enviando múltiplas variáveis:

  • Temperatura
  • Umidade
  • Pressão

Exemplo de estrutura de dados:

{
  "temperatura": 28,
  "umidade": 60,
  "pressao": 1012,
  "horario": "14:35:10"
}

O servidor deve enviar esses dados a cada 2 segundos.

No cliente:

  • Exibir cada variável em um painel separado.
  • Atualizar os valores dinamicamente.
  • Exibir o horário da última atualização.
  • Horário da última atualização.
  • Quantidade total de eventos recebidos.
  • Alerta visual quando a temperatura estiver acima de 35 graus.

Resultado Esperado

Ao final do laboratório, a aplicação deve apresentar:

  • Um servidor FastAPI com uma rota SSE.
  • Um cliente HTML conectado usando EventSource.
  • Atualizações automáticas na página.
  • Dados recebidos em tempo real.
  • Pelo menos uma melhoria implementada nos exercícios.

Questões para Reflexão

  1. Por que SSE é mais eficiente que polling em determinados cenários?
  2. Em quais situações SSE seria mais adequado que WebSockets?
  3. Qual é a função do cabeçalho text/event-stream?
  4. Por que o servidor precisa manter a conexão aberta?
  5. Quais limitações devem ser consideradas ao usar SSE?