iodine 0.7.39 → 0.7.44
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -5
- data/CHANGELOG.md +28 -0
- data/README.md +36 -39
- data/SPEC-PubSub-Draft.md +89 -47
- data/SPEC-WebSocket-Draft.md +92 -55
- data/examples/async_task.ru +92 -0
- data/exe/iodine +1 -0
- data/ext/iodine/fio.c +65 -65
- data/ext/iodine/fio_tls_missing.c +6 -0
- data/ext/iodine/fio_tls_openssl.c +64 -28
- data/ext/iodine/http.c +11 -7
- data/ext/iodine/http1.c +4 -2
- data/ext/iodine/iodine.c +3 -1
- data/ext/iodine/iodine.h +1 -0
- data/ext/iodine/iodine_caller.c +1 -1
- data/ext/iodine/iodine_connection.c +24 -8
- data/ext/iodine/iodine_http.c +8 -8
- data/ext/iodine/iodine_mustache.c +2 -4
- data/ext/iodine/iodine_tls.c +2 -16
- data/lib/iodine.rb +1 -1
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +6 -0
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a212aecebe63d7033c5c1e985d4e2f90588dc604c8aa6a25965735aeef79ec72
|
4
|
+
data.tar.gz: 299ef31e1f2209cb11c0c49f2d4851d8c779a2744ebc08f42fb430ffaf3d76cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a6f12dfcff4eced37d98f627be0cb6a9662c8bac98365a5080ea0c47f9c3ae3919e315836ec33989be098c52c826707e0f22fa746f95a3847b354d6d0efa4f79
|
7
|
+
data.tar.gz: 1b5541f8c952f6b61feeb183cc6d7587ca200b4295afec9c9e540c69699f065c03724b46b464c929cc0aa800b594a0cdb83ec7b9a1b57c7501ba5633cb270124
|
data/.travis.yml
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
language: ruby
|
2
|
+
arch:
|
3
|
+
- amd64
|
4
|
+
- arm64
|
2
5
|
os:
|
3
6
|
- linux
|
4
7
|
# - osx
|
@@ -6,6 +9,7 @@ before_install:
|
|
6
9
|
- gem install bundler -v 1.10.6
|
7
10
|
- bundle install
|
8
11
|
rvm:
|
12
|
+
- 2.7.1
|
9
13
|
- 2.6.5
|
10
14
|
- 2.5.7
|
11
15
|
- 2.4.9
|
@@ -14,7 +18,7 @@ rvm:
|
|
14
18
|
notifications:
|
15
19
|
email: false
|
16
20
|
sudo: required
|
17
|
-
dist:
|
21
|
+
dist: xenial
|
18
22
|
addons:
|
19
23
|
apt:
|
20
24
|
sources:
|
@@ -24,8 +28,5 @@ addons:
|
|
24
28
|
script:
|
25
29
|
- echo CFLAGS = $CFLAGS
|
26
30
|
- echo cflags = $cflags
|
27
|
-
- bundle exec rake
|
31
|
+
- bundle exec rake install
|
28
32
|
- env VERBOSE=1 bundle exec rspec --format documentation
|
29
|
-
- gem uninstall -x iodine
|
30
|
-
- rake build
|
31
|
-
- find pkg/iodine-*.gem -exec gem install -V {} +
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,34 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
6
6
|
|
7
7
|
## Changes:
|
8
8
|
|
9
|
+
#### Change log v.0.7.44 (2021-02-28)
|
10
|
+
|
11
|
+
**Fix**: Fixes issue #103 where an empty String response would result in the word "null" being returned (no String object was created, which routed the NULL object to facil.io's JSON interpreter). Credit to @waghanza (Marwan Rabbâa) for exposing the issue.
|
12
|
+
|
13
|
+
**Fix**: Fixes a possible edge case race condition where the GC might free a channel name's String object before it's passed on to the user's callback.
|
14
|
+
|
15
|
+
**Fix**: Fixes a typo in the CLI documentation.
|
16
|
+
|
17
|
+
#### Change log v.0.7.43 (2020-11-03)
|
18
|
+
|
19
|
+
**Fix**: Fixes an issue where the GVL state in user-spawned threads is inaccurate. This issue only occurs if spawning a new thread and calling certain Iodine methods from a user thread.
|
20
|
+
|
21
|
+
**Fix**: validate that data passed by the user to `write` is a String object and print warnings / raise exceptions if t isn't. Credit to Vamsi Ambati for asking a question that exposed this issue.
|
22
|
+
|
23
|
+
#### Change log v.0.7.42 (2020-08-02)
|
24
|
+
|
25
|
+
**Fix**: Implement fix suggested by @Shelvak (Néstor Coppi) in issue #98.
|
26
|
+
|
27
|
+
#### Change log v.0.7.41 (2020-07-24)
|
28
|
+
|
29
|
+
**Fix**: Hot Restart failed because listening sockets were cleared away. Credit to Néstor Coppi (@Shelvak) for exposing issue #97.
|
30
|
+
|
31
|
+
**Fix**: CLI argument parsing is now only active when using the iodine CLI (or if defining `IODINE_PARSE_CLI` before requiring `iodine`). Credit to Aldis Berjoza (@graudeejs) for exposing issue #96.
|
32
|
+
|
33
|
+
#### Change log v.0.7.40 (2020-05-23)
|
34
|
+
|
35
|
+
**Fix**: fixed TLS logging and performance issues exposed by Franck Gille (@fgi) in issue #93.
|
36
|
+
|
9
37
|
#### Change log v.0.7.39 (2020-05-18)
|
10
38
|
|
11
39
|
**Security**: a request smuggling attack vector and Transfer Encoding attack vector in the HTTP/1.1 parser were exposed by Sam Sanoop from [the Snyk Security team (snyk.io)](https://snyk.io). The parser was updated to deal with these potential issues.
|
data/README.md
CHANGED
@@ -19,14 +19,15 @@ Iodine includes native support for:
|
|
19
19
|
* Pub/Sub (with optional Redis Pub/Sub scaling);
|
20
20
|
* Fast(!) builtin Mustache template engine.
|
21
21
|
* Static file service (with automatic `gzip` support for pre-compressed assets);
|
22
|
+
* Optimized Logging to `stderr`.
|
22
23
|
* Asynchronous event scheduling and timers;
|
23
24
|
* HTTP/1.1 keep-alive and pipelining;
|
25
|
+
* Heap Fragmentation Protection.
|
24
26
|
* TLS 1.2 and above (Requires OpenSSL >= 1.1.0);
|
25
27
|
* TCP/IP server and client connectivity;
|
26
28
|
* Unix Socket server and client connectivity;
|
27
|
-
* Hot Restart (using the USR1 signal);
|
29
|
+
* Hot Restart (using the USR1 signal and without hot deployment);
|
28
30
|
* Custom protocol authoring;
|
29
|
-
* Optimized Logging to `stderr`.
|
30
31
|
* [Sequel](https://github.com/jeremyevans/sequel) and ActiveRecord forking protection.
|
31
32
|
* and more!
|
32
33
|
|
@@ -48,6 +49,10 @@ With `Iodine.listen service: :http` it's possible to run multiple HTTP applicati
|
|
48
49
|
|
49
50
|
Iodine also supports native process cluster Pub/Sub and a native RedisEngine to easily scale iodine's Pub/Sub horizontally.
|
50
51
|
|
52
|
+
### Known Issues and Reporting Issues
|
53
|
+
|
54
|
+
See the [GitHub Open Issues](https://github.com/boazsegev/iodine/issues) list for known issues and to report new issues.
|
55
|
+
|
51
56
|
### Installing and Running Iodine
|
52
57
|
|
53
58
|
Install iodine on any Linux / BSD / macOS system using:
|
@@ -70,6 +75,8 @@ bundler exec iodine
|
|
70
75
|
|
71
76
|
#### Installing with SSL/TLS
|
72
77
|
|
78
|
+
**Note**: iodine has known issues with the TLS/SSL support. TLS/SSL should **NOT** be used in production (see issues #95 and #94).
|
79
|
+
|
73
80
|
Make sure to update OpenSSL to the latest version **before installing Ruby** (`rbenv` should do this automatically).
|
74
81
|
|
75
82
|
To avoid name resolution conflicts, iodine will bind to the same OpenSSL version Ruby is bound to. To use SSL/TLS this should be OpenSSL >= 1.1.0 or LibreSSL >= 2.7.4.
|
@@ -87,9 +94,7 @@ Confirmed OpenSSL to be version 1.1.0 or above (OpenSSL 1.1.0j 20 Nov 2018)...
|
|
87
94
|
...
|
88
95
|
```
|
89
96
|
|
90
|
-
**
|
91
|
-
|
92
|
-
The installation script tests for OpenSSL 1.1.0 and above. However, this testing approach sometimes provides false positives. If TLS isn't required, install with `NO_SSL=1`. i.e.:
|
97
|
+
The installation script tests for OpenSSL 1.1.0 and above. However, this testing approach sometimes provides false positives. **If TLS isn't required, install with `NO_SSL=1`**. i.e.:
|
93
98
|
|
94
99
|
```bash
|
95
100
|
NO_SSL=1 bundler exec iodine
|
@@ -110,17 +115,19 @@ On Rails:
|
|
110
115
|
if(defined?(Iodine))
|
111
116
|
Iodine.threads = ENV.fetch("RAILS_MAX_THREADS", 5).to_i if Iodine.threads.zero?
|
112
117
|
Iodine.workers = ENV.fetch("WEB_CONCURRENCY", 2).to_i if Iodine.workers.zero?
|
113
|
-
Iodine::DEFAULT_SETTINGS[:port]
|
118
|
+
Iodine::DEFAULT_SETTINGS[:port] ||= ENV.fetch("PORT") if ENV.fetch("PORT")
|
114
119
|
end
|
115
120
|
```
|
116
121
|
|
117
122
|
When using native WebSockets with Rails, middle-ware is probably the best approach. A guide for this approach will, hopefully, get published in the future.
|
118
123
|
|
124
|
+
**Note**: command-line instructions (CLI) should be the preferred way for configuring iodine, allowing for code-less configuration updates.
|
125
|
+
|
119
126
|
### Optimizing Iodine's Concurrency
|
120
127
|
|
121
128
|
To get the most out of iodine, consider the amount of CPU cores available and the concurrency level the application requires.
|
122
129
|
|
123
|
-
Iodine will calculate, when possible, a good enough default concurrency model. See if this works for your application or customize according to the application's needs.
|
130
|
+
Iodine will calculate, when possible, a good enough default concurrency model for fast applications. See if this works for your application or customize according to the application's needs.
|
124
131
|
|
125
132
|
Command line arguments allow easy access to different options, including concurrency levels. i.e., to set up 16 threads and 4 processes:
|
126
133
|
|
@@ -136,13 +143,19 @@ export WORKERS=-1 # negative values are fractions of CPU cores.
|
|
136
143
|
bundler exec iodine -p $PORT
|
137
144
|
```
|
138
145
|
|
146
|
+
Negative values are evaluated as "CPU Cores / abs(Value)". i.e., on an 8 core CPU machine, this will produce 4 worker processes with 2 threads per worker:
|
147
|
+
|
148
|
+
```bash
|
149
|
+
bundler exec iodine -p $PORT -t 2 -w -2
|
150
|
+
```
|
151
|
+
|
139
152
|
### Heap Fragmentation Protection
|
140
153
|
|
141
154
|
Iodine includes a fast, network oriented, custom memory allocator, optimizing away some of the work usually placed on the Ruby Garbage Collector (GC).
|
142
155
|
|
143
156
|
This approach helps to minimize heap fragmentation for long running processes, by grouping many short-lived objects into a common memory space.
|
144
157
|
|
145
|
-
It
|
158
|
+
It is still recommended to consider [jemalloc](http://jemalloc.net) or other allocators that also help mitigate heap fragmentation issues.
|
146
159
|
|
147
160
|
### Static file serving support
|
148
161
|
|
@@ -435,11 +448,11 @@ Iodine.connect url: "wss://echo.websocket.org", handler: EchoClient.new, ping: 4
|
|
435
448
|
Iodine.start
|
436
449
|
```
|
437
450
|
|
438
|
-
### TLS 1.2 support
|
451
|
+
### TLS >= 1.2 support
|
439
452
|
|
440
453
|
> Requires OpenSSL >= `1.1.0`. On Heroku, requires `heroku-18`.
|
441
454
|
|
442
|
-
Iodine supports secure connections fore TLS version 1.2 and up (depending on the OpenSSL version).
|
455
|
+
Iodine supports secure connections fore TLS version 1.2 **and up** (depending on the OpenSSL version).
|
443
456
|
|
444
457
|
A self signed certificate is available using the `-tls` flag from the command-line.
|
445
458
|
|
@@ -480,31 +493,11 @@ run APP
|
|
480
493
|
|
481
494
|
### How does it compare to other servers?
|
482
495
|
|
483
|
-
|
484
|
-
|
485
|
-
In my tests, pitching Iodine against Puma, Iodine was anywhere between x1.5 and x7 faster than Puma (depending on use-case). such a big difference is suspect and I recommend that you test it yourself.
|
496
|
+
In my tests, pitching Iodine against Puma, Iodine was anywhere between x1.5 and more than x10 faster than Puma (depending on use-case and settings).
|
486
497
|
|
487
|
-
|
498
|
+
Such a big difference is suspect and I recommend that you test it yourself - even better if you test performance using your own application and a number of possible different settings (how many threads per CPU core? how many worker processes? middleware vs. server request logging, etc').
|
488
499
|
|
489
|
-
|
490
|
-
|
491
|
-
* Iodine performed at 74,786.27 req/sec, consuming ~68.4Mb of memory.
|
492
|
-
|
493
|
-
* Puma performed at 48,994.59 req/sec, consuming ~79.6Mb of memory.
|
494
|
-
|
495
|
-
When benchmarking using a VM (crossing machine boundaries, 16 threads, 4 workers, 200 concurrent connections), I calculated Iodine to be x2.3 faster:
|
496
|
-
|
497
|
-
* Iodine performed at 23,559.56 req/sec, consuming ~88.8Mb of memory.
|
498
|
-
|
499
|
-
* Puma performed at 9,935.31 req/sec, consuming ~84.0Mb of memory.
|
500
|
-
|
501
|
-
When benchmarking using a VM (crossing machine boundaries, single thread, single worker, 200 concurrent connections), I calculated Iodine to be x7.3 faster:
|
502
|
-
|
503
|
-
* Iodine performed at 18,444.31 req/sec, consuming ~25.6Mb of memory.
|
504
|
-
|
505
|
-
* Puma performed at 2,521.56 req/sec, consuming ~27.5Mb of memory.
|
506
|
-
|
507
|
-
I have doubts about my own benchmarks and I recommend benchmarking the performance for yourself using `wrk` or `ab`:
|
500
|
+
I recommend benchmarking the performance for yourself using `wrk` or `ab`:
|
508
501
|
|
509
502
|
```bash
|
510
503
|
$ wrk -c200 -d4 -t2 http://localhost:3000/
|
@@ -512,7 +505,7 @@ $ wrk -c200 -d4 -t2 http://localhost:3000/
|
|
512
505
|
$ ab -n 100000 -c 200 -k http://127.0.0.1:3000/
|
513
506
|
```
|
514
507
|
|
515
|
-
|
508
|
+
The best application to use for benchmarking is your actual application. Or, you could create a simple `config.ru` file with a __hello world__ app:
|
516
509
|
|
517
510
|
```ruby
|
518
511
|
App = Proc.new do |env|
|
@@ -525,17 +518,21 @@ end
|
|
525
518
|
run App
|
526
519
|
```
|
527
520
|
|
528
|
-
Then start comparing servers. Here are the settings I used to compare iodine and Puma (4 processes,
|
521
|
+
Then start comparing servers. Here are the settings I used to compare iodine and Puma (4 processes, 4 threads):
|
529
522
|
|
530
523
|
```bash
|
531
|
-
$ RACK_ENV=production iodine -p 3000 -t
|
524
|
+
$ RACK_ENV=production iodine -p 3000 -t 4 -w 4
|
532
525
|
# vs.
|
533
|
-
$ RACK_ENV=production puma -p 3000 -t
|
526
|
+
$ RACK_ENV=production puma -p 3000 -t 4 -w 4
|
534
527
|
# Review the `iodine -?` help for more command line options.
|
535
528
|
```
|
536
529
|
|
537
530
|
It's recommended that the servers (Iodine/Puma) and the client (`wrk`/`ab`) run on separate machines.
|
538
531
|
|
532
|
+
It is worth noting that iodine can also speed up logging by replacing the logging middleware with `iodine -v`. This approach uses less memory and improves performance at the expense of fuzzy timing and some string caching.
|
533
|
+
|
534
|
+
On my machine, testing with the logging functionality enabled, iodine was more then 10 times faster than puma (60.9K req/sec vs. 5.3K req/sec)
|
535
|
+
|
539
536
|
### A few notes
|
540
537
|
|
541
538
|
Iodine's upgrade / callback design has a number of benefits, some of them related to better IO handling, resource optimization (no need for two IO polling systems), etc. This also allows us to use middleware without interfering with connection upgrades and provides backwards compatibility.
|
@@ -579,8 +576,8 @@ Iodine is written in C and allows some compile-time customizations, such as:
|
|
579
576
|
These options can be used, for example, like so:
|
580
577
|
|
581
578
|
```bash
|
582
|
-
|
583
|
-
|
579
|
+
gem install iodine -- \
|
580
|
+
--with-cflags=\"-DHTTP_MAX_HEADER_LENGTH=48000 -DFIO_FORCE_MALLOC=1 -DHTTP_MAX_HEADER_COUNT=64\"
|
584
581
|
```
|
585
582
|
|
586
583
|
More possible compile time options can be found in the [facil.io documentation](http://facil.io).
|
data/SPEC-PubSub-Draft.md
CHANGED
@@ -1,66 +1,106 @@
|
|
1
|
-
# Ruby
|
1
|
+
# Ruby pub/sub API Specification Draft
|
2
|
+
|
3
|
+
### Draft State
|
4
|
+
|
5
|
+
This draft is under discussion and will be implemented by iodine starting with the 0.8.x versions.
|
6
|
+
|
7
|
+
---
|
2
8
|
|
3
9
|
## Purpose
|
4
10
|
|
5
|
-
|
11
|
+
This document details a Rack specification extension for publish/subscribe (pub/sub) modules that can extend WebSocket / EventSource Rack servers.
|
6
12
|
|
7
|
-
The purpose of this specification is
|
13
|
+
The purpose of this specification is:
|
8
14
|
|
9
|
-
|
15
|
+
1. To keep separation of concerns by avoiding inter-process-communication logic (IPC) in the application code base.
|
10
16
|
|
11
|
-
This
|
17
|
+
This is important since IPC is often implemented using pipes / sockets, which could introduce network and IO concerns into the application code.
|
12
18
|
|
13
|
-
|
19
|
+
2. To specify a common publish/subscribe (pub/sub) API for servers and pub/sub modules, allowing applications to easily switch between conforming implementations and servers.
|
14
20
|
|
15
|
-
|
21
|
+
Since pub/sub design is idiomatic to WebSocket and EventSource approaches, as well as other reactive programming techniques, it is better if applications aren't locked in to a specific server / implementation.
|
16
22
|
|
17
|
-
|
23
|
+
3. To provide common considerations and guidelines for pub/sub implementors to consider when implementing their pub/sub modules.
|
18
24
|
|
19
|
-
|
25
|
+
Some concerns are common for pub/sub implementors, such as integrating third party message brokers (Redis, RabbitMQ, Cassandra)
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
## Pub/Sub Instance Methods
|
28
|
+
|
29
|
+
Conforming pub/sub implementations **MUST** implement the following pub/sub instance methods:
|
30
|
+
|
31
|
+
* `pubsub?` **MUST** return the pub/sub API version as an integer number. Currently, set to `0` (development version).
|
26
32
|
|
27
|
-
|
33
|
+
* `subscribe(to, is_pattern = false) { |from, message| optional_block }` where:
|
28
34
|
|
29
|
-
*
|
35
|
+
* `to` is a named **channel** (or **pattern**).
|
30
36
|
|
31
|
-
|
37
|
+
The implementation **MAY** support pattern matching for named channels (`to`). The pattern matching algorithm used, if any, **SHOULD** be documented.
|
32
38
|
|
33
|
-
|
39
|
+
If the implementation does **NOT** support pattern matching and `is_pattern` is truthful, the implementation **MUST** raise and exception.
|
34
40
|
|
35
|
-
|
41
|
+
`to` **SHOULD** be a String, but implementations **MAY** support Symbols as aliases to Strings (in which case `:my_channel` is the same `'my_channel'`).
|
36
42
|
|
37
|
-
|
43
|
+
* `block` is optional and accepts (if provided) two arguments (`from` which is equal to `to` and `message` which contains the data's content).
|
38
44
|
|
39
|
-
|
45
|
+
`block` (if provided) **MUST** be called when a publication was received by the named channel.
|
46
|
+
|
47
|
+
If no `block` is provided:
|
48
|
+
|
49
|
+
* If the pub/sub instance is an extended WebSocket / EventSource (SSE) `client` object (see the WebSocket / EventSource extension draft) data should be directly sent to the `client`.
|
50
|
+
|
51
|
+
* If the pub/sub isn't linked with a `client` / connection, an exception **MUST** be raised.
|
52
|
+
|
53
|
+
If a subscription to `to` already exists for the same pub/sub instance, it should be *replaced* by the new subscription (the old subscription should be canceled / unsubscribed).
|
54
|
+
|
55
|
+
If the pub/sub instance is an extended WebSocket / EventSource (SSE) `client` object (see the WebSocket / EventSource extension draft), the subscription **MUST** be closed automatically when the connection is closed (when `on_close` is called).
|
56
|
+
|
57
|
+
Otherwise, the subscription **MUST** be closed automatically when the pub/sub object goes out of scope and is garbage collected (if this ever happens).
|
40
58
|
|
41
59
|
The `subscribe` method **MUST** return `nil` on a known failure (i.e., when the connection is already closed), or any truthful value on success.
|
42
60
|
|
43
|
-
|
61
|
+
* `unsubscribe(from, is_pattern = false)` should cancel a subscription to the `from` named channel / pattern.
|
62
|
+
|
63
|
+
* `publish(to, message, engine = nil)` where:
|
44
64
|
|
45
|
-
|
65
|
+
* `to` is a named channel, same as detailed in `subscribe`.
|
46
66
|
|
47
|
-
|
67
|
+
Implementations **MAY** support pattern based publishing. This **SHOULD** be documented as well as how patterns are detected (as opposed to named channels). Note that pattern publishing isn't supported by common backends (such as Redis) and introduces complex privacy and security concerns.
|
48
68
|
|
49
|
-
* `
|
69
|
+
* `message` a String containing the data to be published.
|
50
70
|
|
51
|
-
|
71
|
+
If `message` is NOT a String, the implementation **MAY** convert the data silently to JSON. Otherwise, the implementation **MUST** raise an exception.
|
52
72
|
|
53
|
-
|
73
|
+
`message` encoding (binary / UTZ-8) **MAY** be altered during publication, but any change **MUST** result in valid encoding.
|
54
74
|
|
55
|
-
* `engine` routes the publish method to the specified
|
75
|
+
* `engine` routes the publish method to the specified pub/sub Engine (see later on). If none is specified, the default engine should be used. If `false` is specified, the message **MUST** be forwarded to all subscribed clients on the **same process**.
|
56
76
|
|
57
|
-
The `publish` method
|
77
|
+
The `publish` method **MUST** return `true` if a publication was scheduled (not necessarily performed). If it's already known that the publication would fail, the method should return `false`.
|
58
78
|
|
59
79
|
An implementation **MUST** call the relevant PubSubEngine's `publish` method after performing any internal book keeping logic. If `engine` is `nil`, the default PubSubEngine should be called. If `engine` is `false`, the implementation **MUST** forward the published message to the actual clients (if any).
|
60
80
|
|
61
81
|
A global alias for this method (allowing it to be accessed from outside active connections) **MAY** be defined as `Rack::PubSub.publish`.
|
62
82
|
|
63
|
-
|
83
|
+
## Integrating a Pub/Sub module into a WebSocket / EventSource (SSE) `client` object
|
84
|
+
|
85
|
+
A conforming pub/sub module **MUST** be designed so that it can be integrated into WebSocket / EventSource (SSE) `client` objects be `include`-ing their class.
|
86
|
+
|
87
|
+
This **MUST** result in a behavior where subscriptions are destroyed / canceled once the `client` object state changes to "closed" - i.e., either when the `on_close` callback is called, or the first time the method `client.open?` returns `false`.
|
88
|
+
|
89
|
+
The idiomatic way to add a pub/sub module to a `client`'s class is:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
client.class.prepend MyPubSubModule unless client.pubsub?
|
93
|
+
```
|
94
|
+
|
95
|
+
The pub/sub module **MUST** expect and support this behavior.
|
96
|
+
|
97
|
+
**Note**: the use of `prepend` (rather than `include`) is chosen so that it's possible to override the `client`'s instance method of `pubsub?`.
|
98
|
+
|
99
|
+
## Connecting to External Backends (pub/sub Engines)
|
100
|
+
|
101
|
+
It is common for scaling applications to require an external message broker backend such as Redis, RabbitMQ, etc'. The following requirements set a common interface for such "engine" implementation and integration.
|
102
|
+
|
103
|
+
Pub/sub implementations **MUST** implement the following module / class methods in one of their public classes / modules (iodine implements these under `Iodine::PubSub`):
|
64
104
|
|
65
105
|
* `attach(engine)` where `engine` is a `PubSubEngine` object, as described in this specification.
|
66
106
|
|
@@ -72,9 +112,9 @@ Implementations **MUST** implement the following methods in one of their public
|
|
72
112
|
|
73
113
|
* `detach(engine)` where `engine` is a PubSubEngine object as described in this specification.
|
74
114
|
|
75
|
-
|
115
|
+
The implementation **MUST** remove the engine from the attached engine list. The opposite of `attach`.
|
76
116
|
|
77
|
-
* `default
|
117
|
+
* `default=(engine)` sets a default pub/sub engine, where `engine` is a PubSubEngine object as described in this specification.
|
78
118
|
|
79
119
|
Implementations **MUST** forward any `publish` method calls to the default pub/sub engine, unless an `engine` is specified in arguments passes to the `publish` method.
|
80
120
|
|
@@ -84,34 +124,36 @@ Implementations **MUST** implement the following methods in one of their public
|
|
84
124
|
|
85
125
|
Implementations **MUST** behave as if the engine was newly registered and (re)inform the engine of any existing subscriptions by calling engine's `subscribe` callback for each existing subscription.
|
86
126
|
|
87
|
-
|
127
|
+
A `PubSubEngine` instance object **MUST** implement the following methods:
|
88
128
|
|
89
|
-
|
129
|
+
* `subscribe(to, is_pattern = false)` this method informs the engine that a subscription to the specified channel / pattern exists for the calling the process. It **MUST ONLY** be called **once** for all existing and future subscriptions to that channel within the process.
|
90
130
|
|
91
|
-
|
131
|
+
The method **MUST** return `true` if a subscription was scheduled (or performed) or `false` if the subscription is known to fail.
|
92
132
|
|
93
|
-
|
133
|
+
This method will be called by the pub/sub implementation (for each registered engine). The engine **MAY** assume that the method would never be called directly by an application / client.
|
94
134
|
|
95
|
-
|
135
|
+
This method **MUST NOT** raise an exception.
|
96
136
|
|
97
|
-
|
137
|
+
* `unsubscribe(from, is_pattern = false)` this method informs an engine that there are no more subscriptions to the named channel / pattern for the calling process.
|
98
138
|
|
99
|
-
|
139
|
+
The method's semantics are similar to `subscribe` only is performs the opposite action.
|
100
140
|
|
101
|
-
|
141
|
+
This method **MUST NOT** raise an exception.
|
102
142
|
|
103
|
-
The method
|
143
|
+
This method will be called by the pub/sub implementation (for each registered engine). The engine **MAY** assume that the method would never be called directly by an application / client.
|
104
144
|
|
105
|
-
|
106
|
-
|
107
|
-
* `publish(channel, message)` where both `channel` and `message` are String objects.
|
145
|
+
* `publish(channel, message)` where both `channel` is either a Symbol or a String (both being equivalent) and `message` **MUST** be a String.
|
108
146
|
|
109
147
|
This method will be called by the server when a message is published using the engine.
|
110
148
|
|
111
|
-
|
149
|
+
This method will be called by the pub/sub implementation (for each registered engine).
|
150
|
+
|
151
|
+
The engine **MUST** assume that the method **MAY** be called directly by an application / client.
|
152
|
+
|
153
|
+
In order for a PubSubEngine instance object to publish messages to all subscribed clients on a particular process, it **SHOULD** call the implementation's global `publish` method with the engine set to `false`.
|
112
154
|
|
113
|
-
|
155
|
+
i.e., if the implementation's global `publish` method is in a class called `Iodine`:
|
114
156
|
|
115
157
|
```ruby
|
116
|
-
|
158
|
+
Iodine.publish channel, message, false
|
117
159
|
```
|