HTTP Upgrade / WebSocket RFC
XIDL HTTP Upgrade / WebSocket Mapping Specification
Section titled “XIDL HTTP Upgrade / WebSocket Mapping Specification”This page defines the dual-mode semantics of @upgrade:
- WebSocket mode (
protocol = "websocket"): a full RFC 6455 WebSocket session with typed stream items and WebSocket-specific options. - Raw upgrade mode (any other protocol name): HTTP 101 followed by a raw byte stream for private protocols.
This RFC defines:
- Mode selection from
protocol. - Signature and parameter rules per mode.
- Handshake requirements.
- WebSocket wire profile (framing, close codes, heartbeat, backpressure).
- Compatibility with
@bidi_stream.
This RFC does not define:
- Private byte protocols carried in raw upgrade mode.
- JSON-RPC over WebSocket (see the JSON-RPC Stream RFC).
- Broker-level delivery guarantees.
Mode Selection
Section titled “Mode Selection”protocol (ASCII case-insensitive) == "websocket" -> WebSocket modeotherwise (non-empty) -> Raw upgrade modeprotocol | Mode |
|---|---|
"websocket", "WebSocket", "WEBSOCKET" | WebSocket |
"fastnet", "xidl-raw", … | Raw upgrade |
"ws", "wss" | Invalid — use protocol = "websocket" |
| empty / missing | Invalid |
TLS is an endpoint concern (https / wss at the transport layer), not a
protocol name.
Annotation Syntax
Section titled “Annotation Syntax”Raw upgrade mode
Section titled “Raw upgrade mode”@upgrade(protocol = "xidl-raw")@path("/raw{?token}")void connect_raw(@query @rename("token") string token);WebSocket mode
Section titled “WebSocket mode”@upgrade(protocol = "websocket")@upgrade(protocol = "websocket", codec = "json")@upgrade(protocol = "websocket", subprotocol = "fastnet.v1")@upgrade(protocol = "websocket", heartbeat = "20s")@upgrade(protocol = "websocket", max_message = 1048576)Combined form:
@upgrade( protocol = "websocket", codec = "json", subprotocol = "fastnet.v1", heartbeat = "15s")@get(path = "/ctrl/{session}")void control( @path string session, in string opcode, in sequence<octet> payload, out string event, out sequence<octet> data);Parameters
Section titled “Parameters”| Parameter | Modes | Type | Default | Constraints |
|---|---|---|---|---|
protocol | both | string | required | non-empty; websocket selects WebSocket mode |
codec | WebSocket | string | "json" | json | msgpack | bytes |
subprotocol | WebSocket | string | none | RFC 6455 token; non-empty |
heartbeat | WebSocket | duration string | none (no active ping) | "500ms" / "20s" / "1m"; "0" disables |
max_message | WebSocket | integer bytes | implementation default (1 MiB recommended) | >= 1 |
WebSocket-only parameters must not appear on a raw upgrade method.
Allowed companion annotations: HTTP verb/path, HTTP security annotations,
@cors, @deprecated, @rename, @rename_all, @optional, @flatten,
and handshake-scope @path / @query / @header / @cookie.
Forbidden: @stream_codec(...), and combining @upgrade with
@bidi_stream / @server_stream / @client_stream on the same method.
Signature Rules
Section titled “Signature Rules”Raw upgrade mode
Section titled “Raw upgrade mode”- The method must return
void. - Body / stream-item parameters are forbidden.
- Only handshake-scope parameters (
@path/@query/@header/@cookie) are allowed. - The generated server handler receives an upgrade handle and owns the byte protocol after 101.
WebSocket mode
Section titled “WebSocket mode”Same shape as @bidi_stream:
- The method must return
void. in/inoutparameters that are not handshake-scope form the inbound stream itemTIn(0 params -> unit, 1 param -> that type, N params -> generated item struct).out/inoutparameters form the outbound stream itemTOut.TInandTOutmust not both be empty.- Handshake-scope parameters are bound during the HTTP request and never appear in stream messages.
- With
codec = "bytes", both item types must besequence<octet>.
HTTP Mapping
Section titled “HTTP Mapping”- HTTP method is GET.
- Route templates follow the HTTP RFC.
- The upgrade request must match one of the method routes; otherwise
respond
404.
Raw upgrade handshake
Section titled “Raw upgrade handshake”- Authenticate and bind handshake parameters first; on failure return a normal HTTP error without upgrading.
- Respond
101withUpgrade: <protocol>andConnection: upgrade. - The implementation should reject requests whose
Upgradeheader does not match the declared protocol (400). - After 101, expose a raw upgraded byte stream.
WebSocket handshake (RFC 6455)
Section titled “WebSocket handshake (RFC 6455)”Required request headers:
Upgrade: websocketConnection: UpgradeSec-WebSocket-KeySec-WebSocket-Version: 13Sec-WebSocket-Protocol: <subprotocol>when a subprotocol is declared
Server steps:
- Route match, auth, handshake parameter binding. Failures return normal
HTTP errors (
401/403/404/400) without upgrading. - Validate WebSocket upgrade headers and key.
- If
subprotocolis declared, select it from the request offer; if it cannot be selected, respond400. - Respond
101withUpgrade: websocket,Connection: Upgrade,Sec-WebSocket-Accept: base64(SHA-1(key + GUID)), andSec-WebSocket-Protocolwhen negotiated.
Sec-WebSocket-Accept MUST be computed. Blind 101 responses without RFC 6455
handshake headers are not a valid WebSocket mode implementation.
Security follows the HTTP Security RFC: credentials are checked before upgrade and never appear in stream payloads.
WebSocket Wire Profile
Section titled “WebSocket Wire Profile”Application messages
Section titled “Application messages”One application message equals one WebSocket message.
codec | Frame | Payload |
|---|---|---|
json | Text | UTF-8 JSON of TIn / TOut |
msgpack | Binary | MessagePack of TIn / TOut |
bytes | Binary | raw sequence<octet> content |
Application payloads are bare typed values (no mandatory envelope). Line
delimiters (\n) are not injected or required.
Close and errors
Section titled “Close and errors”| Event | Close code | Reason |
|---|---|---|
| normal end | 1000 | optional short text |
| application error | 4000 | UTF-8 JSON {"code":<i32>,"msg":"..."} |
| protocol error | 1002 | optional |
| message too big | 1009 | optional |
| internal error | 4001 | same JSON shape as application error |
Codes 4000–4999 are reserved for XIDL application errors. Reasons are
limited to 123 bytes by RFC 6455.
Heartbeat
Section titled “Heartbeat”- Implementations MUST answer Ping with Pong and MUST flush the Pong promptly (before waiting for the next application write).
- When
heartbeat = D(D > 0), both peers SHOULD ping everyD. - Missing heartbeat / no frames for two intervals MAY close the connection.
Backpressure and limits
Section titled “Backpressure and limits”- Send queues MUST be bounded;
sendwaits or fails when full. - Payloads larger than
max_messageclose with1009. - Implementations SHOULD bound concurrent sessions and handshake timeouts.
Compatibility with @bidi_stream
Section titled “Compatibility with @bidi_stream”@bidi_streamremains valid and projects to a bidirectional stream with the default WebSocket profile (codec = json, no subprotocol, no active heartbeat).- Its wire profile is bit-compatible with
@upgrade(protocol = "websocket")using default parameters. - New contracts SHOULD prefer
@upgrade(protocol = "websocket", ...)when they need WebSocket-specific options.
Generator Notes
Section titled “Generator Notes”protocol = "websocket"is projected as a bidirectional HTTP stream plus a WebSocket configuration object.- Raw upgrade methods keep the upgrade-handle API shape.
- Targets that cannot generate WebSocket mode MUST fail codegen with an explicit unsupported-target error.
Target support
Section titled “Target support”| Target | WebSocket mode / @bidi_stream | Raw @upgrade |
|---|---|---|
rust-axum | Supported (typed bidi session, subprotocol, ping/pong) | Supported (upgrade handle) |
go-rest | Supported (WSBidiStream, subprotocol, ping/pong), codec = json | Not yet (explicit error) |
typescript-rest | Supported (open_*Session / create_*Handler, codec = json) | Not yet (explicit error) |
openapi | Described with x-protocol: websocket and optional x-websocket-subprotocol | Not described as WebSocket |