# Server-sent events

## Vad det är

Server-sent events är ett sätt för en webbserver att skicka meddelanden till en
sida genom ett vanligt HTTP-svar, som servern håller öppet och skriver i varje
gång det finns något nytt. De definieras i HTML-standarden, som WHATWG
underhåller, och den beskriver både textformatet för strömmen och `EventSource`,
gränssnittet i webbläsaren som läser strömmen. Namnet förkortas ofta SSE, och
formatet kallas ofta efter sin medietyp, `text/event-stream`.

En sida öppnar en ström med en enda rad, `new EventSource(url)`. Webbläsaren
skickar en `GET`-begäran, som kan ha med sig `Accept: text/event-stream`, och
väntar sig ett svar med status 200 och den innehållstypen. Sedan skickar servern
sina headrar men gör aldrig klart svaret. Varje gång något händer skriver den
några rader text, och webbläsaren gör om varje block av rader till en händelse
som sidan kan lyssna efter.

Formatet är medvetet enkelt. En ström är alltid UTF-8, och varje rad är ett
fält. En `data`-rad bär innehållet, och flera `data`-rader i följd sätts ihop
med radbrytningar. En `event`-rad ger händelsen en typ, och utan den är typen
`message`. En `id`-rad sätter id:t för den senaste händelsen, och en `retry`-rad
anger hur många millisekunder webbläsaren väntar innan den ansluter igen. En rad
som börjar med kolon är en kommentar, som webbläsaren ignorerar. En tom rad
avslutar blocket och skickar i väg händelsen, så ett block som avbryts före sin
tomma rad kastas.

Återanslutning är en del av standarden. När anslutningen bryts väntar
webbläsaren under återanslutningstiden, som börjar på ett värde som webbläsaren
själv väljer, enligt standarden troligen några sekunder, och skickar sedan
begäran på nytt. Har servern gett sina händelser id:n har den nya begäran med
sig det senaste av dem i headern `Last-Event-ID`, och servern kan fortsätta
därifrån. Vissa svar avslutar i stället strömmen för gott: en annan status än
200 eller en annan innehållstyp får webbläsaren att ge upp och sluta försöka,
och standarden pekar ut 204 No Content som sättet för en server att säga att
strömmen är slut.

Riktningen går bara åt ett håll. Efter den första begäran går ingenting från
sidan till servern över den anslutningen; när sidan har något att säga skickar
den en egen HTTP-begäran.

## Varför det spelar roll

Många funktioner som ser levande ut behöver bara en riktning: förloppet i ett
långt jobb, ett nytt meddelande, en siffra på en dashboard, ett svar som kommer
i delar. Utan en ström måste sidan fråga om och om igen, så kallad polling. Den
frågar med några sekunders mellanrum, de flesta svaren säger att ingenting har
ändrats, och nyheter väntar upp till ett helt intervall innan någon ser dem. Med
en ström skriver servern i samma ögonblick som något händer.

Det vanliga alternativet för att skicka data till en sida är WebSocket, som
byter över anslutningen från HTTP till ett eget protokoll. Server-sent events
håller sig till HTTP från början till slut, så de passerar samma proxyer och
lastbalanserare som vilken annan begäran som helst, de använder samma cookies
och samma regler för begäranden mellan olika ursprung, och de kan dela en
HTTP/2-anslutning med resten av sidan. Återanslutning och återupptagning från
den senaste händelsen följer med webbläsaren i stället för att vara kod som
varje team skriver själv. WebSocket är fortfarande rätt val när klienten har
lika mycket att säga som servern, som i en chatt eller en delad editor.

Formatet är inte heller bundet till webbläsaren. Vilken HTTP-klient som helst
kan läsa det, och specifikationer utanför webben använder det. I Model Context
Protocol, som AI-agenter anropar verktyg genom, låter transporten Streamable
HTTP en server svara på en begäran antingen med ett enda JSON-objekt eller med
ett `text/event-stream`-svar. På den strömmen kan servern skicka notifieringar
och egna begäranden före det slutliga svaret, och den kan sätta id:n på
händelserna, så att en klient som tappar anslutningen kan fortsätta med
`Last-Event-ID`. Streamable HTTP ersatte HTTP+SSE, transporten i den tidigare
protokollversionen 2024-11-05.

Begränsningarna är värda att känna till innan man väljer det. Strömmen bär bara
text, så binära data måste först kodas som text. Webbläsarens
`EventSource`-konstruktor tar en URL och en enda inställning, `withCredentials`,
och ingenting annat, så en sida kan inte lägga till en `Authorization`-header i
begäran.

## Så ser det ut i praktiken

Utbytet nedan följer ett långt exportjobb. Webbläsaren har tappat anslutningen
efter händelse 41 och ansluter igen, så begäran har med sig det id:t, och
servern fortsätter från händelse 42:

```http
GET /jobs/4471/events HTTP/1.1
Host: example.com
Accept: text/event-stream
Last-Event-ID: 41

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-store

retry: 5000

: still working

id: 42
event: progress
data: {"done": 120, "total": 400}

id: 43
event: progress
data: {"done": 400, "total": 400}

id: 44
event: finished
data: {"report": "/jobs/4471/report.pdf"}

```

`retry`-raden ber webbläsaren vänta fem sekunder före varje senare
återanslutning, och kommentarraden bär ingen händelse alls; den finns där för
att hålla anslutningen aktiv medan jobbet är tyst. Sidan lyssnar efter de två
händelsetyperna vid namn:

