skip to content

Wire Format

The text/event-stream grammar: the data, event, id and retry fields, multi-line payloads, comment lines, and the blank line that dispatches. Small enough that interviewers expect exact answers.

part ofServer-Sent Events (SSE)overview, primer and where to startread it →
on this pageshow

questions

5

In a Server-Sent Events feed of elevator car positions, what media type does the response carry, and what ends one event?

level: juniorimportance: must knowfreq 74%

answer

  1. one long response, not a new protocol
  2. the media type is the contract
  3. line-oriented: name, colon, value
  4. four field names, compared literally
  5. blank line dispatches, nothing else does

basics

~20 s

The response is served as text/event-stream. Its body is a stream of field lines - data, event, id and retry - and a single blank line ends the current block and dispatches it as one event.

solid answer

~40 s

The server answers an ordinary GET with a success status and `Content-Type: text/event-stream`, then writes a body it never ends. That body is line-oriented: everything before the first colon on a line is the field name, everything after it is the value, and one leading space in the value is dropped. Only four names mean anything - `data`, `event`, `id` and `retry` - and they are compared literally, so `Data:` binds to nothing. Lines accumulate into a block and a **blank line** is the only thing that dispatches it; a block that accumulated no `data` field dispatches nothing at all. A line beginning with a colon is a comment and fires nothing.

code

http · 11 lines
http
GET /bank-3/cars/events HTTP/1.1
Host: ops.example.internal
Accept: text/event-stream

HTTP/1.1 200 OK
Content-Type: text/event-stream

event: position
data: {"car":"A","floor":14,"dir":"up"}

data: {"car":"B","floor":3,"dir":"idle"}

go deeper

for a junior

Be able to state the media type exactly - text/event-stream - name the four fields, and say that a blank line is what ends an event. Those three facts are the whole screening question.

for a middle

Explain the parse: everything before the first colon is the field name, one leading space of the value is dropped, and unrecognised names are ignored without error. Say why a misspelt field fails silently.

for a senior

Show that bytes on the wire are not delivered events, and that a block with no data field dispatches nothing. That distinction is what turns into a production incident when an emitter stops mid-block.

for a principal

Frame the trade being made: one media type and a six-line grammar buy you push over ordinary HTTP infrastructure, at the cost of one direction only. Know when that cost is the right one to accept fleet-wide.

