iodine 0.4.19 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +22 -0
- data/LIMITS.md +19 -9
- data/README.md +92 -77
- data/SPEC-PubSub-Draft.md +113 -0
- data/SPEC-Websocket-Draft.md +127 -143
- data/bin/http-hello +0 -1
- data/bin/raw-rbhttp +1 -1
- data/bin/raw_broadcast +8 -10
- data/bin/updated api +2 -2
- data/bin/ws-broadcast +2 -4
- data/bin/ws-echo +2 -2
- data/examples/config.ru +13 -13
- data/examples/echo.ru +5 -6
- data/examples/hello.ru +2 -3
- data/examples/info.md +316 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/redis.ru +9 -9
- data/examples/shootout.ru +45 -11
- data/ext/iodine/defer.c +194 -297
- data/ext/iodine/defer.h +61 -53
- data/ext/iodine/evio.c +0 -260
- data/ext/iodine/evio.h +50 -22
- data/ext/iodine/evio_callbacks.c +26 -0
- data/ext/iodine/evio_epoll.c +251 -0
- data/ext/iodine/evio_kqueue.c +193 -0
- data/ext/iodine/extconf.rb +1 -1
- data/ext/iodine/facil.c +1420 -542
- data/ext/iodine/facil.h +151 -64
- data/ext/iodine/fio_ary.h +418 -0
- data/ext/iodine/{base64.c → fio_base64.c} +33 -24
- data/ext/iodine/{base64.h → fio_base64.h} +6 -7
- data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
- data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
- data/ext/iodine/fio_hashmap.h +759 -0
- data/ext/iodine/fio_json_parser.h +651 -0
- data/ext/iodine/fio_llist.h +257 -0
- data/ext/iodine/fio_mem.c +672 -0
- data/ext/iodine/fio_mem.h +140 -0
- data/ext/iodine/fio_random.c +248 -0
- data/ext/iodine/{random.h → fio_random.h} +11 -14
- data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
- data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
- data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
- data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
- data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
- data/ext/iodine/fio_siphash.h +18 -0
- data/ext/iodine/fio_tmpfile.h +38 -0
- data/ext/iodine/fiobj.h +24 -7
- data/ext/iodine/fiobj4sock.h +23 -0
- data/ext/iodine/fiobj_ary.c +143 -226
- data/ext/iodine/fiobj_ary.h +17 -16
- data/ext/iodine/fiobj_data.c +1160 -0
- data/ext/iodine/fiobj_data.h +164 -0
- data/ext/iodine/fiobj_hash.c +298 -406
- data/ext/iodine/fiobj_hash.h +101 -54
- data/ext/iodine/fiobj_json.c +478 -601
- data/ext/iodine/fiobj_json.h +34 -9
- data/ext/iodine/fiobj_numbers.c +383 -51
- data/ext/iodine/fiobj_numbers.h +87 -11
- data/ext/iodine/fiobj_str.c +423 -184
- data/ext/iodine/fiobj_str.h +81 -32
- data/ext/iodine/fiobject.c +273 -522
- data/ext/iodine/fiobject.h +477 -112
- data/ext/iodine/http.c +2243 -83
- data/ext/iodine/http.h +842 -121
- data/ext/iodine/http1.c +810 -385
- data/ext/iodine/http1.h +16 -39
- data/ext/iodine/http1_parser.c +146 -74
- data/ext/iodine/http1_parser.h +15 -4
- data/ext/iodine/http_internal.c +1258 -0
- data/ext/iodine/http_internal.h +226 -0
- data/ext/iodine/http_mime_parser.h +341 -0
- data/ext/iodine/iodine.c +86 -68
- data/ext/iodine/iodine.h +26 -11
- data/ext/iodine/iodine_helpers.c +8 -7
- data/ext/iodine/iodine_http.c +487 -324
- data/ext/iodine/iodine_json.c +304 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_protocol.c +107 -45
- data/ext/iodine/iodine_pubsub.c +526 -225
- data/ext/iodine/iodine_pubsub.h +10 -0
- data/ext/iodine/iodine_websockets.c +268 -510
- data/ext/iodine/iodine_websockets.h +2 -4
- data/ext/iodine/pubsub.c +726 -432
- data/ext/iodine/pubsub.h +85 -103
- data/ext/iodine/rb-call.c +4 -4
- data/ext/iodine/rb-defer.c +46 -22
- data/ext/iodine/rb-fiobj2rb.h +117 -0
- data/ext/iodine/rb-rack-io.c +73 -238
- data/ext/iodine/rb-rack-io.h +2 -2
- data/ext/iodine/rb-registry.c +35 -93
- data/ext/iodine/rb-registry.h +1 -0
- data/ext/iodine/redis_engine.c +742 -304
- data/ext/iodine/redis_engine.h +42 -39
- data/ext/iodine/resp_parser.h +311 -0
- data/ext/iodine/sock.c +627 -490
- data/ext/iodine/sock.h +345 -297
- data/ext/iodine/spnlock.inc +15 -4
- data/ext/iodine/websocket_parser.h +16 -20
- data/ext/iodine/websockets.c +188 -257
- data/ext/iodine/websockets.h +24 -133
- data/lib/iodine.rb +52 -7
- data/lib/iodine/cli.rb +6 -24
- data/lib/iodine/json.rb +40 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/iodine/websocket.rb +5 -3
- data/lib/rack/handler/iodine.rb +58 -13
- metadata +38 -48
- data/bin/ws-shootout +0 -107
- data/examples/broadcast.ru +0 -56
- data/ext/iodine/bscrypt-common.h +0 -116
- data/ext/iodine/bscrypt.h +0 -49
- data/ext/iodine/fio2resp.c +0 -60
- data/ext/iodine/fio2resp.h +0 -51
- data/ext/iodine/fio_dict.c +0 -446
- data/ext/iodine/fio_dict.h +0 -99
- data/ext/iodine/fio_hash_table.h +0 -370
- data/ext/iodine/fio_list.h +0 -111
- data/ext/iodine/fiobj_internal.h +0 -280
- data/ext/iodine/fiobj_primitives.c +0 -131
- data/ext/iodine/fiobj_primitives.h +0 -55
- data/ext/iodine/fiobj_sym.c +0 -135
- data/ext/iodine/fiobj_sym.h +0 -60
- data/ext/iodine/hex.c +0 -124
- data/ext/iodine/hex.h +0 -70
- data/ext/iodine/http1_request.c +0 -81
- data/ext/iodine/http1_request.h +0 -58
- data/ext/iodine/http1_response.c +0 -417
- data/ext/iodine/http1_response.h +0 -95
- data/ext/iodine/http_request.c +0 -111
- data/ext/iodine/http_request.h +0 -102
- data/ext/iodine/http_response.c +0 -1703
- data/ext/iodine/http_response.h +0 -250
- data/ext/iodine/misc.c +0 -182
- data/ext/iodine/misc.h +0 -74
- data/ext/iodine/random.c +0 -208
- data/ext/iodine/redis_connection.c +0 -278
- data/ext/iodine/redis_connection.h +0 -86
- data/ext/iodine/resp.c +0 -842
- data/ext/iodine/resp.h +0 -261
- data/ext/iodine/siphash.c +0 -154
- data/ext/iodine/siphash.h +0 -22
- data/ext/iodine/xor-crypt.c +0 -193
- data/ext/iodine/xor-crypt.h +0 -107
data/SPEC-Websocket-Draft.md
CHANGED
@@ -1,223 +1,207 @@
|
|
1
|
-
### Draft
|
1
|
+
### Draft State
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Even though a number of other development teams (such as the teams for the Puma and Passenger server) mentioned that they plan to implements this draft, Iodine seems to be the only server currently implementing this draft and it is unlikely that this initiative will grow to become a community convention.
|
6
|
-
|
7
|
-
I still believe it's important to separate the Websocket server from the Websocket API used by application developers and frameworks, much like Rack did for HTTP. I hope that in the future a community convention for this separation of concerns can be achieved.
|
3
|
+
I am currently discussing a variation of this draft to be implemented by [the Agoo server](https://github.com/ohler55/agoo).
|
8
4
|
|
9
5
|
---
|
10
|
-
##
|
11
|
-
|
12
|
-
This is the proposed Websocket support extension for Rack servers.
|
13
|
-
|
14
|
-
Servers that publish Websocket support using the `env['upgrade.websocket?']` value are assume by users to follow the requirements set in this document and thus should follow the requirements set in herein.
|
15
|
-
|
16
|
-
This document reserves the Rack `env` Hash keys of `upgrade.websocket?` and `upgrade.websocket`.
|
17
|
-
|
18
|
-
## The Websocket Callback Object
|
19
|
-
|
20
|
-
Websocket connection upgrade and handling is performed using a Websocket Callback Object.
|
21
|
-
|
22
|
-
The Websocket Callback Object should be a class (or an instance of such class) who's instances implement any of the following callbacks:
|
6
|
+
## Purpose
|
23
7
|
|
24
|
-
|
8
|
+
This document details WebSocket / EventSource connection support for Rack servers.
|
25
9
|
|
26
|
-
|
10
|
+
The purpose of these specifications is:
|
27
11
|
|
28
|
-
|
29
|
-
|
30
|
-
Servers MAY, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional and it is *not* required.
|
31
|
-
|
32
|
-
* `on_ready()` **MAY** be called when the state of the out-going socket buffer changes from full to not full (data can be sent to the socket). **If** `has_pending?` returns `true`, the `on_ready` callback **MUST** be called once the buffer state changes.
|
33
|
-
|
34
|
-
* `on_shutdown()` 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.
|
12
|
+
1. To allow separation of concerns between the transport layer and the application, thereby allowing the application to be server agnostic.
|
35
13
|
|
36
|
-
|
14
|
+
Simply put, when choosing between conforming servers, the application doesn’t need to have any knowledge about the chosen server.
|
37
15
|
|
38
|
-
|
16
|
+
2. To Support “native" (server-side) WebSocket and EventSource (SSE) connections and using application side callbacks.
|
39
17
|
|
40
|
-
|
18
|
+
Simply put, to make it easy for applications to accept WebSocket and EventSource (SSE) connections from WebSocket and EventSource clients (commonly browsers).
|
41
19
|
|
42
|
-
|
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).
|
43
21
|
|
44
|
-
|
22
|
+
## Rack WebSockets / EventSource
|
45
23
|
|
46
|
-
|
47
|
-
|
48
|
-
`write` shall return `true` on success and `false` if the websocket is closed.
|
24
|
+
Servers that publish WebSocket and/or EventSource (SSE) support using the `env['rack.upgrade?']` value MUST follow the requirements set in this document.
|
49
25
|
|
50
|
-
|
26
|
+
This document reserves the Rack `env` Hash keys of `rack.upgrade?` and `rack.upgrade`.
|
51
27
|
|
52
|
-
|
28
|
+
The historical use of `upgrade.websocket?` and `upgrade.websocket` (iodine 0.4.x) will be gradually deprecated.
|
53
29
|
|
54
|
-
|
30
|
+
### The WebSocket / EventSource Callback Object
|
55
31
|
|
56
|
-
|
32
|
+
WebSocket and EventSource connection upgrade and handling is performed using a Callback Object.
|
57
33
|
|
58
|
-
|
34
|
+
The Callback Object should be a class (or an instance of such class) where **instances** implement any of the following callbacks:
|
59
35
|
|
60
|
-
|
36
|
+
* `on_open()` WILL be called once the connection had been established.
|
61
37
|
|
62
|
-
|
38
|
+
* `on_message(data)` WILL be called when incoming WebSocket data is received.
|
63
39
|
|
64
|
-
|
40
|
+
This callback is ignored for EventSource connections.
|
65
41
|
|
66
|
-
|
42
|
+
`data` will 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).
|
67
43
|
|
68
|
-
|
44
|
+
The *client* **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.
|
69
45
|
|
70
|
-
|
46
|
+
Servers **MAY**, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional and it is *not* required.
|
71
47
|
|
72
|
-
|
48
|
+
* `on_drained()` **MAY** be called when the the `write` buffer becomes empty. **If** `pending` returns a non-zero value, the `on_drained` callback **MUST** be called once the write buffer becomes empty.
|
73
49
|
|
74
|
-
|
50
|
+
* `on_shutdown()` **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.
|
75
51
|
|
76
|
-
* **
|
52
|
+
* `on_close()` **MUST** be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `close` being called, etc').
|
77
53
|
|
78
|
-
*
|
54
|
+
* `on_open`, `on_drained`, `on_shutdown` and `on_close` shouldn't expect any arguments (`arity == 0`).
|
79
55
|
|
80
|
-
|
56
|
+
The following method names are reserved for the network implementation: `write`, `close`, `open?` and `pending`.
|
81
57
|
|
82
|
-
|
58
|
+
The server **MUST** extend the Callback Object's *class* using `extend`, so the Callback Object **inherits** the following methods (this approach promises applications could be server agnostic\*):
|
83
59
|
|
84
|
-
|
60
|
+
* `write(data)` will attempt to send the data through the connection. `data` **MUST** be a String.
|
85
61
|
|
86
|
-
|
62
|
+
`write` has the same delivery promise as `Socket#write` (a successful `write` does **not** mean any of the data will reach the other side).
|
87
63
|
|
88
|
-
|
64
|
+
`write` shall return `true` on success and `false` if the connection is closed.
|
89
65
|
|
90
|
-
|
66
|
+
For WebSocket connections only (irrelevant for EventSource connections):
|
91
67
|
|
92
|
-
|
68
|
+
* If `data` is UTF-8 encoded, the data will be sent as text.
|
93
69
|
|
94
|
-
|
70
|
+
* If `data` is binary encoded it will be sent as non-text (as specified by the WebSocket Protocol).
|
95
71
|
|
96
|
-
|
72
|
+
A server **SHOULD** document whether `write` will block or return immediately. It is **RECOMMENDED** that servers implement buffered IO, allowing `write` to return immediately when resources allow and block (or, possibly, disconnect) when the IO buffer is full.
|
97
73
|
|
98
|
-
|
74
|
+
* `close` closes the connection once all the data in the outgoing queue was sent. If `close` is called while there is still data to be sent, `close` will only take effect once the data was sent.
|
99
75
|
|
100
|
-
|
76
|
+
`close` shall always return `nil`.
|
101
77
|
|
102
|
-
|
78
|
+
* `open?` returns the state of the connection. Servers **MUST** set the method to return `true` if the connection is open and `false` if the connection is closed or marked to be closed.
|
103
79
|
|
104
|
-
|
80
|
+
* `pending` **MUST** return -1 if the connection is closed or the number of pending writes (calls to `write`) that need to be processed before the next time the `on_drained` callback is called\*.
|
105
81
|
|
106
|
-
|
82
|
+
Servers **MAY** choose to always return the value `0` if they never call the `on_drained` callback and the connection is open.
|
107
83
|
|
108
|
-
|
84
|
+
Servers that return a positive number MUST call the `on_drained` callback when a call to `pending` would return the value `0`.
|
109
85
|
|
110
|
-
|
111
|
-
Iodine::Websocket.each {|ws| ws.write "You're connected to PID #{Process.pid}" }
|
112
|
-
```
|
86
|
+
\*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.
|
113
87
|
|
114
|
-
|
88
|
+
The following keyword(s) (both as method names and instance variable names) is reserved for the internal server implementations: `_sock`, `_cid`.
|
115
89
|
|
116
|
-
|
117
|
-
Iodine::Websocket.defer(self.conn_id) {|ws| ws.write "still open" }
|
118
|
-
```
|
90
|
+
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 detect network failure.
|
119
91
|
|
120
|
-
|
92
|
+
Server settings **MAY** (not required) 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.
|
121
93
|
|
122
|
-
|
123
|
-
defer { write "still open" }
|
124
|
-
```
|
94
|
+
\* The requirement that the server extends the class of the Callback Object (instead of the client application doing so explicitly) is designed to allow the client application to be server agnostic
|
125
95
|
|
126
|
-
|
96
|
+
To clarify, an implicit `extend` doesn't require a namespace, while an explicit `extend` does. By avoiding the requirement to explicitly extend the callback object, the application can be namespace agnostic.
|
127
97
|
|
128
|
-
|
129
|
-
write "#{Iodine::Websocket.count} clients sharing this process"
|
130
|
-
```
|
98
|
+
---
|
131
99
|
|
132
|
-
##
|
100
|
+
## Implementation Examples
|
133
101
|
|
134
|
-
|
102
|
+
### Server-Client Upgrade to WebSockets / EventSource
|
135
103
|
|
136
|
-
|
104
|
+
* **Server**:
|
137
105
|
|
138
|
-
|
106
|
+
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.
|
139
107
|
|
140
|
-
|
108
|
+
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.
|
141
109
|
|
142
|
-
|
110
|
+
* **Client**:
|
143
111
|
|
144
|
-
|
112
|
+
When a client decides to upgrade a request, they will place an appropriate Callback Object (either a class or an instance) in the `env['rack.upgrade']` Hash key.
|
145
113
|
|
146
|
-
|
114
|
+
* **Server**:
|
147
115
|
|
148
|
-
* `
|
116
|
+
1. The server will review the `env` Hash *before* sending the response. If the `env['rack.upgrade']` was set, the server will perform the upgrade.
|
149
117
|
|
150
|
-
|
118
|
+
**If the callback handler is a Class object, the server will create a new instance of that class**.
|
151
119
|
|
152
|
-
|
120
|
+
2. 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.
|
153
121
|
|
154
|
-
|
122
|
+
The response status provided by the response object shall be ignored and the correct response status shall be set by the server.
|
155
123
|
|
156
|
-
|
124
|
+
If the application's response status indicates an error or a redirection (status code >= 300), the server shall ignore the Callback Object.
|
157
125
|
|
158
|
-
|
126
|
+
3. Once the upgrade had completed and the server extended the Callback Object, it will call the `on_open` callback.
|
159
127
|
|
160
|
-
|
128
|
+
No other callbacks shall be called until the `on_open` callback had returned.
|
161
129
|
|
162
|
-
|
130
|
+
WebSocket messages shall be handled by the `on_message` callback in the same order in which they arrive and the `on_message` will **not** be executed concurrently for the same connection.
|
163
131
|
|
164
|
-
|
132
|
+
The `on_close` callback will **not** be called while any other callback is running (`on_open`, `on_message`, `on_drained`, etc').
|
165
133
|
|
166
|
-
|
134
|
+
The `on_drained` callback might be called concurrently with the `on_message` callback, allowing data to be sent even while other data is being processed. Multi-threading considerations may apply.
|
167
135
|
|
168
|
-
|
136
|
+
## Example Usage
|
169
137
|
|
170
|
-
|
138
|
+
The following is an example WebSocket echo server implemented using this specification:
|
171
139
|
|
172
140
|
```ruby
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
141
|
+
class WSConnection
|
142
|
+
def on_open
|
143
|
+
puts "WebSocket connection established."
|
144
|
+
end
|
145
|
+
def on_message(data)
|
146
|
+
write data
|
147
|
+
puts "on_drained MUST be implemented if #{ pending } != 0."
|
148
|
+
end
|
149
|
+
def on_drained
|
150
|
+
puts "Yap,on_drained is implemented."
|
151
|
+
end
|
152
|
+
def on_shutdown
|
153
|
+
write "The server is going away. Goodbye."
|
154
|
+
end
|
155
|
+
def on_close
|
156
|
+
puts "WebSocket connection closed."
|
157
|
+
end
|
189
158
|
end
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
# server side anonymous block subscription requires reference...
|
200
|
-
subscribe(pattern: "channel 5", force: text) do |channel, message|
|
201
|
-
puts message
|
202
|
-
end
|
203
|
-
# It's impossible to locate an anonymous block subscriptions!
|
204
|
-
subscription? (pattern: "channel 5", force: text) do |channel, message|
|
205
|
-
puts message
|
159
|
+
|
160
|
+
module App
|
161
|
+
def self.call(env)
|
162
|
+
if(env['rack.upgrade?'.freeze] == :websocket)
|
163
|
+
env['rack.upgrade'.freeze] = WSConnection.new
|
164
|
+
return [0, {}, []]
|
165
|
+
end
|
166
|
+
return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]]
|
167
|
+
end
|
206
168
|
end
|
207
|
-
# => nil # ! can't locate
|
208
169
|
|
170
|
+
run App
|
209
171
|
```
|
210
172
|
|
211
|
-
|
212
|
-
|
213
|
-
Engines MUST inherit from a server specific Engine class and implement the following three methods that will be called by the server when required (servers might implement an internal distribution layer and call these functions at their discretion):
|
214
|
-
|
215
|
-
* `subscribe(channel, is_pattern)`: Subscribes to the channel. The `is_pattern` flag sets the type of subscription. Returns `true` / `false`.
|
173
|
+
The following is uses Push notifications for both WebSocket and SSE connections. The Pub/Sub API isn't part of this specification:
|
216
174
|
|
217
|
-
|
175
|
+
```ruby
|
176
|
+
class Chat
|
177
|
+
def initialize(nickname)
|
178
|
+
@nickname = nickname
|
179
|
+
end
|
180
|
+
def on_open
|
181
|
+
subscribe "chat"
|
182
|
+
publish "chat", "#{@nickname} joined the chat."
|
183
|
+
end
|
184
|
+
def on_message(data)
|
185
|
+
publish "chat", "#{@nickname}: #{data}"
|
186
|
+
end
|
187
|
+
def on_close
|
188
|
+
publish "chat", "#{@nickname}: left the chat."
|
189
|
+
end
|
190
|
+
end
|
218
191
|
|
219
|
-
|
192
|
+
module App
|
193
|
+
def self.call(env)
|
194
|
+
if(env['rack.upgrade?'.freeze])
|
195
|
+
nickname = env['PATH_INFO'][1..-1]
|
196
|
+
nickname = "Someone" if nickname == "".freeze
|
197
|
+
env['rack.upgrade'.freeze] = Chat.new(nickname)
|
198
|
+
return [0, {}, []]
|
199
|
+
end
|
200
|
+
return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]]
|
201
|
+
end
|
202
|
+
end
|
220
203
|
|
221
|
-
|
204
|
+
run App
|
205
|
+
```
|
222
206
|
|
223
|
-
|
207
|
+
Note that SSE connections will only be able to receive messages (the `on_message` callback is never called).
|
data/bin/http-hello
CHANGED
@@ -19,7 +19,6 @@ Iodine::Rack.public = '~/Documents/Scratch'
|
|
19
19
|
count = 2
|
20
20
|
Iodine::Rack.app = proc { |_env| [200, { 'Content-Length'.freeze => '12'.freeze }, ['He11o World!'.freeze]] }
|
21
21
|
puts Iodine::Rack.address
|
22
|
-
Iodine::HTTP.listen app: Iodine::Rack.app, port: "3030", public: '~/Documents/Scratch'
|
23
22
|
Iodine.start
|
24
23
|
|
25
24
|
# puts env.to_a.map { |pair| pair.join(': ') } .join("\n").to_s;
|
data/bin/raw-rbhttp
CHANGED
@@ -18,7 +18,7 @@ class HttpProtocol
|
|
18
18
|
@timeout = 10
|
19
19
|
# `on_message` is an optional alternative to the `on_data` callback.
|
20
20
|
# `on_message` has a 1Kb buffer that recycles itself for memory optimization.
|
21
|
-
def
|
21
|
+
def on_message _
|
22
22
|
# writing will never block and will use a buffer written in C when needed.
|
23
23
|
write "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nKeep-Alive: timeout=1\r\nContent-Length: 12\r\n\r\nHello World!"
|
24
24
|
end
|
data/bin/raw_broadcast
CHANGED
@@ -42,20 +42,18 @@ class EchoProtocol
|
|
42
42
|
puts "We have 1 timer, and #{Iodine.count} connections."
|
43
43
|
end
|
44
44
|
|
45
|
+
def on_open
|
46
|
+
subscribe(channel: :chat) {|ch, msg| write msg }
|
47
|
+
subscribe channel: :quite
|
48
|
+
end
|
49
|
+
|
45
50
|
# `on_message` is an optional alternative to the `on_data` callback.
|
46
51
|
# `on_message` has a 1Kb buffer that recycles itself for memory optimization.
|
47
52
|
def on_message(buffer)
|
48
53
|
# writing will never block and will use a buffer written in C when needed.
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# was sent. use `force_close` to close early.
|
53
|
-
close if buffer =~ /^bye[\r\n]/i
|
54
|
-
upgrade ShoooProtocol if buffer =~ /^shoo[\r\n]/i
|
55
|
-
# use buffer.dup to save the data from being recycled once we return.
|
56
|
-
data = "Someone said: #{buffer}"
|
57
|
-
# run asynchronous tasks with ease
|
58
|
-
each { |c| puts c; c.write data }
|
54
|
+
publish channel: :chat, message: "Someone said: #{buffer}"
|
55
|
+
switch_protocol ShoooProtocol if buffer =~ /^shoo[\r\n]/i
|
56
|
+
write "You speak!\n" if unsubscribe(subscribed?(channel: :quite))
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
data/bin/updated api
CHANGED
data/bin/ws-broadcast
CHANGED
@@ -25,6 +25,7 @@ class WSEcho
|
|
25
25
|
|
26
26
|
def on_open
|
27
27
|
puts 'We have a websocket connection'
|
28
|
+
subscribe channel: :all
|
28
29
|
end
|
29
30
|
|
30
31
|
def on_close
|
@@ -37,10 +38,7 @@ class WSEcho
|
|
37
38
|
|
38
39
|
def on_message(data)
|
39
40
|
puts "got message: #{data}"
|
40
|
-
|
41
|
-
data_cp = "Broadcast: #{data}"
|
42
|
-
# puts "broadcasting #{data_cp.bytesize} bytes"
|
43
|
-
each { |h| h.write data_cp }
|
41
|
+
publish channe: :all, message: data
|
44
42
|
end
|
45
43
|
|
46
44
|
def self.pr_raise
|
data/bin/ws-echo
CHANGED
data/examples/config.ru
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
# This is a
|
1
|
+
# This is a WebSocket / SSE notification broadcasting application.
|
2
2
|
#
|
3
|
-
# Running this application from the command line is
|
3
|
+
# Running this application from the command line is easy with:
|
4
4
|
#
|
5
|
-
# iodine
|
5
|
+
# iodine
|
6
6
|
#
|
7
7
|
# Or, in single thread and single process:
|
8
8
|
#
|
9
|
-
# iodine -t 1 -w 1
|
9
|
+
# iodine -t 1 -w 1
|
10
10
|
#
|
11
11
|
# Benchmark with `ab` or `wrk` (a 5 seconds benchmark with 2000 concurrent clients):
|
12
12
|
#
|
@@ -18,16 +18,16 @@
|
|
18
18
|
module MyHTTPRouter
|
19
19
|
# This is the HTTP response object according to the Rack specification.
|
20
20
|
HTTP_RESPONSE = [200, { 'Content-Type' => 'text/html',
|
21
|
-
'Content-Length' => '
|
22
|
-
['Please connect using
|
21
|
+
'Content-Length' => '77' },
|
22
|
+
['Please connect using WebSockets or SSE (send messages only using WebSockets).']]
|
23
23
|
|
24
|
-
|
24
|
+
WS_RESPONSE = [0, {}, []].freeze
|
25
25
|
|
26
26
|
# this is function will be called by the Rack server (iodine) for every request.
|
27
27
|
def self.call env
|
28
|
-
# check if this is an upgrade request.
|
29
|
-
if(env['upgrade
|
30
|
-
env['upgrade
|
28
|
+
# check if this is an upgrade request (WebsSocket / SSE).
|
29
|
+
if(env['rack.upgrade?'.freeze])
|
30
|
+
env['rack.upgrade'.freeze] = BroadcastClient
|
31
31
|
return WS_RESPONSE
|
32
32
|
end
|
33
33
|
# simply return the RESPONSE object, no matter what request was received.
|
@@ -36,10 +36,10 @@ module MyHTTPRouter
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# A simple Websocket Callback Object.
|
39
|
-
class
|
39
|
+
class BroadcastClient
|
40
40
|
# seng a message to new clients.
|
41
41
|
def on_open
|
42
|
-
|
42
|
+
subscribe :broadcast
|
43
43
|
end
|
44
44
|
# send a message, letting the client know the server is suggunt down.
|
45
45
|
def on_shutdown
|
@@ -47,7 +47,7 @@ class WebsocketEcho
|
|
47
47
|
end
|
48
48
|
# perform the echo
|
49
49
|
def on_message data
|
50
|
-
|
50
|
+
publish :broadcast, data
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|