Skip to content

[Discussion] Websocket support #138

@joshmossas

Description

@joshmossas

Currently the only support for realtime messages is via Server Sent Events. This works well but only supports server -> client messages. One day it would be good to have first class websocket support enabling two-way realtime communication.

Some Things To Consider

  • Do we keep the request -> response model of current HTTP procedures? Or do we need to establish a new abstraction for these sorts of messages?
    • For example JSON-RPC has request -> response style RPCs and notifications which are send and forget
    • Maybe if we come up with a new notifications / events abstraction this could be reused for webhooks and other similar send and forget style events.
  • How will we handle authentication?
    • HTTP headers are only available during the initial "upgrade" request
  • How will we determine what procedure is being called?
    • URLs are being used for HTTP procedures, which obv doesn't work for websockets

Some Initial Ideas

Not a ton of thought has been put into these just kinda spitballing here.

Mimicking the Server Sent Event (SSE) payload

We could use a format similar to Server Sent Events (SSE). SSE messages use \n\n to separate different properties.

# example SSE
event: message
data: {"id":"1","content":"hello world"}

Similarly websocket messages could look something like this:

# request payload
id: 1442
procedure: users.getUser
headers: { "Authorization": "Bearer 12345", "client-version": "4" }
data: { "userId": "1" }
# response payload
id: 1442
statusCode: 200
procedure: users.getUser
data: { "id":"1", "name": "John Doe", "email": null, "isAdmin": false }

\n already have to be escaped by JSON so there's no conflicts there.

The id field could be used by the client to implement a request -> response style pattern. Maybe omitting the id field would make it "send and forget".

The procedure field is used to indicate which procedure is being called.

The statusCode field is used to indicated whether the response failed or not. I used an HTTP status code here but that might be weird - unsure. (Currently RPC errors are pretty heavily tied to HTTP status codes. I might need to make a cleaner error declaration abstract to enable other protocols. Either that or we just use HTTP status codes for everything even if it's technically not HTTP)

The headers field would be the client headers sent with every request. (Some alternative approaches could be explored to maybe avoid sending client headers with every request?)

The data field is the actual payload of the message

Mimicking the HTTP Payload

We could also explore sending a payload that's similar to HTTP itself. Then any needed metadata could be set as headers before the actual body

# example HTTP request
POST /users/get-user HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
Host: www.example.com
Content-Type: application/json
Authorization: Bearer 12345
Client-Version: 4

{ "userId": "1" }
# example Arri websocket request payload
users.getUser
Request-Id: 1442
Authorization: Bearer 12345
Client-Version: 4

{ "userId": "1" }
# example Arri websocket response payload
users.getUser
Status-Code: 200
Request-Id: 1442

{ "id":"1", "name": "John Doe", "email": null, "isAdmin": false }

Just like the other example maybe the Request-Id header could be omitted for "send and forget" style requests.

One advantage to this is that it would be pretty trivial to send alternative content types like cbor or messagepack should we add support down the line (I would like to eventually support a binary encoding method). A simple Content-Type header would do the trick just like with HTTP.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions