Project

General

Profile

Websocket server » History » Version 8

Tom Clegg, 10/25/2016 06:03 PM

1 1 Tom Clegg
h1. Websocket server
2
3 8 Tom Clegg
(draft)
4 1 Tom Clegg
5
{{toc}}
6
7 8 Tom Clegg
See also: [[Events API]]
8 1 Tom Clegg
9 8 Tom Clegg
h1. Messages
10 1 Tom Clegg
11 8 Tom Clegg
Each message is JSON-encoded as an object with exactly one key. The key indicates the message type, and the value contains the message content.
12 1 Tom Clegg
13 8 Tom Clegg
This allows clients and servers to decode messages efficiently: decode the first token to determine the message type, then (if the message content is relevant) decode the message payload into an appropriate data structure.
14 1 Tom Clegg
15 8 Tom Clegg
<pre><code class="javascript">
16
good: {"error":{"code":418,"text":"I'm a teapot"}}
17 1 Tom Clegg
18 8 Tom Clegg
bad:  {"errorCode":418,"errorText":"I'm a teapot"}
19
</code></pre>
20 1 Tom Clegg
21 8 Tom Clegg
Clients must ignore any unrecognized keys they encounter in the payload. This allows the server to add features without breaking existing clients.
22 1 Tom Clegg
23 8 Tom Clegg
h2. setAuth
24 1 Tom Clegg
25 8 Tom Clegg
After establishing a connection, and before subscribing to any streams, the client must supply an authorization token.
26 1 Tom Clegg
27 8 Tom Clegg
Successful authorization is acknowledged.
28 1 Tom Clegg
29 8 Tom Clegg
<pre><code class="javascript">
30
client: {
31
          "setAuth":{"token":"3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi"}
32
        }
33 1 Tom Clegg
34 8 Tom Clegg
server: {
35
          "auth":{"uuid":"zzzzz-gj3su-077z32aux8dg2s1"}
36
        }
37
</code></pre>
38 1 Tom Clegg
39 8 Tom Clegg
Unsuccessful authorization results in an error.
40 1 Tom Clegg
41 8 Tom Clegg
<pre><code class="javascript">
42
client: {
43
          "setAuth":{
44
            "token":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}}
45 6 Peter Amstutz
46 8 Tom Clegg
server: {
47
          "authError":{
48
            "errorText":"invalid or expired token"}}
49
</code></pre>
50 1 Tom Clegg
51 8 Tom Clegg
h2. subscribe
52 1 Tom Clegg
53 8 Tom Clegg
Subscribe to an event stream.
54 1 Tom Clegg
55 8 Tom Clegg
If the given ETag does not match the current ETag, the server should send an update event right away: this means the client has already missed one or more updates since the version it has cached.
56 1 Tom Clegg
57 8 Tom Clegg
<pre><code class="javascript">
58
client: {
59
          "subscribe":{
60
            "uuid":"zzzzz-4zz18-1g4g0vhpjn9wq7i",
61
            "etag":"9u32836jpz7i046sd84gu190h"}}
62 1 Tom Clegg
63 8 Tom Clegg
server: {
64
          "event":{
65
            "msgID":12345,
66
            "type":"update",
67
            "uuid":"zzzzz-4zz18-1g4g0vhpjn9wq7i",
68
            "etag":"1wfdizt65l5w597jf5lojf8jm"}}
69
</code></pre>
70 1 Tom Clegg
71 8 Tom Clegg
When a client subscribes to a stream X, but is not authorized to read the object with UUID X (or there is no such object), the server sends an error message. This does not terminate the connection, nor does it affect any other streams.
72 1 Tom Clegg
73 8 Tom Clegg
<pre><code class="javascript">
74
client: {
75
          "subscribe":{
76
            "uuid":"zzzzz-tpzed-000000000000000",
77
            "etag":"x"}}
78 1 Tom Clegg
79 8 Tom Clegg
server: {
80
          "subscribeError":{
81
            "uuid":"zzzzz-tpzed-000000000000000",
82
            "errorText":"forbidden"}}