```js
const source = new EventSource("/jobs/4471/events");
source.addEventListener("progress", (event) => {
  const { done, total } = JSON.parse(event.data);
  showProgress(done / total);
});
source.addEventListener("finished", (event) => {
  showReport(JSON.parse(event.data).report);
  source.close();
});
source.addEventListener("error", () => {
  if (source.readyState === EventSource.CLOSED) showRetryButton();
});
```

Anropet till `close()` spelar roll. När servern avslutar ett svar på vanligt
sätt behandlar webbläsaren det som en bruten anslutning och ansluter igen, så
utan anropet skulle en ström för ett färdigt jobb öppnas igen efter fem
sekunder. `error`-hanteraren skiljer mellan de två sorternas fel: så länge
`readyState` är `CONNECTING` försöker webbläsaren fortfarande, och när den är
`CLOSED` har den gett upp.

Arbete som fortsätter efter att agenten har fått sitt svar är ett av ämnena på
sidan om [din programvara som verktyg för AI-agenter](/sv/ai-tools/) här på
webbplatsen. Den beskriver hur varje operation som en agent ska ha erbjuds som
ett verktyg, med en MCP-server som erbjuder samma operationer till de agenter
som talar det protokollet, och hur långsamt arbete flyttas till en kö så att
agenten får ett svar medan arbetet fortsätter. I MCP-specifikationen är det en
händelseström som den ovan som en server använder när den vill berätta mer för
en klient än det slutliga svaret innan den är klar.

## Var det går fel

**Händelser som kommer i klumpar, eller alla på en gång i slutet.** Något mellan
servern och webbläsaren håller tillbaka svaret. nginx, till exempel, buffrar som
standard svaren från servern bakom sig och skickar bara vidare ett svar
allteftersom det kommer när buffring är avstängd. HTML-standarden varnar också
för att HTTP-chunking som utförs av ett lager som inte känner till tidpunkterna
i strömmen kan göra den opålitlig. Stäng av buffring för strömmens location, i
nginx med `proxy_buffering off` eller med headern `X-Accel-Buffering: no` i
svaret, töm skrivbufferten efter varje händelse i serverkoden, och testa genom
hela kedjan, proxyn inräknad, i stället för mot servern ensam.

**Tysta strömmar som kapas av en timeout.** En proxy som inte ser en enda byte
på ett tag utgår från att anslutningen är död. I nginx är standardvärdet för
`proxy_read_timeout` 60 sekunder mellan två läsningar, och därefter stängs
anslutningen, och HTML-standarden varnar för att vissa äldre proxyer bryter
anslutningar efter en kort timeout. Den föreslår en kommentarrad ungefär var
15:e sekund, som kostar några få byte och som webbläsaren ignorerar.

**Händelser som försvinner vid en återanslutning.** Webbläsaren skickar bara
`Last-Event-ID` om servern har gett sina händelser id:n, och headern hjälper
bara om servern fortfarande kan hitta det som kom efter det id:t. Ge varje
händelse ett id som fungerar som en markör i strömmen, behåll ett fönster med de
senaste händelserna som kan spelas upp igen, och skicka hela det aktuella
tillståndet när ett id är för gammalt att fortsätta från. MCP-specifikationen
lägger till en regel som är värd att kopiera: id:n hör till en enda ström, och
en server får inte spela upp meddelanden på en ström när de var avsedda för en
annan.

**Återanslutning som aldrig slutar, eller slutar för gott.** En ström som
avslutas normalt kommer tillbaka efter återanslutningstiden, om och om igen, så
ett färdigt jobb fortsätter att öppna sin ström på nytt om inte sidan anropar
`close()` eller servern svarar på återanslutningen med 204. Det motsatta händer
under en driftsättning. En proxy som svarar 502 eller 503 medan servrarna
startar om ger varje öppen `EventSource` en annan status än 200, och varenda en
av dem slutar för gott. Lyssna efter `error`, och när `readyState` är `CLOSED`,
skapa en ny `EventSource` efter en slumpmässig fördröjning som växer för varje
fel.

**För många strömmar till en webbläsare.** Webbläsare begränsar hur många
anslutningar de håller öppna till en server, och en ström över HTTP/1.1 upptar
en av dem så länge den pågår. HTML-standarden varnar för att en webbplats där
varje öppen sida har sin egen `EventSource` kan slå i den gränsen. Leverera
strömmen över HTTP/2, där strömmar delar en anslutning och RFC 9113
rekommenderar att minst 100 tillåts samtidigt, eller dela en enda `EventSource`
mellan flikarna genom en shared worker.

**En token i adressen.** Eftersom `EventSource` inte kan skicka en
`Authorization`-header blir den snabba lösningen en token i URL:en, där omvända
proxyer och åtkomstloggar skriver ner den. Autentisera strömmen med sessionens
cookie, som sidan redan har, och lägg till `withCredentials` när strömmen kommer
från ett annat ursprung, eller med en kortlivad engångsbiljett som sidan hämtar
med en autentiserad begäran precis innan den öppnar strömmen.

## Läs mer

- [HTML Standard: Server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html)
  — formatet för strömmen, gränssnittet `EventSource`, återanslutning och
  standardens anmärkningar till författare om proxyer och gränser för antalet
  anslutningar.
- [Transports - Model Context Protocol](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports)
  — Streamable HTTP, där en server kan svara med en händelseström, och hur en
  avbruten ström återupptas.
- [RFC 9113: HTTP/2](https://www.rfc-editor.org/rfc/rfc9113) — strömmar som
  delar en anslutning, och inställningen som begränsar hur många som kan vara
  öppna samtidigt.
- [nginx: Module ngx_http_proxy_module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html)
  — `proxy_buffering`, headern `X-Accel-Buffering` och `proxy_read_timeout`.

<https://engineer.company/sv/glossary/server-sent-events/>
