File: design.md

package info (click to toggle)
python-wslink 2.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,396 kB
  • sloc: python: 2,849; javascript: 1,176; cpp: 29; makefile: 3
file content (124 lines) | stat: -rw-r--r-- 6,673 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# wslink Design and Motivation

wslink grew out of several needs and pressures, and I've collected information
and notes about the background and design here.

## ParaViewWeb RPC and publish/subscribe

ParaViewWeb uses autobahn WAMP protocol for RPC and publish/subscribe messages
as of May 2017. Due to changes to autobahn's WAMP implementation, we can't
upgrade autobahn and continue using WAMP. We require:
* RPC - a remote procedure call that can be fired by the client and return sometime later with a response from the server, possibly an error.
* Publish/subscribe - client can subscribe to a topic provided by the server, possibly with a filter on the parts of interest. When the topic has updated results, the server publishes them to the client, without further action on the client's part.

We would also like:
* Real binary messages - WebSockets support binary messages, and one of our
major use cases is publishing images (rendered frames). WAMP only supports base64-encoded binary objects.
* Other webservers - WAMP has been implemented elsewhere, but in JavaScript
the only implementations require autobahn's router, crossbar.io. It would
be great to support Tornado and CherryPy as alternative webservers to Twisted/autobahn.

## Foundation

[jsonrpc](http://www.jsonrpc.org/specification) has a well-defined, simple, and easily implemented specification for using JSON to do RPC. It is transport agnostic, and language independent, so we can use it between javascript and python. There are many implementations, but we are free to use them or ignore them. JSON can represent four primitive types (Strings, Numbers, Booleans, and Null) and two structured types (Objects and Arrays). We want to extend our messages to handle binary objects, inserted into JSON dict/object, discussed below.

Websockets allow bi-directional communication between the server and client. Authentication needs to be addressed.

Because we wish to support pub/sub, we make the client and server more symmetric than in the jsonrpc spec - the server can publish to the client after a subscription is made, so in jsonrpc terms, the server is making an RPC call to the client. So our server and client must both be able to make and receive jsonrpc calls.

Small examples have proven websocket and binary message support in twisted, tornado, and cherrypy, so if we can abstract the webserver sufficiently, we may be able to support all of these.

## Existing API

Existing ParaViewWeb applications use these code patterns:
* @exportRPC decorator in Python server code to register a method as being remotely callable
* session.call("method.uri", [args]) in the JavaScript client to make an RPC call. Usually wrapped as an object method so it appears to be a normal class method.
* session.subscribe("method.uri", callback) in JS client to initiate a pub/sub relationship.
    * server calls self.publish("method.uri", result) to push info back to the client

We don't support introspection or initial handshake about which methods are supported - the client and server must be in sync. Could the server supply the client with a list of RPC methods it supplies? Yes, but then the client couldn't operate unless it's connected to a server - it would call undefined methods, rather than calling those methods and getting a 'not connected' error. Maybe not a concern.

The 'session' object is provided by Autobahn|JS WAMP, so we need to replace it.

## Jsonrpc implementations

An old [wiki page](https://en.wikipedia.org/w/index.php?title=JSON-RPC&oldid=731445841#Implementations) lists implementations - most are transport/server specific.

For Python, [Tinyrpc](https://tinyrpc.readthedocs.io/en/latest/) has a parser for messages that will do validity checking - seems useful.

For Javascript, [jrpc](https://github.com/vphantom/js-jrpc) extends jsonrpc to be bi-directional, and includes a handshake to upgrade from standard jsonrpc. [RaptorRPC](https://github.com/LinusU/raptor-rpc) might be interesting.

Message format:
```javascript
{
const request = {
    wslink: 1.0,
    id: `rpc:${clientId}:${count}`,
    method: 'render.window.image',
    args: [],
    kwargs: { w: 512, h: 512 }
};

const response = {
    wslink: 1.0,
    id: `rpc:${clientId}:${count}`,
    result: {}, // either result or error, not both
    error: {}
};

// types used as prefix for id.
const types = ['rpc', 'publish', 'system'];
}
```

```python
// add a binary attachment
def getImage(self):
    return {
        "size": [512, 512],
        "blob": session.addAttachment(memoryview(dataArray)),
        "mtime": dataArray.getMTime()
    }
```

## wslink.js

We would like to support kwargs, like wamp, which violates pure jsonrpc.
We can extend it by simply adding a 'kwargs' param, as above. Therefore we'll
use 'wslink' as our version string, instead of 'jsonrpc'. We also change from 'params' to 'args'.

AutobahnJS `session.call()` returns a Promise, except that IE doesn't support
promises, so it uses an alternative if needed. node_modules/autobahn/lib/session.js uses `self._defer()` to retrieve it. connection.js has the factory.
ParaViewWeb uses babel-polyfill, which includes a Promise implementation.

We can use the same defer() pattern, where we store the resolve, reject
functions so we can call them when the message response is received.

### Binary attachments

session.addAttachment() takes binary data and stores it, returning a string
key that will be associated with the attachment. When a message is sent that
uses the attachment key, a binary message is sent beforehand with the
attachment. The client can then substitute the binary buffer for the string
key when it receives the final message.

Now sending a text header message for each binary message to associate it with
a key.

### Subscribe

The client needs to know about subscriptions - the server can blindly send out
messages for any data it produces which might be subscribed to. This is not
very efficient - if the client notifies the server of a subscription, it can
send the data only when someone is listening.

### Handshake

When the client initially connects, it can authenticate with the server, so the
server knows this client can handle the messages it sends, and the server can
provide the client with a unique client ID - which the client can embed in the
rpc "id" field of it's messages to the server.

* The first message client sends should be hello, with the secret key provided by it's launcher.
* Server authenicates the key, responds with the client ID.
* If the client doesn't send a key, the server can choose to serve an un-authenticated client, or respond with an authentication error message.