83
</code></pre>
84
85
h2. Container and job logging events
86
87
[[Events API]] &rarr; "Non-state-changing events"
88
89
<pre><code class="javascript">
90
client: {
91
          "subscribe":{
92
            "uuid":"zzzzz-dz642-logscontainer03",
93
            "etag":"2qtm62j6zb3nx5zud8b5v0ayl",
94
            "select":["logs.event_type","logs.properties.text"]}}
95
96
server: {
97
          "event":{
98
            "msgID":12346,
99
            "type":"log",
100
            "uuid":"zzzzz-dz642-logscontainer03",
101
            "etag":"2qtm62j6zb3nx5zud8b5v0ayl",
102
            "log":{
103
              "event_type":"stderr",
104
              "properties":{
105
                "text":"foo\n"}}}}
106
</code></pre>
107
108
h2. Update events
109
110
h2. Create events
111
112
h2. Delete events
113
114
h2. Missed events
115
116
Zero or more events for a single stream have been skipped:
117
118
<pre><code class="javascript">
119
server: {
120
          "eventsMissed":{
121
            "msgID":12347,
122
            "uuid":"zzzzz-dz642-logscontainer03"}}
123
</code></pre>
124
125
Zero or more events on one or more of the subscribed streams have been skipped:
126
127
<pre><code class="javascript">
128
server: {
129
          "eventsMissed":{
130
            "msgID":12348}}
131
</code></pre>
132
133
h1. Server implementation
134
135
h2. Architecture
136
137
Go server with a goroutine serving each connection.
138
139
One goroutine receives incoming events and assigns msgID numbers.
140
141
Each connection has an outgoing event queue. Leave room for ability to resize a connection's outgoing queue dynamically, provided no subscriptions are active: this way privileged clients can request bigger queues.
142
143
Common events should be serialized once and distributed to all connections. This avoids serializing each event N times, and allows outgoing queues to share a single message buffer for a given event.
144
145
If practical, when a connection's outgoing queue fills up, send a "missed events" signal and discard all buffered events (and, of course, any incoming events that arrive while the buffer is full). After a "missed events" signal the client needs to assume its cache is out of date anyway. Expect a faster recovery from a temporary backlog if, when skipping events, we skip as many as we can.
146
147
h2. Logging
148
149
Print JSON-formatted log entries on stderr.
150
151
Print a log entry when a client connects.
152
153
Print a log entry when a client disconnects. Show counters for:
154
* Number of streams (UUIDs) added while connection was up
155
* Number of streams removed
156
* Number of events sent
157
* Number of bytes sent
158
* Total time spent waiting for Write() to return (or a better way to measure congestion?)
159
160 2 Tom Clegg
h2. Libraries
161
162
Websocket:
163
* https://godoc.org/golang.org/x/net/websocket
164
165
PostgreSQL:
166
* https://godoc.org/github.com/lib/pq via https://godoc.org/database/sql
167 1 Tom Clegg
* https://godoc.org/github.com/lib/pq#hdr-Notifications and https://godoc.org/github.com/lib/pq/listen_example
168
169 8 Tom Clegg
h1. Problems with old/current implementation
170 3 Tom Clegg
171 8 Tom Clegg
(Lessons to avoid re-learning next time...)
172 3 Tom Clegg
173 8 Tom Clegg
The Rails API server can function as a websocket server. Clients (notably Workbench, arv-mount, arv-ws) use it to listen for events without polling.
174 4 Tom Clegg
175 8 Tom Clegg
Problems with current implementation:
176
* Unreliable. See #9427, #8277
177
* Resource-heavy (one postgres connection per connected client, uses lots of memory)
178
* Logging is not very good
179
* Updates look like database records instead of API responses (e.g., computed fields are missing, collection manifest_text has no signatures)
180
* Offers an API for catching up on missed events after disconnecting/reconnecting, but this API (let alone the code) isn't enough to offer a "don't miss any events, don't send any events twice" guarantee. See #9388
181
182
#8460