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.

Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
@@ -1,223 +1,207 @@
1
- ### Draft Inactivity Notice
1
+ ### Draft State
2
2
 
3
- This proposed draft is only implemented by Iodine and hadn't seen external activity in a while.
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
- ## Rack Websockets
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
- * `on_open()` WILL be called once the upgrade had completed.
8
+ This document details WebSocket / EventSource connection support for Rack servers.
25
9
 
26
- * `on_message(data)` (REQUIRED) WILL be called when incoming Websocket data is received. `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).
10
+ The purpose of these specifications is:
27
11
 
28
- 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.
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
- * `on_close()` WILL be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `close` being called, etc').
14
+ Simply put, when choosing between conforming servers, the application doesn’t need to have any knowledge about the chosen server.
37
15
 
38
- * `on_open`, `on_ready`, `on_shutdown` and `on_close` shouldn't expect any arguments (`arity == 0`).
16
+ 2. To Support “native" (server-side) WebSocket and EventSource (SSE) connections and using application side callbacks.
39
17
 
40
- The following method names are reserved for the network implementation: `write`, `close` and `has_pending?`.
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
- The server **MUST** extend the Websocket Callback Object's *class* using `extend`, so that the Websocket Callback Object inherits the following methods:
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
- * `write(data)` will attempt to send the data through the websocket connection. `data` **MUST** be a String. If `data` is UTF-8 encoded, the data will be sent as text. If `data` is binary encoded it will be sent as non-text (as specified by the Websocket Protocol).
22
+ ## Rack WebSockets / EventSource
45
23
 
46
- `write` has the same delivery promise as `Socket#write` (a successful `write` does **not** mean any of the data will reach the other side).
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
- 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.
26
+ This document reserves the Rack `env` Hash keys of `rack.upgrade?` and `rack.upgrade`.
51
27
 
52
- * `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.
28
+ The historical use of `upgrade.websocket?` and `upgrade.websocket` (iodine 0.4.x) will be gradually deprecated.
53
29
 
54
- `close` shall always return `nil`.
30
+ ### The WebSocket / EventSource Callback Object
55
31
 
56
- * `has_pending?` queries the state of the server's buffer for the specific connection (i.e., if the server has any data it is waiting to send through the socket).
32
+ WebSocket and EventSource connection upgrade and handling is performed using a Callback Object.
57
33
 
58
- `has_pending?`, shall return `true` **if** the server has data waiting to be written to the socket **and** the server promises to call the `on_ready` callback once the buffer is empty and the socket is writable. Otherwise (i.e., if the server doesn't support the `on_ready` callback), `has_pending?` shall return `false`.
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
- To clarify: **implementing `has_pending?` is semi-optional**, meaning that a server may choose to always return `false`, no matter the actual state of the socket's buffer.
36
+ * `on_open()` WILL be called once the connection had been established.
61
37
 
62
- The following keywords (both as method names and instance variable names) are reserved for the internal server implementation: `_server_ws` and `conn_id`.
38
+ * `on_message(data)` WILL be called when incoming WebSocket data is received.
63
39
 
64
- * The `_server_ws` object is private and shouldn't be accessed by the client.
40
+ This callback is ignored for EventSource connections.
65
41
 
66
- * The `conn_id` object may be used as a connection ID for any functionality not specified herein.
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
- Connection `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.
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
- 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.
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
- The requirement that the server extends the class of the Websocket Callback Object (instead of the client application doing so explicitly) is designed to allow the client application to be more server agnostic.
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
- ## Upgrading
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
- * **Server**: When an upgrade request is received, the server will set the `env['upgrade.websocket?']` flag to `true`, indicating that: 1. this specific request is upgradable; and 2. this server supports this specification.
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
- * **Client**: When a client decides to upgrade a request, they will place a Websocket Callback Object (either a class or an instance) in the `env['upgrade.websocket']` Hash key.
54
+ * `on_open`, `on_drained`, `on_shutdown` and `on_close` shouldn't expect any arguments (`arity == 0`).
79
55
 
80
- * **Server**: The server will review the `env` Hash *before* sending the response. If the `env['upgrade.websocket']` was set, the server will perform the upgrade.
56
+ The following method names are reserved for the network implementation: `write`, `close`, `open?` and `pending`.
81
57
 
82
- * **Server**: 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 exists.
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
- The response status provided by the response object shall be ignored and the correct response status shall be set by the server.
60
+ * `write(data)` will attempt to send the data through the connection. `data` **MUST** be a String.
85
61
 
86
- * **Server**: Once the upgrade had completed, The server will add the required websocket/network functions to the callback handler or it's class (as aforementioned). If the callback handler is a Class object, the server will create a new instance of that class.
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
- * **Server**: The server will call the `on_open` callback.
64
+ `write` shall return `true` on success and `false` if the connection is closed.
89
65
 
