Skip to content
Subprotocols & Extensions
step 1/5

Reading — step 1 of 5

Read

~2 min readProduction

Subprotocols & Extensions

RFC 6455 hands you a message pipe — TEXT and BINARY frames — and says nothing about what the bytes mean. Is {"op":1} a chat message? An MQTT packet? A GraphQL subscription event? Both sides must agree, and the handshake has a slot for exactly that agreement.

Subprotocols: negotiating meaning

The client offers a list, in preference order; the server picks exactly one or declines:

Client:  Sec-WebSocket-Protocol: chat, superchat, json-rpc
Server:  Sec-WebSocket-Protocol: superchat

The rules that matter when you implement this:

  • The server's chosen value MUST be one the client offered. If a client sees a subprotocol it never asked for, it MUST fail the connection.
  • The server may omit the header entirely — meaning "no subprotocol"; the client then decides whether bare WebSocket is acceptable.
  • Names are case-sensitive tokens. Chat and chat are different protocols.
  • Which side's preference order wins is your server's policy choice; a common design (and this exercise's rule) is that the server's list is ordered by preference and the first server entry the client also offered wins.

Real names you'll meet: mqtt (IoT brokers over WS), graphql-transport-ws / graphql-ws (GraphQL subscriptions), wamp.2.json (WAMP RPC/pub-sub), plus any private string your application invents — the IANA registry exists but unregistered names are fine and common.

Extensions: negotiating the wire format

A subprotocol layers meaning on top of messages. An extension changes the framing itself — which is why extensions get their own header and their own RSV bits in the frame header:

Client:  Sec-WebSocket-Extensions: permessage-deflate
Server:  Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover

permessage-deflate (RFC 7692) compresses payloads and flags compressed messages with the RSV1 bit — a bit that is a protocol error if no extension negotiated it. That's the deal: RSV bits mean nothing until a handshake gives them meaning. The server can also attach parameters when accepting, as above. A dedicated lesson later in this course implements the compression itself.

Libraries (ws, websockets, gorilla) handle the header plumbing; your job as a server author is the selection policy — which is precisely this lesson's exercise.

Your exercise

Implement the negotiation: first entry of the server's list that also appears in the client's list, else (none). What the grader catches: server preference wins — for a,b,c|c,b,a the expected output is a, not c (walking the client's list first is the classic bug). Matching is case-sensitive: Chat|chat expects (none) — do not .lower() anything. And either side empty means no deal: both |chat and chat| expect (none), so filter out empty strings after splitting ("".split(",") yields [""], which would falsely match another empty entry).

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…