isomorfeus-iodine 0.7.44

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.gitignore +20 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +32 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1038 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +44 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/config.ru +56 -0
  25. data/examples/echo.ru +59 -0
  26. data/examples/hello.ru +29 -0
  27. data/examples/pubsub_engine.ru +81 -0
  28. data/examples/redis.ru +70 -0
  29. data/examples/shootout.ru +73 -0
  30. data/examples/sub-protocols.ru +90 -0
  31. data/examples/tcp_client.rb +66 -0
  32. data/examples/x-sendfile.ru +14 -0
  33. data/exe/iodine +277 -0
  34. data/ext/iodine/extconf.rb +109 -0
  35. data/ext/iodine/fio.c +11985 -0
  36. data/ext/iodine/fio.h +6373 -0
  37. data/ext/iodine/fio_cli.c +431 -0
  38. data/ext/iodine/fio_cli.h +189 -0
  39. data/ext/iodine/fio_json_parser.h +687 -0
  40. data/ext/iodine/fio_siphash.c +157 -0
  41. data/ext/iodine/fio_siphash.h +37 -0
  42. data/ext/iodine/fio_tls.h +129 -0
  43. data/ext/iodine/fio_tls_missing.c +649 -0
  44. data/ext/iodine/fio_tls_openssl.c +1056 -0
  45. data/ext/iodine/fio_tmpfile.h +50 -0
  46. data/ext/iodine/fiobj.h +44 -0
  47. data/ext/iodine/fiobj4fio.h +21 -0
  48. data/ext/iodine/fiobj_ary.c +333 -0
  49. data/ext/iodine/fiobj_ary.h +139 -0
  50. data/ext/iodine/fiobj_data.c +1185 -0
  51. data/ext/iodine/fiobj_data.h +167 -0
  52. data/ext/iodine/fiobj_hash.c +409 -0
  53. data/ext/iodine/fiobj_hash.h +176 -0
  54. data/ext/iodine/fiobj_json.c +622 -0
  55. data/ext/iodine/fiobj_json.h +68 -0
  56. data/ext/iodine/fiobj_mem.h +71 -0
  57. data/ext/iodine/fiobj_mustache.c +317 -0
  58. data/ext/iodine/fiobj_mustache.h +62 -0
  59. data/ext/iodine/fiobj_numbers.c +344 -0
  60. data/ext/iodine/fiobj_numbers.h +127 -0
  61. data/ext/iodine/fiobj_str.c +433 -0
  62. data/ext/iodine/fiobj_str.h +172 -0
  63. data/ext/iodine/fiobject.c +620 -0
  64. data/ext/iodine/fiobject.h +654 -0
  65. data/ext/iodine/hpack.h +1923 -0
  66. data/ext/iodine/http.c +2754 -0
  67. data/ext/iodine/http.h +1002 -0
  68. data/ext/iodine/http1.c +912 -0
  69. data/ext/iodine/http1.h +29 -0
  70. data/ext/iodine/http1_parser.h +873 -0
  71. data/ext/iodine/http_internal.c +1278 -0
  72. data/ext/iodine/http_internal.h +237 -0
  73. data/ext/iodine/http_mime_parser.h +350 -0
  74. data/ext/iodine/iodine.c +1430 -0
  75. data/ext/iodine/iodine.h +63 -0
  76. data/ext/iodine/iodine_caller.c +218 -0
  77. data/ext/iodine/iodine_caller.h +27 -0
  78. data/ext/iodine/iodine_connection.c +933 -0
  79. data/ext/iodine/iodine_connection.h +55 -0
  80. data/ext/iodine/iodine_defer.c +420 -0
  81. data/ext/iodine/iodine_defer.h +6 -0
  82. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  83. data/ext/iodine/iodine_helpers.c +282 -0
  84. data/ext/iodine/iodine_helpers.h +12 -0
  85. data/ext/iodine/iodine_http.c +1171 -0
  86. data/ext/iodine/iodine_http.h +23 -0
  87. data/ext/iodine/iodine_json.c +302 -0
  88. data/ext/iodine/iodine_json.h +6 -0
  89. data/ext/iodine/iodine_mustache.c +567 -0
  90. data/ext/iodine/iodine_mustache.h +6 -0
  91. data/ext/iodine/iodine_pubsub.c +580 -0
  92. data/ext/iodine/iodine_pubsub.h +26 -0
  93. data/ext/iodine/iodine_rack_io.c +281 -0
  94. data/ext/iodine/iodine_rack_io.h +20 -0
  95. data/ext/iodine/iodine_store.c +142 -0
  96. data/ext/iodine/iodine_store.h +20 -0
  97. data/ext/iodine/iodine_tcp.c +346 -0
  98. data/ext/iodine/iodine_tcp.h +13 -0
  99. data/ext/iodine/iodine_tls.c +261 -0
  100. data/ext/iodine/iodine_tls.h +13 -0
  101. data/ext/iodine/mustache_parser.h +1546 -0
  102. data/ext/iodine/redis_engine.c +957 -0
  103. data/ext/iodine/redis_engine.h +79 -0
  104. data/ext/iodine/resp_parser.h +317 -0
  105. data/ext/iodine/websocket_parser.h +505 -0
  106. data/ext/iodine/websockets.c +735 -0
  107. data/ext/iodine/websockets.h +185 -0
  108. data/isomorfeus-iodine.gemspec +42 -0
  109. data/lib/iodine/connection.rb +61 -0
  110. data/lib/iodine/json.rb +42 -0
  111. data/lib/iodine/mustache.rb +113 -0
  112. data/lib/iodine/pubsub.rb +55 -0
  113. data/lib/iodine/rack_utils.rb +43 -0
  114. data/lib/iodine/tls.rb +16 -0
  115. data/lib/iodine/version.rb +3 -0
  116. data/lib/iodine.rb +274 -0
  117. data/lib/rack/handler/iodine.rb +33 -0
  118. data/logo.png +0 -0
  119. metadata +271 -0
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/extensiontask"
3
+ require_relative 'lib/iodine/version'
4
+
5
+ begin
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new(:rspec)
8
+ rescue LoadError
9
+ end
10
+
11
+ task :spec => [:compile, :rspec]
12
+
13
+ task :push_packages do
14
+ Rake::Task['push_packages_to_rubygems'].invoke
15
+ Rake::Task['push_packages_to_github'].invoke
16
+ end
17
+
18
+ task :push_packages_to_rubygems do
19
+ system("gem push isomorfeus-iodine-#{Iodine::VERSION}.gem")
20
+ end
21
+
22
+ task :push_packages_to_github do
23
+ system("gem push --key github --host https://rubygems.pkg.github.com/isomorfeus isomorfeus-iodine-#{Iodine::VERSION}.gem")
24
+ end
25
+
26
+ task :push do
27
+ system("git push github")
28
+ system("git push gitlab")
29
+ system("git push bitbucket")
30
+ system("git push gitprep")
31
+ end
32
+
33
+ task :default => [:compile, :spec]
34
+
35
+ Rake::ExtensionTask.new "iodine" do |ext|
36
+ ext.lib_dir = "lib/iodine"
37
+ end
38
+
39
+ # Rake::ExtensionTask.new "iodine_http" do |ext|
40
+ # ext.name = 'iodine_http'
41
+ # ext.lib_dir = "lib/iodine"
42
+ # ext.ext_dir = 'ext/iodine'
43
+ # ext.config_script = 'extconf-http.rb'
44
+ # end
@@ -0,0 +1,159 @@
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
+ ---
8
+
9
+ ## Purpose
10
+
11
+ This document details a Rack specification extension for publish/subscribe (pub/sub) modules that can extend WebSocket / EventSource Rack servers.
12
+
13
+ The purpose of this specification is:
14
+
15
+ 1. To keep separation of concerns by avoiding inter-process-communication logic (IPC) in the application code base.
16
+
17
+ This is important since IPC is often implemented using pipes / sockets, which could introduce network and IO concerns into the application code.
18
+
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.
20
+
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.
22
+
23
+ 3. To provide common considerations and guidelines for pub/sub implementors to consider when implementing their pub/sub modules.
24
+
25
+ Some concerns are common for pub/sub implementors, such as integrating third party message brokers (Redis, RabbitMQ, Cassandra)
26
+
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).
32
+
33
+ * `subscribe(to, is_pattern = false) { |from, message| optional_block }` where:
34
+
35
+ * `to` is a named **channel** (or **pattern**).
36
+
37
+ The implementation **MAY** support pattern matching for named channels (`to`). The pattern matching algorithm used, if any, **SHOULD** be documented.
38
+
39
+ If the implementation does **NOT** support pattern matching and `is_pattern` is truthful, the implementation **MUST** raise and exception.
40
+
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'`).
42
+
43
+ * `block` is optional and accepts (if provided) two arguments (`from` which is equal to `to` and `message` which contains the data's content).
44
+
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).
58
+
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.
60
+
61
+ * `unsubscribe(from, is_pattern = false)` should cancel a subscription to the `from` named channel / pattern.
62
+
63
+ * `publish(to, message, engine = nil)` where:
64
+
65
+ * `to` is a named channel, same as detailed in `subscribe`.
66
+
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.
68
+
69
+ * `message` a String containing the data to be published.
70
+
71
+ If `message` is NOT a String, the implementation **MAY** convert the data silently to JSON. Otherwise, the implementation **MUST** raise an exception.
72
+
73
+ `message` encoding (binary / UTZ-8) **MAY** be altered during publication, but any change **MUST** result in valid encoding.
74
+
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**.
76
+
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`.
78
+
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).
80
+
81
+ A global alias for this method (allowing it to be accessed from outside active connections) **MAY** be defined as `Rack::PubSub.publish`.
82
+
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`):
104
+
105
+ * `attach(engine)` where `engine` is a `PubSubEngine` object, as described in this specification.
106
+
107
+ When a pub/sub engine is attached, the implementation **MUST** inform the engine of any existing or future subscriptions.
108
+
109
+ The implementation **MUST** call the engine's `subscribe` callback for each existing (and future) subscription.
110
+
111
+ The implementation **MUST** allow multiple "engines" to be attached when multiple calls to `attach` are made.
112
+
113
+ * `detach(engine)` where `engine` is a PubSubEngine object as described in this specification.
114
+
115
+ The implementation **MUST** remove the engine from the attached engine list. The opposite of `attach`.
116
+
117
+ * `default=(engine)` sets a default pub/sub engine, where `engine` is a PubSubEngine object as described in this specification.
118
+
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.
120
+
121
+ * `default` returns the current default pub/sub engine, where the engine is a PubSubEngine object as described in this specification.
122
+
123
+ * `reset(engine)` where `engine` is a PubSubEngine object as described in this specification.
124
+
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.
126
+
127
+ A `PubSubEngine` instance object **MUST** implement the following methods:
128
+
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.
130
+
131
+ The method **MUST** return `true` if a subscription was scheduled (or performed) or `false` if the subscription is known to fail.
132
+
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.
134
+
135
+ This method **MUST NOT** raise an exception.
136
+
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.
138
+
139
+ The method's semantics are similar to `subscribe` only is performs the opposite action.
140
+
141
+ This method **MUST NOT** raise an exception.
142
+
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.
144
+
145
+ * `publish(channel, message)` where both `channel` is either a Symbol or a String (both being equivalent) and `message` **MUST** be a String.
146
+
147
+ This method will be called by the server when a message is published using the engine.
148
+
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`.
154
+
155
+ i.e., if the implementation's global `publish` method is in a class called `Iodine`:
156
+
157
+ ```ruby
158
+ Iodine.publish channel, message, false
159
+ ```
@@ -0,0 +1,239 @@
1
+ ### Draft State
2
+
3
+ This draft is also implemented by [the Agoo server](https://github.com/ohler55/agoo) according to the specifications stated in [Rack PR#1272](https://github.com/rack/rack/pull/1272).
4
+
5
+ ---
6
+ ## Purpose
7
+
8
+ This document details a Rack specification extension for WebSocket / EventSource servers.
9
+
10
+ The purpose of this specification is:
11
+
12
+ 1. To improve application safety by phasing out the use of `hijack` and replacing it with the use of application object callbacks.
13
+
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
+
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
+
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'.
19
+
20
+ ## Rack WebSockets / EventSource
21
+
22
+ Servers that publish WebSocket and/or EventSource (SSE) support using the `env['rack.upgrade?']` value **MUST** follow the requirements set in this document.
23
+
24
+ This document reserves the Rack `env` Hash keys of `rack.upgrade?` and `rack.upgrade`.
25
+
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']`:
37
+
38
+ * The server **SHOULD** ignore the Callback Object and process the response as if it did not exist.
39
+
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.
41
+
42
+ ### The WebSocket / EventSource Callback Object
43
+
44
+ WebSocket and EventSource connection upgrade and handling is performed using a Callback Object.
45
+
46
+ The Callback Object could be a any object which implements any (of none) of the following callbacks:
47
+
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.
49
+
50
+ * `on_message(client, data)` **MUST** be called when incoming WebSocket data is received.
51
+
52
+ This callback is ignored for EventSource connections.
53
+
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).
55
+
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.
57
+
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.
59
+
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.
61
+
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.
63
+
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.
65
+
66
+
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):
68
+
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.
70
+
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.
74
+
75
+ `write` shall return `true` on success and `false` if the connection is closed.
76
+
77
+ For WebSocket connections only (irrelevant for EventSource connections):
78
+
79
+ * If `data` is UTF-8 encoded, the data will be sent as text.
80
+
81
+ * If `data` is binary encoded it will be sent as non-text (as specified by the WebSocket Protocol).
82
+
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.
86
+
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.
88
+
89
+ `close` shall always return `nil`.
90
+
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.
92
+
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.
94
+
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.
104
+
105
+ * `class` **MUST** return the client's Class, allowing it be extended with additional features (such as Pub/Sub, etc').
106
+
107
+ **Note**: Ruby adds this method automatically to every class, no need to do a thing.
108
+
109
+ The server **MAY** support the following (optional) methods for the `client` object:
110
+
111
+ * `handler` if implemented, **MUST** return the callback object linked to the `client` object.
112
+
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.
129
+
130
+ ---
131
+
132
+ ## Implementation Examples
133
+
134
+ ### Server-Client Upgrade to WebSockets / EventSource
135
+
136
+ * **Server**:
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
+
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.
141
+
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.
143
+
144
+ * **Client**:
145
+
146
+ If a client decides to upgrade a request, they will place an appropriate Callback Object in the `env['rack.upgrade']` Hash key.
147
+
148
+ * **Server**:
149
+
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.
151
+
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.
153
+
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.
155
+
156
+ The response status provided by the response object shall be ignored and the correct response status shall be set by the server.
157
+
158
+ 4. Once the upgrade had completed, the server will call the `on_open` callback.
159
+
160
+ No other callbacks shall be called until the `on_open` callback had returned.
161
+
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.
163
+
164
+ The `on_close` callback **MUST NOT** be called while any other callback is running (`on_open`, `on_message`, `on_drained`, etc').
165
+
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.
167
+
168
+ ## Example Usage
169
+
170
+ The following is an example WebSocket echo server implemented using this specification:
171
+
172
+ ```ruby
173
+ module WSConnection
174
+ def on_open(client)
175
+ puts "WebSocket connection established (#{client.object_id})."
176
+ end
177
+ def on_message(client, data)
178
+ client.write data # echo the data back
179
+ puts "on_drained MUST be implemented if #{ pending } != 0."
180
+ end
181
+ def on_drained(client)
182
+ puts "If this line prints out, on_drained is supported by the server."
183
+ end
184
+ def on_shutdown(client)
185
+ client.write "The server is going away. Goodbye."
186
+ end
187
+ def on_close(client)
188
+ puts "WebSocket connection closed (#{client.object_id})."
189
+ end
190
+ extend self
191
+ end
192
+
193
+ module App
194
+ def self.call(env)
195
+ if(env['rack.upgrade?'.freeze] == :websocket)
196
+ env['rack.upgrade'.freeze] = WSConnection
197
+ return [0, {}, []]
198
+ end
199
+ return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]]
200
+ end
201
+ end
202
+
203
+ run App
204
+ ```
205
+
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):
207
+
208
+ ```ruby
209
+ module Chat
210
+ def on_open(client)
211
+ client.class.prepend MyPubSubModule unless client.pubsub?
212
+ client.subscribe "chat"
213
+ client.publish "chat", "#{env[:nickname]} joined the chat."
214
+ end
215
+ def on_message(client, data)
216
+ client.publish "chat", "#{env[:nickname]}: #{data}"
217
+ end
218
+ def on_close(client)
219
+ client.publish "chat", "#{env[:nickname]}: left the chat."
220
+ end
221
+ extend self
222
+ end
223
+
224
+ module App
225
+ def self.call(env)
226
+ if(env['rack.upgrade?'.freeze])
227
+ nickname = env['PATH_INFO'][1..-1]
228
+ nickname = "Someone" if nickname == "".freeze
229
+ env[:nickname] = nickname
230
+ return [0, {}, []]
231
+ end
232
+ return [200, {"Content-Length" => "12", "Content-Type" => "text/plain"}, ["Hello World!"]]
233
+ end
234
+ end
235
+
236
+ run App
237
+ ```
238
+
239
+ Note that SSE connections will only be able to receive messages (the `on_message` callback is never called).
data/bin/console ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # this will compile Iodine and start a Ruby console with the Iodine gem loaded.
4
+
5
+ Dir.chdir(File.expand_path(File.join('..', '..'), __FILE__))
6
+ puts `rake clean`
7
+ puts `rake compile`
8
+
9
+ require 'benchmark'
10
+ $LOAD_PATH.unshift File.expand_path(File.join('..', '..', 'lib'), __FILE__ )
11
+ require "bundler/setup"
12
+ require "iodine"
13
+
14
+ # You can add fixtures and/or initialization code here to make experimenting
15
+ # with your gem easier. You can also use a different console, if you like.
16
+
17
+ # (If you use this, don't forget to add pry to your Gemfile!)
18
+ # require "pry"
19
+ # Pry.start
20
+
21
+ require "irb"
22
+ IRB.start