REST over WebSocket - ngmediaserver/NG-Media-Server GitHub Wiki

REST over WebSocket protocol specification

REST is an elegant and powerful architecture for representing server resources and related actions.

Common REST architectures have proved to be effective with many Web Services.

However in some environment, like those where client requests are as important as server requests ("notifications"), common REST architectures show limitations because they are too tied to the Client/Server model of HTTP (commonly used as the transport layer with REST).

For example, with Real Time Communications (RTC), calls can be seen as server resources - perfectly matching REST -, however client and server roles are most often interchangeable:

  • Client can request to create an outgoing call / Server can notify of an incoming call created by a Remote (trying to join the Client)
  • Client can request to answer an incoming call / Server can notify of an outgoing call answered by the Remote
  • Client can request to send a message / Server can notify of a message sent by the Remote
  • Client can request to terminate a call / Server can notify of a call terminated by the Remote

Traditional AJAX requests and Webhooks (callbacks) work well for many Web Services, but when notifications are as important as requests, HTTP wastes significant resources (with unnecessary HTTP headers and some unnecessary responses) and HTTP callbacks can be hard to deploy because they are not NAT/Firewall friendly.

WebSockets provide an interesting solution, by enabling to exchange full-duplex messages simply and efficiently. In addition WebSockets work typically where HTTPS works, because WebSockets are established on top of HTTP.

We designed the REST over WebSocket protocol to use REST in environments requiring high performance (minimal bandwidth, minimal delay, minimal complexity) compared to commonly used HTTP.

The REST over WebSocket protocol specifies how to transport CRUD requests and responses over WebSockets, eliminating all unnecessary HTTP headers, and eliminating NAT/Firewall issues with notifications (callbacks).

Example

>> Sample request sent by the client:

[
  {
    "method":"POST",
    "path":"/calls",
    "body":{"fromUri":"callto:me", "toUri":"callto:you"},
    "id":"1234"
  }
]

<< Sample response sent by the server, matching the previous client request:

[
  {
    "code":201,
    "phrase":"Created",
    "body":{"id":"xyz"},
    "id":"1234"
  }
]

<< Sample notification sent by the server:

[
  {
    "method":"PATCH",
    "path":"/calls/xyz",
    "body":{"state":"connected"}
  }
]

Design

The REST over WebSocket protocol is a very thin layer transporting full-duplex CRUD messages (requests and responses) over WebSockets using JSON messages and UTF8 charset encoding. Compared to REST over HTTP, it provides 3 main enhancements:

  • It is designed for notifications (requests sent from the Server to the Client). Notifications can be useful to notify of:
    • Resources created on the server (POST)
    • Resources deleted on the server (DELETE)
    • Resources modified on the server (PUT and PATCH)
  • It is optimized to save resources:
    • When no type is specified the body is expected to be a JSON object representing the JSON body of the corresponding HTTP message. When a type is provided, the body MUST be a JSON string representation of the body of the corresponding HTTP message. No need to stringify/unstringify JSON within JSON.
    • A request that needs a response provides an "id". The response will provide the same id so that the initiator of the request can match the response with the request.
    • A request that does not need a response simply provides no "id". The receiving side must not send a response then. This is particularly important with notifications, because often a notification is just informative, and any response to such request is typically ignored.
    • Short names are used (like "body" and "type") and distinction between requests and responses is implicit: requests provide a "method" (and "path") whereas responses provide a "code" (and "text")
    • No initial handshake is necessary.
  • A JSON message is a collection representing the HTTP message. Optionally, it can also be an array of 1 or more HTTP messages:
    • An array of 0 message MUST not be used. In particular, it is not a valid practice for KeepAlive, as routing extensions may be added on a per HTTP message level.
    • ??? Several HTTP messages in one JSON messages are not guaranteed to be delivered all together in the same JSON message. A proxy may route some HTTP messages to different locations ???
    • The id sent in the request may be delivered modified to the remote party, like routing information may be appended in the middle. The side receiving the id must not interpret this information considered as opaque; it must simply send a response using the id received. The response delivered to the initiator of the request will contain an id matching the one of the original request; any routing information previously appended must have been removed previously.

Both with client requests and server notifications, resources always represent server resources.

Note that this protocol can also be used for more generic HTTP over WebSocket operations, but here we define optimized behaviors for the CRUD specific case.

Basic parameters:

  • method : HTTP request Method (GET, POST, PUT, PATCH, DELETE, ...)
  • path : HTTP request path (path part of the Request-URI, potentially excluding query strings)
  • body : HTTP request and response message-body (content)
  • code : HTTP response Status-Code
  • phrase : HTTP response Reason-Phrase
  • id : identifier used to associate a response with the original request

Additional parameters:

  • type : HTTP Content-Type (MIME type of the body). When no type parameter is provided, the body is a JSON object representing the JSON content of the HTTP message.
  • headers[]. Additional HTTP headers MUST be provided as {"name":"value"} pairs.
  • params[]. Query strings parameters SHOULD (???MUST???) be provided as {"name":"value"} pairs instead of being present in the uri.