90
- No other callbacks shall be called until the `on_open` callback had returned.
66
+ For WebSocket connections only (irrelevant for EventSource connections):
91
67
 
92
- 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.
68
+ * If `data` is UTF-8 encoded, the data will be sent as text.
93
69
 
94
- The `on_close` callback will **not** be called while `on_message` or `on_open` callbacks are running.
70
+ * If `data` is binary encoded it will be sent as non-text (as specified by the WebSocket Protocol).
95
71
 
96
- The `on_ready` 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.
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
- ## Iodine's Collection Extension to the Rack Websockets
76
+ `close` shall always return `nil`.
101
77
 
102
- The biggest benefit from Iodine's Collection Extension is that it allows the creation of pub/sub plugins and other similar extensions that require access to all the connected Websockets - no need for the plugin to ask "where's the list" or "add this code to `on_open`" or anything at all, truly "plug and play".
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
- This extension should be easy enough to implement using a loop or an array within each process context. These methods do **not** cross process boundaries and they are meant to help implement more advanced features (they aren't a feature as much as an "access point").
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
- Internally, this extension also allows iodine to manage connection memory and resource allocation while relieving developers from rewriting the same workflow of connection storing (`on_open do ALL_WS << self; end `) and management. Knowing that the user doesn't keep Websocket object references, allows Iodine to safely optimize it's memory use.
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
- * `Iodine::Websocket.each` (class method) will run a block of code for each connected Websocket Callback Object **belonging to the current process**. This can be called from outside of a Websocket Callback Object as well.
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
- ```ruby
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
- * `Iodine::Websocket.defer(conn_id)` Schedules a block of code to run for the specified connection at a later time, (*if* the connection is open) while preventing concurrent code from running for the same connection object.
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
- ```ruby
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
- * `#defer` (instance method) Schedules a block of code to run for the specified connection at a later time, (*if* the connection is still open) while preventing concurrent code from running for the same connection object.
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
- ```ruby
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
- * `Iodine::Websocket.count` (instance method) Returns the number of active websocket connections (including connections that are in the process of closing down) **belonging to the current process**.
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
- ```ruby
129
- write "#{Iodine::Websocket.count} clients sharing this process"
130
- ```
98
+ ---
131
99
 
132
- ## Iodine's Pub/Sub Extension to the Rack Websockets
100
+ ## Implementation Examples
133
101
 
134
- This extension separates the websocket Pub/Sub semantics from the Pub/Sub engine (i.e. Redis, MongoDB, etc'). This allows the Pub/Sub pattern to integrate with the Websocket API without the need for servers or applications to implement the Pub/Sub features.
102
+ ### Server-Client Upgrade to WebSockets / EventSource
135
103
 
136
- This allows Pub/Sub "engines" to seamlessly integrate with a server and offer Pub/Sub functionality for the specified environment, without the need for applications or servers to know anything about the Pub/Sub details or the environment.
104
+ * **Server**:
137
105
 
138
- For example, Iodine includes three Pub/Sub engines `Iodine::PubSub::CLUSTER` (the default, for single machine multi-process pub/sub), `Iodine::PubSub::SINGLE_PROCESS` (a single process pub/sub) and `Iodine::PubSub::RedisEngine` (for horizontal scaling across machine boundaries, using Redis pub/sub)...
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
- ...but it would be easy enough to write a gem that will add another engine for MongoDB Pub/Sub and that engine would be server agnostic.
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
- If the Collection Extension is implemented, than this extension should be relatively easy to implement by the server.
110
+ * **Client**:
143
111
 
144
- I don't personally believe this has a chance of being adopted by other server implementors, but it's a very powerful tool that Iodine supports.
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
- The Websocket module (i.e. `Iodine::Websocket`) includes the following singleton methods:
114
+ * **Server**:
147
115
 
148
- * `Iodine::Websocket.default_pubsub=` sets the default Pub/Sub engine.
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
- * `Iodine::Websocket.default_pubsub` gets the default Pub/Sub engine.
118
+ **If the callback handler is a Class object, the server will create a new instance of that class**.
151
119
 
152
- Websocket Callback Objects inherit the following functions:
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
- * `#subscribe` (instance method) Subscribes to a channel using a specific "engine" (or the default engine). *Returns a subscription object (success) or `nil` (error)*. Doesn't promise actual subscription, only that the subscription request was scheduled to be sent.
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
- Messages from the subscription are sent directly to the client unless an optional block is provided.
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
- If an optional block is provided, it should accept the channel and message couplet (i.e. `{|channel, message| } `) and call the websocket `write` manually.
126
+ 3. Once the upgrade had completed and the server extended the Callback Object, it will call the `on_open` callback.
159
127
 
160
- All subscriptions (including server side subscriptions) MUST be automatically canceled **by the server** when the websocket closes.
128
+ No other callbacks shall be called until the `on_open` callback had returned.
161
129
 
162
- * `#subscription?` (instance method) locates an existing subscription, if any. *Returns a subscription object (success) or `nil` (error)*.
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
- * `#unsubscribe` (instance method) cancel / stop a subscription. Safely ignores `nil` subscription objects. Returns `nil`. Performance promise is similar to `subscribe` - the event was scheduled.
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
- Notice: there's no need to call this function during the `on_close` callback, since the server will cancel all subscriptions automatically.
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
- * `#publish` (instance method) publishes a message to engine's specified channel. Returns `true` on success or `false` on failure (i.e., engine error). Doesn't promise actual publishing, only that the message was scheduled to be published.
136
+ ## Example Usage
169
137
 
170
- For example:
138
+ The following is an example WebSocket echo server implemented using this specification:
171
139
 
172
140
  ```ruby
173
- # client side subscription
174
- s = subscribe(channel: "channel 1") # => subscription ID?
175
- # client side unsubscribe
176
- unsubscribe(s)
177
-
178
- # client side subscription forcing binary encoding for Websocket protocol.
179
- subscribe(channel: "channel 1", encoding: :binary) # => subscription ID?
180
-
181
- # client side pattern subscription without saving reference
182
- subscribe(pattern: "channel [0-9]") # => subscription ID?
183
- # client side unsubscribe
184
- unsubscribe( subscription?(pattern: "channel [0-9]"))
185
-
186
- # server side anonymous block subscription
187
- s = subscribe(channel: "channel ✨") do |channel, message|
188
- puts message
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
- # server side unsubscribe
191
- unsubscribe(s)
192
-
193
- # server side persistent block subscription without saving reference
194
- block = proc {|channel, message| puts message }
195
- subscribe({pattern: "*"}, &block)
196
- # server side unsubscribe
197
- unsubscribe subscription?({pattern: "*"}, &block)
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
- Servers supporting this extension aren't required to implement any Pub/Sub engines, but they MUST implement a **bridge** between the server and Pub/Sub Engines that follows the following semantics:
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
- * `unsubscribe(channel, is_pattern)`: Unsubscribes from the channel. The `is_pattern` flag sets the type of subscription. Returns `true` / `false`.
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
- * `publish(channel, msg, is_pattern)`: Publishes to the channel (or all channels matching the pattern). Returns `true` / `false` (some engines, such as Redis, might not support pattern publishing, they should simply return `false`).
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
- Engines inherit the following message from the server's Engine class and call it when messages were received:
204
+ run App
205
+ ```
222
206
 
223
- * `distribute(channel, message, is_pattern = nil)`: If `channel` is a channel pattern rather than a channel name, the `is_pattern` should be set to `true` by the Engine. Engines that don't support pattern publishing, can simply ignore this flag.
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 on_data
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
- write buffer
50
- puts buffer.dump
51
- # close will be performed only once all the data in the write buffer
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
@@ -35,8 +35,8 @@ class WSEcho
35
35
  puts "I'm shutting down #{self}"
36
36
  end
37
37
 
38
- def on_ready
39
- puts "on_ready called for #{self}"
38
+ def on_drained
39
+ puts "on_drained called for #{self}"
40
40
  end
41
41
 
42
42
  def on_message(data)
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
- write data
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
@@ -38,8 +38,8 @@ class WSEcho
38
38
  puts "I'm shutting down #{self}"
39
39
  end
40
40
 
41
- def on_ready
42
- puts "on_ready called for #{self}"
41
+ def on_drained
42
+ puts "on_drained called for #{self}"
43
43
  end
44
44
 
45
45
  def on_message(data)
data/examples/config.ru CHANGED
@@ -1,12 +1,12 @@
1
- # This is a Websocket echo application.
1
+ # This is a WebSocket / SSE notification broadcasting application.
2
2
  #
3
- # Running this application from the command line is eacy with:
3
+ # Running this application from the command line is easy with:
4
4
  #
5
- # iodine hello.ru
5
+ # iodine
6
6
  #
7
7
  # Or, in single thread and single process:
8
8
  #
9
- # iodine -t 1 -w 1 hello.ru
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' => '32' },
22
- ['Please connect using websockets.']]
21
+ 'Content-Length' => '77' },
22
+ ['Please connect using WebSockets or SSE (send messages only using WebSockets).']]
23
23
 
24
- WS_RESPONSE = [0, {}, []].freeze
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.websocket?'.freeze])
30
- env['upgrade.websocket'.freeze] = WebsocketEcho
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 WebsocketEcho
39
+ class BroadcastClient
40
40
  # seng a message to new clients.
41
41
  def on_open
42
- write "Echo service initiated."
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
- write data
50
+ publish :broadcast, data
51
51
  end
52
52
  end
53
53