In a WebSocket handshake, how is `permessage-deflate` negotiated, and what marks a message on the wire as compressed?
answer
- one field, agreed once
- offers in, one comes back
- the unit is a message
- RSV1 on the first frame
- four octets stripped by the sender
basics
~20 sThe client offers permessage-deflate in Sec-WebSocket-Extensions on the opening request; the server returns the one offer it accepts, with agreed parameters. Afterwards the RSV1 bit on a message's first frame marks that message's payload as compressed.
solid answer
~40 s`permessage-deflate` is defined by RFC 7692 as an extension to the WebSocket protocol, negotiated once in the opening handshake through `Sec-WebSocket-Extensions`. The client sends one or more offers, each optionally carrying parameters; the server accepts at most one and returns it, with the parameter values that will actually apply, on the `101 Switching Protocols` response. A server must not return an extension the client did not offer, and there is no renegotiation later — what the handshake agreed holds for the connection's life. Afterwards, compression is applied **per message**, not per frame: the sender compresses the payload, removes the trailing four octets `0x00 0x00 0xff 0xff`, and sets **RSV1** on the message's first frame. The receiver sees RSV1, appends those four octets back, and inflates. Control frames are never compressed.
code
http · 13 linesGET /cues HTTP/1.1
Host: desk.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=12go deeper
Know that WebSocket compression exists, is asked for in the opening handshake rather than turned on later, and is called permessage-deflate. Recognising the extension field in a captured handshake is enough at this stage.
Explain the offer-and-response shape, that the server may accept at most one offer, and what happens per message: compress, strip the four-octet tail, set RSV1 on the first frame, receiver appends and inflates.
Show when you would decline it — already-compressed payloads, very small messages, CPU-bound sockets — and be able to say what a rejected or absent extension response means for the connection.
The trade is fleet-wide: bandwidth against CPU and memory on every socket you hold, and a compression-oracle hazard wherever secrets share a context with attacker-influenced text.
## What the extension is `permessage-deflate` is a WebSocket **extension**, defined in RFC 7692 rather than in RFC 6455 itself. It compresses the payload of each application message with deflate. Two words in its name do real work: *per message*, because the unit is a whole message and not a frame, and *deflate*, because the compressor keeps a sliding window of previously seen bytes and refers back to it. This is one of several things called compression on a connection like this, and they are not interchangeable: | Mechanism | Negotiated where | Applies to | |---|---|---| | `permessage-deflate` | the WebSocket opening handshake | each WebSocket message payload | | HPACK or QPACK field compression | on the HTTP/2 or HTTP/3 connection beneath | header fields, never your payload | | an HTTP content coding | per HTTP response | one whole response body | ## Negotiating it - The client sends `Sec-WebSocket-Extensions` on the opening request with one or more **offers**. Each offer names `permessage-deflate` and may carry parameters. - Several offers may be listed, from most to least preferred, so the client can say "these parameters if you can, otherwise these". - The server accepts **at most one** offer and returns it in its own `Sec-WebSocket-Extensions` field on the `101 Switching Protocols` response, carrying the parameter values that will actually apply. - The server **must not** return an extension the client did not offer. A client that sees one fails the WebSocket connection — and because that check is part of validating the handshake, the connection is never established, so no close frame is sent at all. - If the server omits the field, nothing is compressed and the socket is perfectly usable. Declining is legal. - There is **no renegotiation**. Whatever the handshake agreed holds until the connection closes. ## What happens per message Once agreed, each message a sender compresses goes through four steps: 1. Compress the message payload with deflate, flushing so the message can be read on its own. 2. If the compressed result ends with the four octets `0x00 0x00 0xff 0xff` — the empty uncompressed block that the flush emits — **remove them**. They are redundant on the wire because the receiver knows they are there. 3. Send the result as the message payload, with **RSV1 set on the first frame of that message**. 4. The receiver sees RSV1, **appends `0x00 0x00 0xff 0xff` back** to the payload it received, and inflates. The direction in step 2 and step 4 is the detail interviewers probe, because it is easy to state backwards: the **sender strips**, the **receiver appends**. Four octets per message is also the reason the extension is not free on tiny messages — a compressed short message can be longer than the original. Two framing rules follow from *per message*: - RSV1 is set only on the **first** frame of a message. If the message is fragmented across several frames, the continuation frames leave it clear, and the compressed bytes simply span them. - **Control frames are never compressed**, so RSV1 does not appear on them. A sender is never obliged to compress. With the extension negotiated it may still send an uncompressed message by leaving RSV1 clear, which is exactly what a sensible implementation does for a payload that will not shrink. ## When it does not pay - **Already-compressed payloads.** Images, audio, video and anything pre-gzipped will not shrink, and you pay CPU and four octets to discover that. - **Very small messages.** Below a threshold the deflate framing overhead exceeds the saving. - **High message rates.** Compression is per message, so the CPU cost scales with message count, not just with bytes. - **Memory.** Retaining a compression context between messages costs memory on every open socket, which is a separate negotiation decision of its own. - **Mixing secrets with attacker-influenced text.** Compressing both in the same context leaks information through the compressed length — the compression-oracle class of attack — so a payload that mixes them wants care or no compression at all. Where it pays is the opposite shape: repetitive text messages, especially structured payloads that repeat the same field names on every message, on a connection that is not already CPU-bound.
- With `permessage-deflate` negotiated, must every message be compressed?No. A sender decides per message and signals its decision with RSV1 on the first frame. Leaving RSV1 clear sends the payload as-is, which is the right choice for already-compressed or very small payloads. Control frames are never compressed.
- A fragmented message is compressed. Where does RSV1 appear?Only on the first frame of that message. Continuation frames leave RSV1 clear while carrying the remaining compressed bytes, because the compression unit is the whole message and the flag describes the message, not each frame.
- The 101 response returns an extension the client never offered. What now?The client fails the WebSocket connection. Because that check happens while validating the handshake response, the connection is never established, so there is no socket on which to send a close frame — the client abandons it outright.
saying these in an interview costs you the question
- Says each frame is compressed independently rather than each message
- Thinks compression is negotiated per message instead of once per connection
- Claims the receiver strips the four-octet deflate tail
- Believes RSV1 appears on every frame of a compressed message
- Confuses it with header-field compression on the transport beneath
- Assumes compression can be turned on later without a new handshake