# Server-Sent Events (SSE) : du temps réel en HTTP, et pourquoi les API d’IA les adorent

> Découvrez les Server-Sent Events, pourquoi les API d’IA les utilisent pour le streaming et comment les consommer simplement en .NET avec Tiny.RestClient.

- Auteur: Jérôme Giacomini
- Langue: fr
- URL canonique: [Server-Sent Events (SSE) : du temps réel en HTTP, et pourquoi les API d’IA les adorent](https://jeromegiacomini.net/fr/articles/2026/09/18/server-sent-events-sse-dotnet-tiny-restclient)
- Publié le: 2026-09-18
- Dernière modification: 2026-09-18
- Thèmes: SSE, Server-Sent Events, .NET, C#, ASP.NET, ASP.NET Core

![Des événements SSE contenant du JSON composent la réponse « Hello world » dans un terminal illustré du logo ChatGPT](/media/2026/server-sent-events-sse-dotnet-tiny-restclient/images/sse-json-ai-hero.png)

Lorsque ChatGPT ou un autre assistant affiche sa réponse mot après mot, on pourrait croire qu’un WebSocket sophistiqué se cache nécessairement derrière l’interface.

Souvent, la réalité est beaucoup plus simple : une requête HTTP dont la réponse reste ouverte et livre ses événements au fur et à mesure. Ce mécanisme porte un nom : **Server-Sent Events**, ou **SSE**.

SSE n’est ni nouveau, ni réservé à l’intelligence artificielle. Sa spécification a été publiée comme [recommandation du W3C le 3 février 2015](https://www.w3.org/TR/2015/REC-eventsource-20150203/). On l’utilise depuis longtemps pour afficher des notifications, des journaux, la progression d’un traitement ou les données d’un tableau de bord en temps réel. Mais l’arrivée massive des API d’IA lui a offert une seconde jeunesse particulièrement méritée.

## Qu’est-ce que Server-Sent Events ?

Avec une requête HTTP classique, le client appelle un serveur, attend la réponse complète, la lit, puis la connexion peut être réutilisée ou fermée.

Avec SSE, le début est identique : **le client ouvre une requête HTTP**. Le serveur répond avec le type de contenu `text/event-stream`, mais il ne termine pas immédiatement la réponse. Il conserve le flux ouvert et y écrit de nouveaux événements dès qu’ils sont disponibles.

![Échange SSE entre un client .NET et une API : une requête HTTP reçoit plusieurs événements traités dès leur réception](/media/2026/server-sent-events-sse-dotnet-tiny-restclient/images/sse-sequence-fr.svg)

*Une seule requête HTTP, plusieurs événements : le client traite chaque fragment dès sa réception. Les noms `token` et `completed` sont propres à cet exemple ; ils ne sont pas imposés par SSE.*

Une réponse peut ressembler à ceci :

```text
event: token
id: 42
data: {"text":"Bonjour"}

event: token
id: 43
data: {"text":" tout le monde"}

event: completed
data: {"finishReason":"stop"}

```

La ligne vide est importante : elle marque la fin d’un événement. Chaque événement peut contenir plusieurs champs définis par le protocole :

- `data` contient les données, souvent du texte ou du JSON ;
- `event` donne un type à l’événement, par exemple `token`, `progress` ou `completed` ;
- `id` identifie l’événement et peut aider à reprendre un flux interrompu ;
- `retry` suggère au client un délai avant une reconnexion ;
- une ligne commençant par `:` est un commentaire, souvent utilisé comme heartbeat pour empêcher les intermédiaires de considérer la connexion comme inactive.

Plusieurs lignes `data` consécutives appartiennent au même événement et sont réunies avec un saut de ligne.

Le protocole est **unidirectionnel** : une fois la requête envoyée, les événements circulent du serveur vers le client. Cela ne signifie pas que SSE est limité aux requêtes `GET`. Une API d’IA peut parfaitement recevoir un prompt dans une requête `POST`, puis diffuser sa réponse dans le corps HTTP de cette même requête. En revanche, l’API JavaScript native `EventSource` des navigateurs est, elle, principalement conçue autour de `GET`.

## SSE, polling ou WebSocket ?

Ces trois solutions répondent à des besoins proches, mais pas identiques.

Avec le **polling**, le client demande régulièrement au serveur s’il existe de nouvelles données. C’est facile à comprendre, mais on multiplie les requêtes inutiles et on ajoute une latence pouvant aller jusqu’au prochain passage.

Un **WebSocket** ouvre un canal bidirectionnel : le client et le serveur peuvent s’envoyer des messages à tout moment. C’est idéal pour un jeu multijoueur, un éditeur collaboratif ou une conversation réellement interactive dans les deux sens.

SSE occupe un espace intermédiaire très intéressant :

- une connexion HTTP standard ;
- des événements reçus dès leur émission ;
- un format texte simple à inspecter ;
- aucune couche bidirectionnelle à gérer lorsque le serveur est le seul à devoir pousser des données.

Autrement dit, si le client envoie une commande puis écoute une suite de résultats, SSE est souvent exactement le bon outil. Il ne sert à rien de construire une autoroute à huit voies lorsque tout le trafic va dans la même direction.

## Pourquoi les fournisseurs d’IA utilisent autant SSE

La génération d’une réponse par un modèle peut prendre plusieurs secondes, parfois beaucoup plus. Attendre que tout le texte soit produit avant de l’afficher donne l’impression que l’application est bloquée.

En activant le streaming, l’API envoie des fragments dès qu’ils sont disponibles. L’utilisateur voit le début de la réponse rapidement, même si le temps total de génération ne change pas. On améliore donc surtout le **temps jusqu’au premier résultat visible**, et par conséquent la perception de réactivité.

SSE permet également d’envoyer autre chose que du texte :

- la création de la réponse ;
- les fragments de texte successifs ;
- les arguments d’un appel d’outil en cours de construction ;
- un changement d’état ;
- les informations d’usage ;
- la fin de la génération ou une erreur.

C’est précisément le modèle adopté par de nombreux fournisseurs. L’[API Responses d’OpenAI](https://platform.openai.com/docs/api-reference/responses-streaming) émet des Server-Sent Events lorsque `stream` vaut `true`. L’[API Chat de Mistral](https://docs.mistral.ai/api/endpoint/chat) peut envoyer les tokens sous forme d’événements, jusqu’au message final `[DONE]`. De son côté, l’[API Gemini](https://ai.google.dev/api) propose `streamGenerateContent` pour retourner les morceaux de la réponse au fil de leur génération.

Le choix est logique : le client envoie un prompt une fois, puis le serveur produit une séquence ordonnée d’événements. Une connexion HTTP unidirectionnelle suffit dans la majorité des cas.

## Produire des SSE avec ASP.NET Core

Depuis [.NET 10](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0?view=aspnetcore-10.0#support-for-server-sent-events-sse), ASP.NET Core sait retourner nativement un flux SSE avec `TypedResults.ServerSentEvents`. Une Minimal API complète peut tenir en quelques lignes :

```csharp
using System.Runtime.CompilerServices;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/events", (CancellationToken cancellationToken) =>
{
    return TypedResults.ServerSentEvents(
        GetEventsAsync(cancellationToken),
        eventType: "message");
});

app.Run();

static async IAsyncEnumerable<string> GetEventsAsync(
    [EnumeratorCancellation] CancellationToken cancellationToken)
{
    for (var index = 1; index <= 10; index++)
    {
        yield return $"Événement numéro {index}";
        await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
    }
}
```

ASP.NET Core se charge du format `text/event-stream` et écrit chaque valeur dans la réponse sans attendre la fin de l’énumération. Le `CancellationToken` est essentiel : si le client ferme la connexion, le serveur doit arrêter le travail devenu inutile.

Il est également possible de retourner des `SseItem<T>` pour définir le type, l’identifiant et les données structurées de chaque événement. Les objets autres que les chaînes sont alors sérialisés en JSON.

## Consommer un flux SSE en .NET

.NET 10 fournit aussi [`SseParser`](https://learn.microsoft.com/en-us/dotnet/api/system.net.serversentevents.sseparser?view=net-10.0) dans l’espace de noms `System.Net.ServerSentEvents`. Pour consommer correctement le flux avec `HttpClient`, il faut demander à recevoir la réponse dès que ses en-têtes sont disponibles, récupérer son `Stream`, créer le parser, puis l’énumérer :

```csharp
using System.Net.Http.Headers;
using System.Net.ServerSentEvents;

using var httpClient = new HttpClient();
using var cancellationSource =
    new CancellationTokenSource(TimeSpan.FromMinutes(1));

var cancellationToken = cancellationSource.Token;

using var request = new HttpRequestMessage(
    HttpMethod.Get,
    "https://localhost:5001/events");

request.Headers.Accept.Add(
    new MediaTypeWithQualityHeaderValue("text/event-stream"));

using var response = await httpClient.SendAsync(
    request,
    HttpCompletionOption.ResponseHeadersRead,
    cancellationToken);

response.EnsureSuccessStatusCode();

await using var stream =
    await response.Content.ReadAsStreamAsync(cancellationToken);

var parser = SseParser.Create(stream);

await foreach (var item in parser.EnumerateAsync(cancellationToken))
{
    Console.WriteLine($"[{item.EventType}] {item.Data}");
}
```

Le détail à ne surtout pas manquer est `HttpCompletionOption.ResponseHeadersRead`. Sans lui, `HttpClient` considère normalement l’opération comme terminée après avoir lu tout le contenu. Or, un flux SSE peut rester ouvert pendant des minutes ou des heures : attendre sa fin avant de commencer à le traiter annule tout l’intérêt du streaming.

Ce code fonctionne et utilise les API natives. Mais il faut tout de même créer la requête, positionner l’en-tête `Accept`, choisir le bon mode de lecture, vérifier la réponse, ouvrir le flux et brancher le parser. Et ce n’est que le chemin heureux : dans une application réelle, il faudra aussi décider comment gérer l’annulation, les erreurs réseau et les éventuelles reconnexions.

## Avec Tiny.RestClient, c’est beaucoup, beaucoup plus simple

J’avais présenté les bases de la bibliothèque dans [mon article consacré à Tiny.RestClient](/fr/articles/2018/09/15/tiny-restclient-le-client-rest-pour-consommer-vos-api-rest).

La version 2.0 de [Tiny.RestClient sur GitHub](https://github.com/jgiacomini/Tiny.RestClient) prend nativement en charge les Server-Sent Events sur .NET Standard 2.1, .NET 8 et .NET 10.

Le package est disponible sur [NuGet](https://www.nuget.org/packages/Tiny.RestClient/) :

```bash
dotnet add package Tiny.RestClient --version 2.0.0
```

La consommation du même endpoint devient :

```csharp
using Tiny.RestClient;

var client = new TinyRestClient(
    new HttpClient(),
    "https://localhost:5001");

await foreach (var sse in client
    .GetRequest("events")
    .ExecuteAsSSEAsync(cancellationToken))
{
    Console.WriteLine($"id    : {sse.Id}");
    Console.WriteLine($"event : {sse.Event}");
    Console.WriteLine($"data  : {sse.Data}");
    Console.WriteLine($"retry : {sse.Retry}");
}
```

C’est tout.

`ExecuteAsSSEAsync` ouvre la connexion en streaming et retourne chaque événement dès sa réception sous la forme d’un `IAsyncEnumerable<ServerSentEvent>`. La réponse n’est pas mise en mémoire en attendant sa fin. Les champs standards du protocole sont directement disponibles avec `Data`, `Event`, `Id` et `Retry`, tandis que les commentaires et les champs inconnus sont ignorés.

L’API fonctionne sur la requête Tiny.RestClient elle-même. Elle reste donc compatible avec ses verbes, ses en-têtes, son authentification et ses contenus. Pour une API d’IA générique utilisant un `POST`, le principe reste le même :

```csharp
var body = new
{
    model = "my-model",
    input = "Explique-moi les Server-Sent Events",
    stream = true,
};

await foreach (var sse in client
    .PostRequest("v1/generate", body)
    .WithOAuthBearer(apiKey)
    .ExecuteAsSSEAsync(cancellationToken))
{
    Console.WriteLine($"{sse.Event}: {sse.Data}");
}
```

Le contenu de `Data` dépend naturellement du fournisseur et doit généralement être désérialisé depuis du JSON. Tiny.RestClient s’occupe ici du transport SSE ; votre code reste responsable de la sémantique des événements propres à l’API appelée.

## Les points à surveiller en production

SSE est simple, mais une connexion longue durée mérite quelques précautions :

- transmettez toujours un `CancellationToken` et annulez-le lorsque l’utilisateur quitte l’écran ou abandonne la génération ;
- réutilisez `HttpClient`, idéalement via l’injection de dépendances, au lieu d’en créer un pour chaque appel ;
- vérifiez les timeouts des proxies, load balancers et passerelles placés devant l’API ;
- désactivez la mise en tampon de la réponse dans les intermédiaires qui la pratiquent ;
- envoyez des heartbeats si le flux peut rester silencieux longtemps ;
- prévoyez une stratégie de reconnexion lorsque le cas d’usage l’exige, en tenant compte de `retry` et du dernier `id` reçu ;
- considérez chaque `data` comme une entrée externe : validez le JSON et gérez les nouveaux types d’événements sans faire tomber tout le flux.

Une reconnexion automatique fait partie du comportement de l’API `EventSource` des navigateurs, mais pas de tous les clients SSE. Avec un client .NET, cette politique doit être explicite afin d’éviter aussi bien les abandons silencieux que les boucles de reconnexion agressives.

## Conclusion

SSE, c’est finalement une connexion HTTP qui n’a pas encore fini de vous raconter sa vie. Et avec [Tiny.RestClient](https://github.com/jgiacomini/Tiny.RestClient), une requête et un `await foreach` suffisent pour l’écouter.

De mon côté, j’utilise beaucoup SSE dans [iolys](https://getiolys.com/?utm_source=blog&utm_medium=referral&utm_campaign=blog&utm_content=tinyrest_article). Autant dire que cet article n’est pas né uniquement de ma passion pour les lignes vides du protocole.

[Au fait, je vous ai parlé de iolys ?](https://getiolys.com/?utm_source=blog&utm_medium=referral&utm_campaign=blog&utm_content=tinyrest_article)

Ce sera pour un prochain article :) Même en streaming, je ne vais pas tout vous envoyer d’un coup !

## Pour aller plus loin

- [Tiny.RestClient : le client REST pour consommer vos API](/fr/articles/2018/09/15/tiny-restclient-le-client-rest-pour-consommer-vos-api-rest) : mon article de présentation de la bibliothèque, publié en 2018.
- [Tiny.RestClient sur GitHub](https://github.com/jgiacomini/Tiny.RestClient) : le code source et la documentation de la bibliothèque.
- [La spécification SSE du WHATWG](https://html.spec.whatwg.org/multipage/server-sent-events.html) : la référence actuelle pour le format des événements, `EventSource` et les règles de reconnexion.
- [Le support SSE dans ASP.NET Core 10](https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-10.0?view=aspnetcore-10.0#support-for-server-sent-events-sse) : la documentation Microsoft pour produire un flux avec `TypedResults.ServerSentEvents`.

Happy coding 🙂
