iodine 0.7.41 → 0.7.45
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +24 -0
- data/README.md +2 -2
- data/SPEC-PubSub-Draft.md +89 -47
- data/SPEC-WebSocket-Draft.md +92 -55
- data/examples/async_task.ru +92 -0
- data/ext/iodine/extconf.rb +21 -16
- data/ext/iodine/fio.c +1108 -162
- data/ext/iodine/fio.h +49 -13
- data/ext/iodine/fio_cli.c +1 -1
- data/ext/iodine/fio_tls_missing.c +8 -0
- data/ext/iodine/fio_tls_openssl.c +8 -0
- data/ext/iodine/fio_tmpfile.h +13 -1
- data/ext/iodine/fiobj_data.c +6 -4
- data/ext/iodine/fiobj_data.h +2 -1
- data/ext/iodine/fiobj_hash.c +32 -6
- data/ext/iodine/fiobj_mustache.c +9 -0
- data/ext/iodine/fiobj_numbers.c +86 -8
- data/ext/iodine/fiobj_str.c +24 -11
- data/ext/iodine/fiobject.c +1 -1
- data/ext/iodine/fiobject.h +5 -3
- data/ext/iodine/http.c +66 -10
- data/ext/iodine/http1.c +2 -1
- data/ext/iodine/http1_parser.h +1065 -103
- data/ext/iodine/http_internal.c +1 -0
- data/ext/iodine/http_internal.h +4 -2
- data/ext/iodine/iodine.c +66 -1
- data/ext/iodine/iodine.h +3 -0
- data/ext/iodine/iodine_caller.c +48 -8
- data/ext/iodine/iodine_connection.c +24 -8
- data/ext/iodine/iodine_http.c +32 -8
- data/ext/iodine/iodine_mustache.c +2 -4
- data/ext/iodine/iodine_rack_io.c +21 -0
- data/ext/iodine/iodine_tcp.c +14 -0
- data/ext/iodine/iodine_tls.c +8 -0
- data/ext/iodine/mustache_parser.h +4 -0
- data/ext/iodine/redis_engine.c +14 -11
- data/ext/iodine/websockets.c +7 -3
- data/iodine.gemspec +5 -4
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +6 -0
- metadata +15 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33f22236ecbf1c2166c056476bbefb6ab80b709cdb6cc16c0ec144926cf3fdc0
|
4
|
+
data.tar.gz: 6b0c70cc71431eb3611d34af45c80f679c0177141843a5e03b1cdb6b242d4f19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3506324c77f3de50b9dda515922c3468bbb49c63cd58831d2f7711e02b28209ba7fb5d15e076e860092be4d783cf595fd69b2483bf7ee59b873040005468161f
|
7
|
+
data.tar.gz: 7c4b0ffb76c749c11f18d66faaad5494e6ab7c675b0d887721de3c7494a7b764886bb8b7b4f5fc369e606e0f3b2e30172818dae27faa4c3cfffa73d094012cf9
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,30 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
6
6
|
|
7
7
|
## Changes:
|
8
8
|
|
9
|
+
#### Change log v.0.7.45 (2021-11-26)
|
10
|
+
|
11
|
+
**Security**: Fixes a number of issues with the HTTP parser that could have been leveraged in potential exploit attempts such as request smuggling. Credit to @dcepelik (David Čepelík).
|
12
|
+
|
13
|
+
**Compatibility**: This release adds experimental Windows support (I don't have Windows, nor Intel, I cannot test this). Credit for a lot of work by @janbiedermann (Jan Biedermann).
|
14
|
+
|
15
|
+
#### Change log v.0.7.44 (2021-02-28)
|
16
|
+
|
17
|
+
**Fix**: Fixes issue #103 where an empty String response would result in the word "null" being returned (no String object was created, which routed the NULL object to facil.io's JSON interpreter). Credit to @waghanza (Marwan Rabbâa) for exposing the issue.
|
18
|
+
|
19
|
+
**Fix**: Fixes a possible edge case race condition where the GC might free a channel name's String object before it's passed on to the user's callback.
|
20
|
+
|
21
|
+
**Fix**: Fixes a typo in the CLI documentation.
|
22
|
+
|
23
|
+
#### Change log v.0.7.43 (2020-11-03)
|
24
|
+
|
25
|
+
**Fix**: Fixes an issue where the GVL state in user-spawned threads is inaccurate. This issue only occurs if spawning a new thread and calling certain Iodine methods from a user thread.
|
26
|
+
|
27
|
+
**Fix**: validate that data passed by the user to `write` is a String object and print warnings / raise exceptions if t isn't. Credit to Vamsi Ambati for asking a question that exposed this issue.
|
28
|
+
|
29
|
+
#### Change log v.0.7.42 (2020-08-02)
|
30
|
+
|
31
|
+
**Fix**: Implement fix suggested by @Shelvak (Néstor Coppi) in issue #98.
|
32
|
+
|
9
33
|
#### Change log v.0.7.41 (2020-07-24)
|
10
34
|
|
11
35
|
**Fix**: Hot Restart failed because listening sockets were cleared away. Credit to Néstor Coppi (@Shelvak) for exposing issue #97.
|
data/README.md
CHANGED
@@ -576,8 +576,8 @@ Iodine is written in C and allows some compile-time customizations, such as:
|
|
576
576
|
These options can be used, for example, like so:
|
577
577
|
|
578
578
|
```bash
|
579
|
-
|
580
|
-
|
579
|
+
gem install iodine -- \
|
580
|
+
--with-cflags=\"-DHTTP_MAX_HEADER_LENGTH=48000 -DFIO_FORCE_MALLOC=1 -DHTTP_MAX_HEADER_COUNT=64\"
|
581
581
|
```
|
582
582
|
|
583
583
|
More possible compile time options can be found in the [facil.io documentation](http://facil.io).
|
data/SPEC-PubSub-Draft.md
CHANGED
@@ -1,66 +1,106 @@
|
|
1
|
-
# Ruby
|
1
|
+
# Ruby pub/sub API Specification Draft
|
2
|
+
|
3
|
+
### Draft State
|
4
|
+
|
5
|
+
This draft is under discussion and will be implemented by iodine starting with the 0.8.x versions.
|
6
|
+
|
7
|
+
---
|
2
8
|
|
3
9
|
## Purpose
|
4
10
|
|
5
|
-
|
11
|
+
This document details a Rack specification extension for publish/subscribe (pub/sub) modules that can extend WebSocket / EventSource Rack servers.
|
6
12
|
|
7
|
-
The purpose of this specification is
|
13
|
+
The purpose of this specification is:
|
8
14
|
|
9
|
-
|
15
|
+
1. To keep separation of concerns by avoiding inter-process-communication logic (IPC) in the application code base.
|
10
16
|
|
11
|
-
This
|
17
|
+
This is important since IPC is often implemented using pipes / sockets, which could introduce network and IO concerns into the application code.
|
12
18
|
|
13
|
-
|
19
|
+
2. To specify a common publish/subscribe (pub/sub) API for servers and pub/sub modules, allowing applications to easily switch between conforming implementations and servers.
|
14
20
|
|
15
|
-
|
21
|
+
Since pub/sub design is idiomatic to WebSocket and EventSource approaches, as well as other reactive programming techniques, it is better if applications aren't locked in to a specific server / implementation.
|
16
22
|
|
17
|
-
|
23
|
+
3. To provide common considerations and guidelines for pub/sub implementors to consider when implementing their pub/sub modules.
|
18
24
|
|
19
|
-
|
25
|
+
Some concerns are common for pub/sub implementors, such as integrating third party message brokers (Redis, RabbitMQ, Cassandra)
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
## Pub/Sub Instance Methods
|
28
|
+
|
29
|
+
Conforming pub/sub implementations **MUST** implement the following pub/sub instance methods:
|
30
|
+
|
31
|
+
* `pubsub?` **MUST** return the pub/sub API version as an integer number. Currently, set to `0` (development version).
|
26
32
|
|
27
|
-
|
33
|
+
* `subscribe(to, is_pattern = false) { |from, message| optional_block }` where:
|
28
34
|
|
29
|
-
*
|
35
|
+
* `to` is a named **channel** (or **pattern**).
|
30
36
|
|
31
|
-
|
37
|
+
The implementation **MAY** support pattern matching for named channels (`to`). The pattern matching algorithm used, if any, **SHOULD** be documented.
|
32
38
|
|
33
|
-
|
39
|
+
If the implementation does **NOT** support pattern matching and `is_pattern` is truthful, the implementation **MUST** raise and exception.
|
34
40
|
|
35
|
-
|
41
|
+
`to` **SHOULD** be a String, but implementations **MAY** support Symbols as aliases to Strings (in which case `:my_channel` is the same `'my_channel'`).
|
36
42
|
|
37
|
-
|
43
|
+
* `block` is optional and accepts (if provided) two arguments (`from` which is equal to `to` and `message` which contains the data's content).
|
38
44
|
|
39
|
-
|
45
|
+
`block` (if provided) **MUST** be called when a publication was received by the named channel.
|
46
|
+
|
47
|
+
If no `block` is provided:
|
48
|
+
|
49
|
+
* If the pub/sub instance is an extended WebSocket / EventSource (SSE) `client` object (see the WebSocket / EventSource extension draft) data should be directly sent to the `client`.
|
50
|
+
|
51
|
+
* If the pub/sub isn't linked with a `client` / connection, an exception **MUST** be raised.
|
52
|
+
|
53
|
+
If a subscription to `to` already exists for the same pub/sub instance, it should be *replaced* by the new subscription (the old subscription should be canceled / unsubscribed).
|
54
|
+
|
55
|
+
If the pub/sub instance is an extended WebSocket / EventSource (SSE) `client` object (see the WebSocket / EventSource extension draft), the subscription **MUST** be closed automatically when the connection is closed (when `on_close` is called).
|
56
|
+
|
57
|
+
Otherwise, the subscription **MUST** be closed automatically when the pub/sub object goes out of scope and is garbage collected (if this ever happens).
|
40
58
|
|
41
59
|
The `subscribe` method **MUST** return `nil` on a known failure (i.e., when the connection is already closed), or any truthful value on success.
|
42
60
|
|
43
|
-
|
61
|
+
* `unsubscribe(from, is_pattern = false)` should cancel a subscription to the `from` named channel / pattern.
|
62
|
+
|
63
|
+
* `publish(to, message, engine = nil)` where:
|
44
64
|
|
45
|
-
|
65
|
+
* `to` is a named channel, same as detailed in `subscribe`.
|
46
66
|
|
47
|
-
|
67
|
+
Implementations **MAY** support pattern based publishing. This **SHOULD** be documented as well as how patterns are detected (as opposed to named channels). Note that pattern publishing isn't supported by common backends (such as Redis) and introduces complex privacy and security concerns.
|
48
68
|
|
49
|
-
* `
|
69
|
+
* `message` a String containing the data to be published.
|
50
70
|
|
51
|
-
|
71
|
+
If `message` is NOT a String, the implementation **MAY** convert the data silently to JSON. Otherwise, the implementation **MUST** raise an exception.
|
52
72
|
|
53
|
-
|
73
|
+
`message` encoding (binary / UTZ-8) **MAY** be altered during publication, but any change **MUST** result in valid encoding.
|
54
74
|
|
55
|
-
* `engine` routes the publish method to the specified
|
75
|
+
* `engine` routes the publish method to the specified pub/sub Engine (see later on). If none is specified, the default engine should be used. If `false` is specified, the message **MUST** be forwarded to all subscribed clients on the **same process**.
|
56
76
|
|
57
|
-
The `publish` method
|
77
|
+
The `publish` method **MUST** return `true` if a publication was scheduled (not necessarily performed). If it's already known that the publication would fail, the method should return `false`.
|
58
78
|
|
59
79
|
An implementation **MUST** call the relevant PubSubEngine's `publish` method after performing any internal book keeping logic. If `engine` is `nil`, the default PubSubEngine should be called. If `engine` is `false`, the implementation **MUST** forward the published message to the actual clients (if any).
|
60
80
|
|
61
81
|
A global alias for this method (allowing it to be accessed from outside active connections) **MAY** be defined as `Rack::PubSub.publish`.
|
62
82
|
|
63
|
-
|
83
|
+
## Integrating a Pub/Sub module into a WebSocket / EventSource (SSE) `client` object
|
84
|
+
|
85
|
+
A conforming pub/sub module **MUST** be designed so that it can be integrated into WebSocket / EventSource (SSE) `client` objects be `include`-ing their class.
|
86
|
+
|
87
|
+
This **MUST** result in a behavior where subscriptions are destroyed / canceled once the `client` object state changes to "closed" - i.e., either when the `on_close` callback is called, or the first time the method `client.open?` returns `false`.
|
88
|
+
|
89
|
+
The idiomatic way to add a pub/sub module to a `client`'s class is:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
client.class.prepend MyPubSubModule unless client.pubsub?
|
93
|
+
```
|
94
|
+
|
95
|
+
The pub/sub module **MUST** expect and support this behavior.
|
96
|
+
|
97
|
+
**Note**: the use of `prepend` (rather than `include`) is chosen so that it's possible to override the `client`'s instance method of `pubsub?`.
|
98
|
+
|
99
|
+
## Connecting to External Backends (pub/sub Engines)
|
100
|
+
|
101
|
+
It is common for scaling applications to require an external message broker backend such as Redis, RabbitMQ, etc'. The following requirements set a common interface for such "engine" implementation and integration.
|
102
|
+
|
103
|
+
Pub/sub implementations **MUST** implement the following module / class methods in one of their public classes / modules (iodine implements these under `Iodine::PubSub`):
|
64
104
|
|
65
105
|
* `attach(engine)` where `engine` is a `PubSubEngine` object, as described in this specification.
|
66
106
|
|
@@ -72,9 +112,9 @@ Implementations **MUST** implement the following methods in one of their public
|
|
72
112
|
|
73
113
|
* `detach(engine)` where `engine` is a PubSubEngine object as described in this specification.
|
74
114
|
|
75
|
-
|
115
|
+
The implementation **MUST** remove the engine from the attached engine list. The opposite of `attach`.
|
76
116
|
|
77
|
-
* `default
|
117
|
+
* `default=(engine)` sets a default pub/sub engine, where `engine` is a PubSubEngine object as described in this specification.
|
78
118
|
|
79
119
|
Implementations **MUST** forward any `publish` method calls to the default pub/sub engine, unless an `engine` is specified in arguments passes to the `publish` method.
|
80
120
|
|
@@ -84,34 +124,36 @@ Implementations **MUST** implement the following methods in one of their public
|
|
84
124
|
|
85
125
|
Implementations **MUST** behave as if the engine was newly registered and (re)inform the engine of any existing subscriptions by calling engine's `subscribe` callback for each existing subscription.
|
86
126
|
|
87
|
-
|
127
|
+
A `PubSubEngine` instance object **MUST** implement the following methods:
|
88
128
|
|
89
|
-
|
129
|
+
* `subscribe(to, is_pattern = false)` this method informs the engine that a subscription to the specified channel / pattern exists for the calling the process. It **MUST ONLY** be called **once** for all existing and future subscriptions to that channel within the process.
|
90
130
|
|
91
|
-
|
131
|
+
The method **MUST** return `true` if a subscription was scheduled (or performed) or `false` if the subscription is known to fail.
|
92
132
|
|
93
|
-
|
133
|
+
This method will be called by the pub/sub implementation (for each registered engine). The engine **MAY** assume that the method would never be called directly by an application / client.
|
94
134
|
|
95
|
-
|
135
|
+
This method **MUST NOT** raise an exception.
|
96
136
|
|
97
|
-
|
137
|
+
* `unsubscribe(from, is_pattern = false)` this method informs an engine that there are no more subscriptions to the named channel / pattern for the calling process.
|
98
138
|
|
99
|
-
|
139
|
+
The method's semantics are similar to `subscribe` only is performs the opposite action.
|
100
140
|
|
101
|
-
|
141
|
+
This method **MUST NOT** raise an exception.
|
102
142
|
|
103
|
-
The method
|
143
|
+
This method will be called by the pub/sub implementation (for each registered engine). The engine **MAY** assume that the method would never be called directly by an application / client.
|
104
144
|
|
105
|
-
|
106
|
-
|
107
|
-
* `publish(channel, message)` where both `channel` and `message` are String objects.
|
145
|
+
* `publish(channel, message)` where both `channel` is either a Symbol or a String (both being equivalent) and `message` **MUST** be a String.
|
108
146
|
|
109
147
|
This method will be called by the server when a message is published using the engine.
|
110
148
|
|
111
|
-
|
149
|
+
This method will be called by the pub/sub implementation (for each registered engine).
|
150
|
+
|
151
|
+
The engine **MUST** assume that the method **MAY** be called directly by an application / client.
|
152
|
+
|
153
|
+
In order for a PubSubEngine instance object to publish messages to all subscribed clients on a particular process, it **SHOULD** call the implementation's global `publish` method with the engine set to `false`.
|
112
154
|
|
113
|
-
|
155
|
+
i.e., if the implementation's global `publish` method is in a class called `Iodine`:
|
114
156
|
|
115
157
|
```ruby
|
116
|
-
|
158
|
+
Iodine.publish channel, message, false
|
117
159
|
```
|
data/SPEC-WebSocket-Draft.md
CHANGED
@@ -5,62 +5,72 @@ This draft is also implemented by [the Agoo server](https://github.com/ohler55/a
|
|
5
5
|
---
|
6
6
|
## Purpose
|
7
7
|
|
8
|
-
This document details
|
8
|
+
This document details a Rack specification extension for WebSocket / EventSource servers.
|
9
9
|
|
10
|
-
The purpose of
|
10
|
+
The purpose of this specification is:
|
11
11
|
|
12
|
-
1. To
|
12
|
+
1. To improve application safety by phasing out the use of `hijack` and replacing it with the use of application object callbacks.
|
13
13
|
|
14
|
-
|
14
|
+
This should make it easer for applications to accept WebSocket and EventSource (SSE) connections without exposing themselves to risks and errors related to IO / network logic (such as slow client attacks, buffer flooding, etc').
|
15
15
|
|
16
|
-
2. To
|
16
|
+
2. To improve separation of concerns between servers and applications, moving the IO / network logic related to WebSocket and EventSource (SSE) back to the server.
|
17
17
|
|
18
|
-
Simply put,
|
19
|
-
|
20
|
-
3. Allow applications to use WebSocket and EventSource (SSE) on HTTP/2 servers. Note: current `hijack` practices will break network connections when attempting to implement EventSource (SSE).
|
18
|
+
Simply put, when using a server that support this extension, the application / framework doesn’t need to have any knowledge about networking, transport protocols, IO streams, polling, etc'.
|
21
19
|
|
22
20
|
## Rack WebSockets / EventSource
|
23
21
|
|
24
|
-
Servers that publish WebSocket and/or EventSource (SSE) support using the `env['rack.upgrade?']` value MUST follow the requirements set in this document.
|
22
|
+
Servers that publish WebSocket and/or EventSource (SSE) support using the `env['rack.upgrade?']` value **MUST** follow the requirements set in this document.
|
25
23
|
|
26
24
|
This document reserves the Rack `env` Hash keys of `rack.upgrade?` and `rack.upgrade`.
|
27
25
|
|
28
|
-
A conforming server MUST set `env['rack.upgrade?']` to `:websocket` for incoming WebSocket connections and `:sse` for incoming EventSource (SSE) connections.
|
26
|
+
A conforming server **MUST** set `env['rack.upgrade?']` to `:websocket` for incoming WebSocket connections and `:sse` for incoming EventSource (SSE) connections.
|
27
|
+
|
28
|
+
If a connection is not "upgradeable", a conforming server **SHOULD** set `env['rack.upgrade?']` to either `nil` or `false`. Setting the `env['rack.upgrade?']` to either `false` or `nil` should make it easier for applications to test for server support during a normal HTTP request.
|
29
|
+
|
30
|
+
If the connection is upgradeable and a client application set a value for `env['rack.upgrade']`:
|
31
|
+
|
32
|
+
* the server **MUST** use that value as a WebSocket / EventSource Callback Object unless the response status code `>= 300` (redirection / error status code).
|
33
|
+
|
34
|
+
* The response `body` **MUST NOT** be sent when switching to a Callback Object.
|
35
|
+
|
36
|
+
If a connection is **NOT** upgradeable and a client application set a value for `env['rack.upgrade']`:
|
29
37
|
|
30
|
-
|
38
|
+
* The server **SHOULD** ignore the Callback Object and process the response as if it did not exist.
|
31
39
|
|
32
|
-
|
40
|
+
* A server **MAY** use the Callback Object to allow a client to "hijack" the data stream as raw data stream. Such behavior **MUST** be documented.
|
33
41
|
|
34
42
|
### The WebSocket / EventSource Callback Object
|
35
43
|
|
36
44
|
WebSocket and EventSource connection upgrade and handling is performed using a Callback Object.
|
37
45
|
|
38
|
-
The Callback Object could be a any object which implements any of the following callbacks:
|
46
|
+
The Callback Object could be a any object which implements any (of none) of the following callbacks:
|
39
47
|
|
40
|
-
* `on_open(client)`
|
48
|
+
* `on_open(client)` **MUST** be called once the connection had been established and/or the Callback Object had been linked to the `client` object.
|
41
49
|
|
42
|
-
* `on_message(client, data)`
|
50
|
+
* `on_message(client, data)` **MUST** be called when incoming WebSocket data is received.
|
43
51
|
|
44
52
|
This callback is ignored for EventSource connections.
|
45
53
|
|
46
|
-
`data`
|
54
|
+
`data` **MUST** be a String with an encoding of UTF-8 for text messages and `binary` encoding for non-text messages (as specified by the WebSocket Protocol).
|
47
55
|
|
48
56
|
The *callback object* **MUST** assume that the `data` String will be a **recyclable buffer** and that it's content will be corrupted the moment the `on_message` callback returns.
|
49
57
|
|
50
|
-
Servers **MAY**, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional
|
58
|
+
Servers **MAY**, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional, is *not* required and might result in issues in cases where the client code is less than pristine.
|
51
59
|
|
52
|
-
* `on_drained(client)` **MAY** be called when the
|
60
|
+
* `on_drained(client)` **MAY** be called when the `client.write` buffer becomes empty. **If** `client.pending` ever returns a non-zero value (see later on), the `on_drained` callback **MUST** be called once the write buffer becomes empty.
|
53
61
|
|
54
62
|
* `on_shutdown(client)` **MAY** be called during the server's graceful shutdown process, _before_ the connection is closed and in addition to the `on_close` function (which is called _after_ the connection is closed.
|
55
63
|
|
56
|
-
* `on_close(client)` **MUST** be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `client.close` being called, etc').
|
64
|
+
* `on_close(client)` **MUST** be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `client.close` being called, etc') or the Callback Object was replaced by another Callback Object.
|
57
65
|
|
58
66
|
|
59
67
|
The server **MUST** provide the Callback Object with a `client` object, that supports the following methods (this approach promises applications could be server agnostic):
|
60
68
|
|
61
|
-
* `
|
69
|
+
* `env` **MUST** return the Rack `env` hash related to the originating HTTP request. Some changes to the `env` hash (such as removal of the IO hijacking support) **MAY** be implemented by the server.
|
62
70
|
|
63
|
-
|
71
|
+
* `write(data)` **MUST** schedule **all** the data to be sent. `data` **MUST** be a String. Servers **MAY** silently convert non-String objects to JSON if an application attempts to `write` a non-String value, otherwise servers **SHOULD** throw an exception.
|
72
|
+
|
73
|
+
A call to `write` only promises that the data is scheduled to be sent. Implementation details may differ across servers.
|
64
74
|
|
65
75
|
`write` shall return `true` on success and `false` if the connection is closed.
|
66
76
|
|
@@ -70,27 +80,52 @@ The server **MUST** provide the Callback Object with a `client` object, that sup
|
|
70
80
|
|
71
81
|
* If `data` is binary encoded it will be sent as non-text (as specified by the WebSocket Protocol).
|
72
82
|
|
73
|
-
A server **SHOULD** document
|
83
|
+
A server **SHOULD** document its concurrency model, allowing developers to know whether `write` will block or not, whether buffered IO is implemented, etc'.
|
84
|
+
|
85
|
+
For example, evented servers are encouraged to avoid blocking and return immediately, deferring the actual `write` operation for later. However, (process/thread/fiber) per-connection based servers **MAY** choose to return only after all the data was sent. Documenting these differences will allows applications to choose the model that best fits their needs and environments.
|
74
86
|
|
75
|
-
* `close` closes the connection once all the data
|
87
|
+
* `close` closes the connection once all the data scheduled using `write` was sent. If `close` is called while there is still data to be sent, `close` **SHOULD** return immediately and only take effect once the data was sent.
|
76
88
|
|
77
89
|
`close` shall always return `nil`.
|
78
90
|
|
79
|
-
* `open?`
|
91
|
+
* `open?` **MUST** return `false` **if** the connection was never open, is known to be closed or marked to be closed. Otherwise `true` **MUST** be returned.
|
80
92
|
|
81
93
|
* `pending` **MUST** return -1 if the connection is closed. Otherwise, `pending` **SHOULD** return the number of pending writes (messages in the `write` queue\*) that need to be processed before the next time the `on_drained` callback is called.
|
82
94
|
|
83
|
-
Servers **MAY** choose to always return the value `0`
|
95
|
+
Servers **MAY** choose to always return the value `0` **ONLY IF** they never call the `on_drained` callback and the connection is open.
|
96
|
+
|
97
|
+
Servers that return a positive number **MUST** call the `on_drained` callback when a call to `pending` would return the value `0`.
|
98
|
+
|
99
|
+
\*Servers that divide large messages into a number of smaller messages (implement message fragmentation) **MAY** count each fragment separately, as if the fragmentation was performed by the user and `write` was called more than once per message.
|
100
|
+
|
101
|
+
* `pubsub?` **MUST** return `false` **unless** the pub/sub extension is supported.
|
102
|
+
|
103
|
+
Pub/Sub patterns are idiomatic for WebSockets and EventSource connections but their API is out of scope for this extension.
|
84
104
|
|
85
|
-
|
105
|
+
* `class` **MUST** return the client's Class, allowing it be extended with additional features (such as Pub/Sub, etc').
|
86
106
|
|
87
|
-
|
107
|
+
**Note**: Ruby adds this method automatically to every class, no need to do a thing.
|
88
108
|
|
89
|
-
|
109
|
+
The server **MAY** support the following (optional) methods for the `client` object:
|
90
110
|
|
91
|
-
|
111
|
+
* `handler` if implemented, **MUST** return the callback object linked to the `client` object.
|
92
112
|
|
93
|
-
|
113
|
+
* `handler=` if implemented, **MUST** set a new Callback Object for `client`.
|
114
|
+
|
115
|
+
This allows applications to switch from one callback object to another (i.e., in case of credential upgrades).
|
116
|
+
|
117
|
+
Once a new Callback Object was set, the server **MUST** call the old handler's `on_close` callback and **afterwards** call the new handler's `on_open` callback.
|
118
|
+
|
119
|
+
It is **RECOMMENDED** (but not required) that this also updates the value for `env['rack.upgrade']`.
|
120
|
+
|
121
|
+
* `timeout` / `timeout=` allows applications to get / set connection timeouts dynamically and separately for each connection. Servers **SHOULD** provide a global setting for the default connection timeout. It is **RECOMMENDED** (but not required) that a global / default timeout setting be available from the command line (CLI).
|
122
|
+
|
123
|
+
* `protocol` if implemented, **MUST** return the same value that was originally set by `env['rack.upgrade?']`.
|
124
|
+
|
125
|
+
|
126
|
+
WebSocket `ping` / `pong`, timeouts and network considerations **SHOULD** be implemented by the server. It is **RECOMMENDED** (but not required) that the server send `ping`s to prevent connection timeouts and to detect network failure. Clients **SHOULD** also consider sending `ping`s to detect network errors (dropped connections).
|
127
|
+
|
128
|
+
Server settings **MAY** be provided to allow for customization and adaptation for different network environments or WebSocket extensions. It is **RECOMMENDED** that any settings be available as command line arguments and **not** incorporated into the application's logic.
|
94
129
|
|
95
130
|
---
|
96
131
|
|
@@ -100,62 +135,65 @@ Server settings **MAY** (not required) be provided to allow for customization an
|
|
100
135
|
|
101
136
|
* **Server**:
|
102
137
|
|
138
|
+
When a regular HTTP request arrives (non-upgradeable), the server will set the `env['rack.upgrade?']` flag to `false`, indicating that: 1. this specific request is NOT upgradable; and 2. the server supports this specification for either WebSocket and/or EventSource connections.
|
139
|
+
|
103
140
|
When a WebSocket upgrade request arrives, the server will set the `env['rack.upgrade?']` flag to `:websocket`, indicating that: 1. this specific request is upgradable; and 2. the server supports this specification for WebSocket connections.
|
104
141
|
|
105
|
-
When an EventSource request arrives, the server will set the `env['rack.upgrade?']` flag to `:sse`, indicating that: 1. this specific request is an EventSource request; and 2. the server supports this specification.
|
142
|
+
When an EventSource request arrives, the server will set the `env['rack.upgrade?']` flag to `:sse`, indicating that: 1. this specific request is an EventSource request; and 2. the server supports this specification for EventSource connections.
|
106
143
|
|
107
144
|
* **Client**:
|
108
145
|
|
109
|
-
|
146
|
+
If a client decides to upgrade a request, they will place an appropriate Callback Object in the `env['rack.upgrade']` Hash key.
|
110
147
|
|
111
148
|
* **Server**:
|
112
149
|
|
113
|
-
1.
|
150
|
+
1. If the application's response status indicates an error or a redirection (status code `>= 300`), the server shall ignore the Callback Object and/or remove it from the `env` Hash, ignoring the rest of the steps that follow.
|
114
151
|
|
115
|
-
2. The server will
|
152
|
+
2. The server will review the `env` Hash *before* sending the response. If the `env['rack.upgrade']` was set, the server will perform the upgrade.
|
116
153
|
|
117
|
-
|
154
|
+
3. The server will send the correct response status and headers, as well as any headers present in the response object. The server will also perform any required housekeeping, such as closing the response body, if it exists.
|
118
155
|
|
119
|
-
|
156
|
+
The response status provided by the response object shall be ignored and the correct response status shall be set by the server.
|
120
157
|
|
121
|
-
|
158
|
+
4. Once the upgrade had completed, the server will call the `on_open` callback.
|
122
159
|
|
123
160
|
No other callbacks shall be called until the `on_open` callback had returned.
|
124
161
|
|
125
|
-
WebSocket messages shall be handled by the `on_message` callback in the same order in which they arrive and the `on_message`
|
162
|
+
WebSocket messages shall be handled by the `on_message` callback in the same order in which they arrive and the `on_message` **SHOULD NOT** be executed concurrently for the same connection.
|
126
163
|
|
127
|
-
The `on_close` callback
|
164
|
+
The `on_close` callback **MUST NOT** be called while any other callback is running (`on_open`, `on_message`, `on_drained`, etc').
|
128
165
|
|
129
|
-
The `on_drained` callback
|
166
|
+
The `on_drained` callback **MAY** be called concurrently with the `on_message` callback, allowing data to be sent even while incoming data is being processed. Multi-threading considerations apply.
|
130
167
|
|
131
168
|
## Example Usage
|
132
169
|
|
133
170
|
The following is an example WebSocket echo server implemented using this specification:
|
134
171
|
|
135
172
|
```ruby
|
136
|
-
|
173
|
+
module WSConnection
|
137
174
|
def on_open(client)
|
138
|
-
puts "WebSocket connection established."
|
175
|
+
puts "WebSocket connection established (#{client.object_id})."
|
139
176
|
end
|
140
177
|
def on_message(client, data)
|
141
|
-
client.write data
|
178
|
+
client.write data # echo the data back
|
142
179
|
puts "on_drained MUST be implemented if #{ pending } != 0."
|
143
180
|
end
|
144
181
|
def on_drained(client)
|
145
|
-
puts "
|
182
|
+
puts "If this line prints out, on_drained is supported by the server."
|
146
183
|
end
|
147
184
|
def on_shutdown(client)
|
148
185
|
client.write "The server is going away. Goodbye."
|
149
186
|
end
|
150
187
|
def on_close(client)
|
151
|
-
puts "WebSocket connection closed."
|
188
|
+
puts "WebSocket connection closed (#{client.object_id})."
|
152
189
|
end
|
190
|
+
extend self
|
153
191
|
end
|
154
192
|
|
155
193
|
module App
|
156
194
|
def self.call(env)
|
157
195
|
if(env['rack.upgrade?'.freeze] == :websocket)
|
158
|
-
env['rack.upgrade'.freeze] = WSConnection
|
196
|
+
env['rack.upgrade'.freeze] = WSConnection
|
159
197
|
return [0, {}, []]
|
160
198
|
end
|
161
199
|
return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]]
|
@@ -165,23 +203,22 @@ end
|
|
165
203
|
run App
|
166
204
|
```
|
167
205
|
|
168
|
-
The following example uses Push notifications for both WebSocket and SSE connections. The Pub/Sub API isn't part of this specification
|
206
|
+
The following example uses Push notifications for both WebSocket and SSE connections. The Pub/Sub API is subject to a separate Pub/Sub API extension and isn't part of this specification (it is, however, supported by iodine):
|
169
207
|
|
170
208
|
```ruby
|
171
|
-
|
172
|
-
def initialize(nickname)
|
173
|
-
@nickname = nickname
|
174
|
-
end
|
209
|
+
module Chat
|
175
210
|
def on_open(client)
|
211
|
+
client.class.prepend MyPubSubModule unless client.pubsub?
|
176
212
|
client.subscribe "chat"
|
177
|
-
client.publish "chat", "#{
|
213
|
+
client.publish "chat", "#{env[:nickname]} joined the chat."
|
178
214
|
end
|
179
215
|
def on_message(client, data)
|
180
|
-
client.publish "chat", "#{
|
216
|
+
client.publish "chat", "#{env[:nickname]}: #{data}"
|
181
217
|
end
|
182
218
|
def on_close(client)
|
183
|
-
client.publish "chat", "#{
|
219
|
+
client.publish "chat", "#{env[:nickname]}: left the chat."
|
184
220
|
end
|
221
|
+
extend self
|
185
222
|
end
|
186
223
|
|
187
224
|
module App
|
@@ -189,7 +226,7 @@ module App
|
|
189
226
|
if(env['rack.upgrade?'.freeze])
|
190
227
|
nickname = env['PATH_INFO'][1..-1]
|
191
228
|
nickname = "Someone" if nickname == "".freeze
|
192
|
-
env[
|
229
|
+
env[:nickname] = nickname
|
193
230
|
return [0, {}, []]
|
194
231
|
end
|
195
232
|
return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]]
|