Como implementar SSE com .NET

Autor: Gabriel Manara
Como implementar SSE com .NET

Server-Sent Events é uma tecnologia que funciona em cima do padrão HTTP que permite uma aplicação enviar dados de maneira continua para a aplicação cliente. A ideia desta tecnologia e atender demandas de aplicações que precisam estar atualizando constantemente seus usuários com informações em tempo real.

Em uma requisição HTTP normal, a aplicação cliente executa a requisição, recebe uma resposta da aplicação servidor e encerra a chamada. Com Server-Sent Events, o servidor pode continuar conectado com a aplicação cliente enviando informações, trabalhando de maneira UNIDIRECIONAL, ou seja, o servidor continua enviando as informações, porém o contrário não acontece. Este metódo pode ser aplicado em diversas demandas que precisam de atualização de informações em tempo real, como sistemas de notificação, estatísticas, etc.

Neste artigo, irei dar um breve exemplo de uma implementação da tecnologia utilizando .NET e uma pagina HTML com Javascript Vanilla.

Para exemplificar, iremos criar uma pagina simples que se conecta na api e recebe os dados de uma partida de futebol, e que fica a cada 5 segundos atualizando o placar(resultados gerados aleatoriamente)

Criação do endpoint

Código:

csharp
app.MapGet("/matches/sse", async (HttpContext context) =>
{
    context.Response.Headers.Append("Content-Type", "text/event-stream");
    context.Response.Headers.Append("Cache-Control", "no-cache");
    context.Response.Headers.Append("Connection", "keep-alive");

    var teams = new[]
    {
        new { Name = "Real Madrid", Logo = "https://placehold.co/100x100?text=RMA" },
        new { Name = "Manchester City", Logo = "https://placehold.co/100x100?text=MCY" }
    };
    var random = new Random();
    var homeTeam = teams[random.Next(teams.Length)];
    var awayTeam = teams[random.Next(teams.Length)];

    while (awayTeam == homeTeam)
    {
        awayTeam = teams[random.Next(teams.Length)];
    }

    int homeScore = 0;
    int awayScore = 0;

    while (!context.RequestAborted.IsCancellationRequested)
    {
        if (random.Next(10) > 7) homeScore++;
        if (random.Next(10) > 7) awayScore++;

        var update = new
        {
            HomeTeam = homeTeam.Name,
            HomeLogo = homeTeam.Logo,
            HomeScore = homeScore,
            AwayTeam = awayTeam.Name,
            AwayLogo = awayTeam.Logo,
            AwayScore = awayScore,
            Timestamp = DateTime.Now.ToString("HH:mm:ss")
        };

        var json = JsonSerializer.Serialize(update);
        await context.Response.WriteAsync($"data: {json}\n\n");
        await context.Response.Body.FlushAsync();

        await Task.Delay(5000);
    }
});

Explicação: Nas três primeiras linhas, nós configuramos nos headers da requisição o que é necessário para a API funcionar utilizando SSE, sendo:

"Content-Type: text/event-stream": Define que deverá seguir o padrão SSE.

"Cache-Control: no-cache": Define que a aplicação cliente não deverá guardar cache.

"Connection": Define que a conexão deverá continuar ativa.

E nós temos em seguida uma logica para gerar aleatoriamente o resultado da partida,

O resultado é gerado e devolvido dentro do loop while, enquanto a condição context.RequestAborted.IsCancellationRequested não for atendida, poís este atributo indica que o usuário se desconectou e a aplicação deve parar de enviar respostas

As chamadas dos metodos context.Response.WriteAsync e context.Response.Body.FlushAsync enviam os dados para a aplicação cliente

Implementação no Javascript

html
<script>
    const eventSource = new EventSource('/matches/sse');

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

        document.getElementById('home-name').textContent = data.HomeTeam;
        document.getElementById('home-logo').src = data.HomeLogo;
        document.getElementById('home-score').textContent = data.HomeScore;

        document.getElementById('away-name').textContent = data.AwayTeam;
        document.getElementById('away-logo').src = data.AwayLogo;
        document.getElementById('away-score').textContent = data.AwayScore;

        document.getElementById('timestamp').textContent = data.Timestamp;
    };

    eventSource.onerror = function(err) {
        console.error("EventSource failed:", err);
        // eventSource.close();
    };
</script>

Para utilizar SSE no front, iremos utilizar a classe EventSource

É passado como paramêtro no construtor da classe a URL do endpoint do backend,

Em seguida, nós atribuímos a propriedade onmessage uma função para interceptar cada resposta da api e exibir na tela

E por último, atribuímos a propriedade onerror uma função para tratar erros

Quando executamos e subimos a aplicação, temos o seguinte resultado:

O Frontend faz a requisição para a API:

imagee

E a cada 5 segundos, é devolvido o placar da partida atualizado: imagee

CONSIDERAÇÕES FINAIS

SSE é uma excelente opção para casos em que uma aplicação precisa de atualização constante em tempo real, ao invés de fazer algo como uma requisição HTTP a cada x segundos, que é algo ante performático, ou até mesmo implementar Websockets, que tende a ser algo muito mais trabalhoso, SSE funciona já em cima do protocolo HTTP o que facilita o desenvolvimento.