## An ordinary response, held open Server-Sent Events introduces no new protocol, no second port and no negotiation step. A client issues a normal GET - conventionally with `Accept: text/event-stream` to state a preference - and the server answers `200 OK` with `Content-Type: text/event-stream`. It then writes the body and simply never ends it. Everything that distinguishes this from a slow download is the media type on the response plus the line grammar inside the body. Two consequences are worth stating before the grammar itself: - **There is no handshake.** Nothing switches protocols, nothing is upgraded, no subprotocol is negotiated. An answer that describes a switching step is describing a different protocol. - **The blank line between the response headers and the body is HTTP message framing, not part of this grammar.** The event-stream parser begins at the first byte of the body; that header separator never reaches it. ## The line grammar The body is a sequence of lines. A line ends with `U+000D CARRIAGE RETURN (CR)`, `U+000A LINE FEED (LF)`, or CR immediately followed by LF, which counts as one terminator. For a non-blank line that is not a comment: 1. Everything before the first `U+003A COLON (:)` is the **field name**. 2. Everything after that colon is the **value**. 3. If the value begins with a single `U+0020 SPACE`, that one space is removed; further spaces belong to the value. Only four field names carry meaning, and they are compared **literally** - no case folding, no trimming of the name: | field | written as | what it contributes to the block | |---|---|---| | `data` | `data: <text>` | appends its value to the payload the block is building | | `event` | `event: <name>` | names the type the dispatched event will carry | | `id` | `id: <value>` | sets an identifier the client retains for this stream | | `retry` | `retry: <digits>` | a reconnection delay in milliseconds, written by the server | Any other field name is ignored. A line whose **first** character is a colon has an empty field name: it is a comment, contributes nothing and dispatches nothing. What a client then *does* with `id` and `retry` - resuming, and waiting before it reconnects - is a separate subject; on this wire they are two more lines obeying exactly the spelling rules above. ## The blank line is the dispatch Field lines accumulate into buffers. Nothing reaches the receiving application while they accumulate, however many bytes have arrived. **A blank line is the only thing that dispatches an event.** When one arrives: - if the block accumulated at least one `data` field, an event is dispatched carrying the assembled payload and the block's type; - if it accumulated no `data` field at all, nothing is dispatched - the buffers are simply cleared; - either way the buffers reset, so nothing leaks from one block into the next. The shape on the wire is therefore: some field lines, a blank line, some field lines, a blank line, indefinitely. An emitter that writes four `data` lines and then pauses has published nothing yet. An emitter that writes one `data` line and a blank line has published one event. ## What the receiving application sees Exactly two things per dispatched event: a **type** - the value of `event`, or the default type `message` when the block named none - and a **payload**, which is a string. The payload is opaque as far as the protocol is concerned. Serialising it as JSON is the near-universal convention because a car-position update has structure, but nothing in the grammar requires it and nothing parses it: `data: A,14,up` is exactly as conforming as an object, and the protocol will not tell you which you sent. ## Where engineers get this wrong - Writing `Data:` or `DATA:` and expecting it to bind. The comparison is literal, so the line is ignored - silently, with no error anywhere. - Expecting each `data` line to arrive as its own event. Repeated `data` lines build **one** payload. - Treating the arrival of bytes as delivery. A partially written block sits in the parser until its blank line arrives. - Reaching for a duplex socket because a brief said "live". When traffic only flows server-to-client, this media type plus six lines of grammar is the entire protocol. - Assuming the stream needs a special status or a negotiated opening. It needs a success status and the right `Content-Type`. The grammar is deliberately small enough to write and read by hand, which is why interviewers expect exact answers about it: you can type an event block into a terminal and a conforming client will dispatch it.

  • Does the server have to do anything special to open the stream, beyond setting the media type?
    No. An ordinary success response with `Content-Type: text/event-stream` and a body it never ends is the whole opening. There is no upgrade, no protocol switch and nothing negotiated; the client's `Accept: text/event-stream` states a preference, it does not open anything.
  • What happens to a line whose field name is none of the four?
    It is ignored. The line contributes nothing to the block being built and causes no error, and the block still dispatches normally when its blank line arrives. That is what makes the grammar forward-extensible and also what makes a misspelling like `Data:` disappear without trace.
  • How do you send a payload that itself contains an empty line?
    Give every payload line its own `data` prefix, including the empty one: a line written as `data:` with no value contributes an empty string plus a line feed. A genuinely blank line cannot appear inside a payload, because a blank line always dispatches the block instead.

A sorting bench where parts pile into the same tray as they come off the belt: an empty gap on the belt is the signal to send that tray on. No gap, no tray goes anywhere.

saying these in an interview costs you the question

  • Thinks Server-Sent Events needs its own port or an upgrade step
  • Believes each data line is delivered as its own event
  • Writes Data: or DATA: and expects the field to bind
  • Assumes the client dispatches as soon as bytes arrive
  • Thinks the payload has to be JSON for the protocol to work
open as a page

A fault report spans three lines in a Server-Sent Events stream - how does the client rebuild that one payload?

level: middleimportance: must knowfreq 60%

basics

~20 s

Each data line contributes its value. The client joins them with a line feed and removes the final one when the block dispatches, so three data lines become one three-line payload. A single space after the colon is stripped.

open as a page

In a Server-Sent Events feed, what does adding an `event: fault` line change about the block it appears in?

level: middleimportance: should knowfreq 52%

basics

~20 s

It sets the type of the event that block dispatches, to the literal name fault. It changes nothing else: the payload, the parsing and the blank-line dispatch are identical, and the name applies to that one block only.

open as a page

Your Server-Sent Events emitter writes a final fault block and closes the response, but clients never see that event - why?

level: seniorimportance: should knowfreq 42%

basics

~20 s

The block was never terminated by a blank line, so the client is still accumulating it. An incomplete block sitting in the parser's buffers when the body ends is discarded, not delivered - the end of the response dispatches nothing.

open as a page

Can a Server-Sent Events stream be delivered in an encoding other than UTF-8, and how would a client know?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

No. An event stream is always encoded and decoded as UTF-8, and the grammar offers no way to announce or select another encoding. A client never inspects anything to decide - it decodes as UTF-8 unconditionally.

open as a page