Skip to content

RFC: Multiplexed WebSocket Streams

Kevin Swiber edited this page Sep 30, 2015 · 3 revisions

Introduction

In Zetta today, each stream is exposed as a WebSocket link in the API.

Example

{
  "title": "state",
  "rel": ["monitor", "http://rels.zettajs.io/object-stream"],
  "href": "ws://zetta-cloud-2.herokuapp.com/servers/Detroit/events?topic=arm%2Fa5cb1a72-c3e7-47b6-818d-6d81b16e9ed4%2Fstate"
}

Clients subscribe to streams by opening a new WebSocket connection using the URL provided in the API response.

Problem

While this approach has worked so far, it is not without its issues.

  1. Each stream subscription requires a new WebSocket connection. If a client wants to subscribe to many WebSockets, this can become resource intensive and even prohibitive on mobile devices.
  2. If a topic can be inferred based on previously acquired information, clients must crawl the API to find the stream representation before subscribing. This can introduce latency in the client experience.

Scope

A change will impact:

  1. HTTP/WebSockets Client API
  2. SPDY Client API
  3. Object stream URL parsing for clients

Proposed Solution

A simple WebSocket sub-protocol will need to be introduced.

Zetta can expose a new link on the root resource of the Client API.

{
  "rel": ["[http://rels.zettajs.io/events](http://rels.zettajs.io/events)"],
  "href": "ws://example.com/events"
}

After a client’s initial connection, no streams will be subscribed to implicitly. Subscription requests must be sent over the WebSocket. Then event messages will be received.

The pattern for topic names can be derived from information about a server, a device, and a stream:

{server-name}/{device-type}/{device-id}/{stream-name}

Subscription Request

A client SHOULD send only one Subscription Request per topic. Upon a successful request, the server MUST send a Subscription Acknowledgement Message. If an error is encountered processing the request, the server MUST send an Error Message if it's able. After a Subscription Acknowledgement Message, a client will start receiving Event Messages.

A subscription request MUST contain the following properties:

  • action - Must be set to the string "subscribe". (String)
  • topic - The topic of the subscription. See Topic Format below for more information. (String)

Optionally, a subscription request MAY contain the following properties:

  • limit - Number of events to be received before the client is automatically unsubscribed from the topic. Server will send an unsubscribe-ack when the limit is reached. When not present, a value of Infinity will be used. (Number)

Topic Format

For a Subscription Request, the topic provided is hierarchical, with each node separated by a slash (/), such as:

Detroit/thermostat/a5cb1a72-c3e7-47b6-818d-6d81b16e9ed4/temperature

Wildcard Support

Topics may include wildcards. A wildcard is represented by an asterisk (*) and may be used in hierarchical topics, such as:

Detroit/thermostat/*/temperature

A single asterisk (*) denotes a wildcard for a single level of the hierarchy. A double asterisk (**) denotes a wildcard for multiple levels of the hierarchy, such as:

Detroit/**/temperature

Regular Expression Support

Topic nodes may be represented in the form of a regular expression. Regular expressions should be wrapped in curly braces ({}), such as:

{^Det.+$}/thermostat/a5cb1a72-c3e7-47b6-818d-6d81b16e9ed4/temperature.

Query Support

Topics may also include a query. Queries ensure an event is only published if it meets a certain filter. They also allow for field selection. Queries are expressed in the Calypso Query Language. They are appended to a topic after a question mark, such as:

Detroit/thermostat/*/temperature?select * where data > 85

Detroit/thermostat/*/temperature?select data.degreesC where data.degreesF > 85

Example

{
  "action": "subscribe",
  "topic": "Detroit/arm/a5cb1a72-c3e7-47b6-818d-6d81b16e9ed4/state",
  "limit": 10
}

Subscription Acknowledgement Message

A server MUST send a Subscription Acknowledgement Message to a client upon a successful subscription.

A Subscription Acknowledgement Message MUST contain the following properties:

  • type - Must be set to the string "subscribe-ack". (String)
  • timestamp - A UTC timestamp of when the message was sent. (Number)
  • topic - The topic of the subscription. (String)
  • subscriptionId - Subscription identifier to be returned with related Event Messages. (Number)

Example

{
  "type": "subscribe-ack",
  "timestamp": 1442944840135,
  "topic": "Detroit/arm/a5cb1a72-c3e7-47b6-818d-6d81b16e9ed4/state",
  "subscriptionId": 2
}

Event Message

Once a subscription request has been made, an Event Message stream will start to flow.

An Event Message MUST contain the following properties:

  • type - Must be set to the string "event". (String)
  • topic - The topic to which the message belongs. (String)
  • subscriptionId - Identifier of the associated subscription. (Number)
  • timestamp - A UTC timestamp of when the message was sent. (Number)
  • data - The data payload for the message. (String|Number|Array|Object)

TODO: Add language about queries coming back as an object (key-value pairs).

Example

{
  "type": "event",
  "topic": "Detroit/arm/a5cb1a72-c3e7-47b6-818d-6d81b16e9ed4/state",
  "subscriptionId": 2,
  "timestamp": 1442944840135,
  "data": "moving-claw"
}

Unsubscribe Request

Once an Unsubscribe Request is received, the server MUST perform a best effort to stop the message flow immediately. The client MAY receive additional messages for a topic while the server is processing the request. Upon successfully processing an Unsubscribe Request, a server MUST send an Unsubscribe Acknowledgement Message. If an error is encountered processing the request, the server should send an Error Message.

An Unsubscribe Request MUST contain the following properties:

  • action - Must be set to the string "unsubscribe". (String)
  • subscriptionId - The identifier of the subscription. (Number)

Example

{
  "action": "unsubscribe",
  "subscriptionId": 2
}

Unsubscribe Acknowledgement Message

A server MUST send an Unsubscribe Acknowledgement Message to a client upon successfully processing an Unsubscribe Request.

An Unsubscribe Acknowledgement Message MUST contain the following properties:

  • type - Must be set to the string "unsubscribe-ack". (String)
  • timestamp - A UTC timestamp of when the message was sent. (Number)
  • subscriptionId - The identifier of the subscription. (Number)

Example

{
  "type": "unsubscribe-ack",
  "timestamp": 1442944840135,
  "subscriptionId": 2
}

Error Message

A server MUST send an Error Message upon encountering any issues processing requests from clients.

An Error Message MUST contain the following properties:

  • type - Must be set to the string "error". (String)
  • code - Error code defining the error. See Error Codes below. (Number)
  • timestamp - A UTC timestamp of when the message was sent. (Number)
  • topic - The topic of the subscription. (String)

An Error Message MAY contain the following property:

  • message - A plaintext description of the error. (String)
  • subscriptionId - Identifier of the associated subscription. Only applicable if an error occurs after a subscription is established. (Number)

Error Codes

  • 400 - Bad Request - Invalid JSON in request
  • 405 - Method Not Supported - Invalid "action" value for incoming message.
  • 500 - Server Error - We messed up.

Example

{
  "type": "error",
  "code": 500,
  "timestamp": 1442944840135,
  "topic": "Detroit/arm/a5cb1a72-c3e7-47b6-818d-6d81b16e9ed4/state",
  "message": "Server exploded.",
  "subscriptionId": 2
}

Notes

  1. Only Zetta object streams will qualify for this protocol.
  2. We should come up with a WebSocket sub-protocol type.
  3. We should investigate if WAMP/STOMP/ReactiveSocket support would better serve our needs.
  4. Existing functionality should remain. WebSocket URLs should be in context.
  5. Zetta needs language around clients parsing topics out of URLs to use with the generic subscription link.
Clone this wiki locally