iodine 0.7.38 → 0.7.43

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 698e63218598fd769063529e151ab15aab31e413365a6e80130e464adac9eea3
4
- data.tar.gz: d4b146805592a1ef5476461c349eeb31069a3213af7257caeaaccbd91b6f1929
3
+ metadata.gz: 7a444a825ab9cf0e6bd11fe14fb20c6a777e9282c1bea56cf5dc869087ce7a44
4
+ data.tar.gz: 4c698a81975a588e8a5c185d57f9ca76ae1d37200d2a258f5d38631de386e9dd
5
5
  SHA512:
6
- metadata.gz: bc9d26dacd98d979d1e110eaa4c7ac04451c8332658a7be2004bd54b9d9f94adf1e55f2ee5407fd2e9f0c27295f32f9f40c3667ae42cee5fb6f76bd1a3000b8c
7
- data.tar.gz: 224db61d880f272de498cd766d40e9641ade6fceb7c0626ba88a5cca3244561b542f846df8102a22dcb48b88b2bc2f4943a6b30d102391c36f3dde7f3ff8b9af
6
+ metadata.gz: 9659c57e4d0c9f884b52dc07cf7b4c1a707f78d27bf03d058db1c2bfe2c873141a7d128c56b933098fa61a1f8fc48cce1319fea298c15bc8c606bb665b6d7d88
7
+ data.tar.gz: f183fb08856e81963d90d03ba0319f2633a72634beb8b37b2a5cee6c66dfa61710de5532abb5c82dd059d0c8382b9968a824f2caefeee2a53213e2bab2bac0b3
@@ -9,10 +9,10 @@ assignees: ''
9
9
 
10
10
  ### System Information
11
11
 
12
- - **OS**: [e.g. macOS 10.14.6]
13
- - **Ruby**: [e.g. 2.6.1]
14
- - **Version**: [e.g. 0.7.37]
15
- - **OpenSSL**: [OpenSSL 1.1.1d 10 Sep 2019]
12
+ - **OS**: [e.g. macOS 10.15.4]
13
+ - **Ruby**: [e.g. 2.7.0]
14
+ - **Version**: [e.g. 0.7.38]
15
+ - **OpenSSL**: [OpenSSL 1.1.1f 20 Mar 2020]
16
16
 
17
17
  ### Description
18
18
 
@@ -37,11 +37,4 @@ A clear and concise description of what you expected to happen.
37
37
 
38
38
  ### Actual behavior
39
39
 
40
- **Smartphone (please complete the following information):**
41
- - Device: [e.g. iPhone6]
42
- - OS: [e.g. iOS8.1]
43
- - Browser [e.g. stock browser, safari]
44
- - Version [e.g. 22]
45
-
46
- **Additional context**
47
- Add any other context about the problem here.
40
+ A clear and concise description of what actually happened.
@@ -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: trusty
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 compile
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 {} +
@@ -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.43 (2020-11-03)
10
+
11
+ **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.
12
+
13
+ **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.
14
+
15
+ #### Change log v.0.7.42 (2020-08-02)
16
+
17
+ **Fix**: Implement fix suggested by @Shelvak (Néstor Coppi) in issue #98.
18
+
19
+ #### Change log v.0.7.41 (2020-07-24)
20
+
21
+ **Fix**: Hot Restart failed because listening sockets were cleared away. Credit to Néstor Coppi (@Shelvak) for exposing issue #97.
22
+
23
+ **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.
24
+
25
+ #### Change log v.0.7.40 (2020-05-23)
26
+
27
+ **Fix**: fixed TLS logging and performance issues exposed by Franck Gille (@fgi) in issue #93.
28
+
29
+ #### Change log v.0.7.39 (2020-05-18)
30
+
31
+ **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.
32
+
33
+ **Fix**: (`http`) fixes an issue with date calculation - exposed by Franck Gille (@fgi) in issue #92.
34
+
35
+ **Fix**: (`extconf`) fixes an installation concern raised by Benoit Daloze (@eregon), in issue #91. The default compiler is now used.
36
+
9
37
  #### Change log v.0.7.38
10
38
 
11
39
  **Fix**: (`http`) fixes an issue and improves support for chunked encoded payloads. Credit to Ian Ker-Seymer ( @ianks ) for exposing this, writing tests and opening both the issue #87 and the PR #88.
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- # iodine - a fast HTTP / Websocket Server with native Pub/Sub support for the new web
1
+ # iodine - Why Settle for a fast HTTP / WebSocket Server with native Pub/Sub?
2
+
2
3
  [![Gem](https://img.shields.io/gem/dt/iodine.svg)](https://rubygems.org/gems/iodine)
3
4
  [![Build Status](https://travis-ci.org/boazsegev/iodine.svg?branch=master)](https://travis-ci.org/boazsegev/iodine)
4
5
  [![Gem Version](https://badge.fury.io/rb/iodine.svg)](https://badge.fury.io/rb/iodine)
@@ -7,28 +8,30 @@
7
8
 
8
9
  [![Logo](https://github.com/boazsegev/iodine/raw/master/logo.png)](https://github.com/boazsegev/iodine)
9
10
 
10
- I believe that network concerns should be separated from application concerns - application developers really shouldn't need to worry about the transport layer.
11
+ Iodine is a fast concurrent web application server for real-time Ruby applications, with native support for WebSockets and Pub/Sub services - but it's also so much more.
11
12
 
12
- And I know that these network concerns are more than just about the web server, which is why iodine is more than just an HTTP server.
13
+ Iodine is a Ruby wrapper for many of the [facil.io](https://facil.io) C framework, leveraging the speed of C for many common web application tasks. In addition, iodine abstracts away all network concerns, so you never need to worry about the transport layer, free to concentrate on your application logic.
13
14
 
14
- Iodine is a fast concurrent web server for real-time Ruby applications, but it's also so much more. Iodine includes native support for:
15
+ Iodine includes native support for:
15
16
 
16
17
  * HTTP, WebSockets and EventSource (SSE) Services (server);
17
18
  * WebSocket connections (server / client);
18
19
  * Pub/Sub (with optional Redis Pub/Sub scaling);
19
- * Static file service (with automatic `gzip` support for pre-compressed versions);
20
- * HTTP/1.1 keep-alive and pipelining;
20
+ * Fast(!) builtin Mustache template engine.
21
+ * Static file service (with automatic `gzip` support for pre-compressed assets);
22
+ * Optimized Logging to `stderr`.
21
23
  * Asynchronous event scheduling and timers;
22
- * Hot Restart (using the USR1 signal);
24
+ * HTTP/1.1 keep-alive and pipelining;
25
+ * Heap Fragmentation Protection.
23
26
  * TLS 1.2 and above (Requires OpenSSL >= 1.1.0);
24
27
  * TCP/IP server and client connectivity;
25
28
  * Unix Socket server and client connectivity;
29
+ * Hot Restart (using the USR1 signal and without hot deployment);
26
30
  * Custom protocol authoring;
27
- * Optimized Logging to `stderr`.
28
31
  * [Sequel](https://github.com/jeremyevans/sequel) and ActiveRecord forking protection.
29
32
  * and more!
30
33
 
31
- Iodine is an **evented** framework with a simple API that ports much of the [C facil.io framework](https://github.com/boazsegev/facil.io) to Ruby. This means that:
34
+ Since iodine wraps much of the [C facil.io framework](https://github.com/boazsegev/facil.io) to Ruby:
32
35
 
33
36
  * Iodine can handle **thousands of concurrent connections** (tested with more then 20K connections on Linux)!
34
37
 
@@ -36,9 +39,9 @@ Iodine is an **evented** framework with a simple API that ports much of the [C f
36
39
 
37
40
  Iodine is a C extension for Ruby, developed and optimized for Ruby MRI 2.2.2 and up... it should support the whole Ruby 2.0 MRI family, but CI tests start at Ruby 2.2.2.
38
41
 
39
- **Note**: iodine does **not** support the streaming when using Rack. Streaming over Rack should be avoided on any server, WebSockets, SSE and `Range` requests should always be preferred. On iodine no data will be sent before the whole of the data is available.
42
+ **Note**: iodine does **not** support streaming when using Rack. It's recommended to avoid blocking the server when using `body.each` since the `each` loop will block the iodine's thread until it's finished and iodine won't send any data before the loop is done.
40
43
 
41
- ## Iodine - a fast & powerful HTTP + Websockets server with native Pub/Sub
44
+ ## Iodine - a fast & powerful HTTP + WebSockets server with native Pub/Sub
42
45
 
43
46
  Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC) and the [Websocket draft extension](./SPEC-Websocket-Draft.md).
44
47
 
@@ -46,6 +49,10 @@ With `Iodine.listen service: :http` it's possible to run multiple HTTP applicati
46
49
 
47
50
  Iodine also supports native process cluster Pub/Sub and a native RedisEngine to easily scale iodine's Pub/Sub horizontally.
48
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
+
49
56
  ### Installing and Running Iodine
50
57
 
51
58
  Install iodine on any Linux / BSD / macOS system using:
@@ -68,6 +75,8 @@ bundler exec iodine
68
75
 
69
76
  #### Installing with SSL/TLS
70
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
+
71
80
  Make sure to update OpenSSL to the latest version **before installing Ruby** (`rbenv` should do this automatically).
72
81
 
73
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.
@@ -85,9 +94,7 @@ Confirmed OpenSSL to be version 1.1.0 or above (OpenSSL 1.1.0j 20 Nov 2018)...
85
94
  ...
86
95
  ```
87
96
 
88
- **KNOWN ISSUE:**
89
-
90
- 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.:
91
98
 
92
99
  ```bash
93
100
  NO_SSL=1 bundler exec iodine
@@ -108,17 +115,19 @@ On Rails:
108
115
  if(defined?(Iodine))
109
116
  Iodine.threads = ENV.fetch("RAILS_MAX_THREADS", 5).to_i if Iodine.threads.zero?
110
117
  Iodine.workers = ENV.fetch("WEB_CONCURRENCY", 2).to_i if Iodine.workers.zero?
111
- Iodine::DEFAULT_SETTINGS[:port] = ENV.fetch("PORT") if ENV.fetch("PORT")
118
+ Iodine::DEFAULT_SETTINGS[:port] ||= ENV.fetch("PORT") if ENV.fetch("PORT")
112
119
  end
113
120
  ```
114
121
 
115
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.
116
123
 
124
+ **Note**: command-line instructions (CLI) should be the preferred way for configuring iodine, allowing for code-less configuration updates.
125
+
117
126
  ### Optimizing Iodine's Concurrency
118
127
 
119
128
  To get the most out of iodine, consider the amount of CPU cores available and the concurrency level the application requires.
120
129
 
121
- 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.
122
131
 
123
132
  Command line arguments allow easy access to different options, including concurrency levels. i.e., to set up 16 threads and 4 processes:
124
133
 
@@ -134,13 +143,19 @@ export WORKERS=-1 # negative values are fractions of CPU cores.
134
143
  bundler exec iodine -p $PORT
135
144
  ```
136
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
+
137
152
  ### Heap Fragmentation Protection
138
153
 
139
154
  Iodine includes a fast, network oriented, custom memory allocator, optimizing away some of the work usually placed on the Ruby Garbage Collector (GC).
140
155
 
141
156
  This approach helps to minimize heap fragmentation for long running processes, by grouping many short-lived objects into a common memory space.
142
157
 
143
- It's still recommended to consider [jemalloc](http://jemalloc.net) or other allocators that also help mitigate heap fragmentation issues.
158
+ It is still recommended to consider [jemalloc](http://jemalloc.net) or other allocators that also help mitigate heap fragmentation issues.
144
159
 
145
160
  ### Static file serving support
146
161
 
@@ -433,11 +448,11 @@ Iodine.connect url: "wss://echo.websocket.org", handler: EchoClient.new, ping: 4
433
448
  Iodine.start
434
449
  ```
435
450
 
436
- ### TLS 1.2 support
451
+ ### TLS >= 1.2 support
437
452
 
438
453
  > Requires OpenSSL >= `1.1.0`. On Heroku, requires `heroku-18`.
439
454
 
440
- 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).
441
456
 
442
457
  A self signed certificate is available using the `-tls` flag from the command-line.
443
458
 
@@ -478,31 +493,11 @@ run APP
478
493
 
479
494
  ### How does it compare to other servers?
480
495
 
481
- The honest answer is "I don't know". I recommend that you perform your own tests.
482
-
483
- 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).
484
497
 
485
- Also, performing benchmarks on a single machine isn't very reliable... but it's all I've got.
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').
486
499
 
487
- When benchmarking with `wrk`, on the same local machine with similar settings for both Puma and Iodine (4 workers, 16 threads each, 200 concurrent connections), I calculated Iodine to be x1.52 faster::
488
-
489
- * Iodine performed at 74,786.27 req/sec, consuming ~68.4Mb of memory.
490
-
491
- * Puma performed at 48,994.59 req/sec, consuming ~79.6Mb of memory.
492
-
493
- When benchmarking using a VM (crossing machine boundaries, 16 threads, 4 workers, 200 concurrent connections), I calculated Iodine to be x2.3 faster:
494
-
495
- * Iodine performed at 23,559.56 req/sec, consuming ~88.8Mb of memory.
496
-
497
- * Puma performed at 9,935.31 req/sec, consuming ~84.0Mb of memory.
498
-
499
- When benchmarking using a VM (crossing machine boundaries, single thread, single worker, 200 concurrent connections), I calculated Iodine to be x7.3 faster:
500
-
501
- * Iodine performed at 18,444.31 req/sec, consuming ~25.6Mb of memory.
502
-
503
- * Puma performed at 2,521.56 req/sec, consuming ~27.5Mb of memory.
504
-
505
- 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`:
506
501
 
507
502
  ```bash
508
503
  $ wrk -c200 -d4 -t2 http://localhost:3000/
@@ -510,7 +505,7 @@ $ wrk -c200 -d4 -t2 http://localhost:3000/
510
505
  $ ab -n 100000 -c 200 -k http://127.0.0.1:3000/
511
506
  ```
512
507
 
513
- Create a simple `config.ru` file with a hello world app:
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:
514
509
 
515
510
  ```ruby
516
511
  App = Proc.new do |env|
@@ -523,17 +518,21 @@ end
523
518
  run App
524
519
  ```
525
520
 
526
- Then start comparing servers. Here are the settings I used to compare iodine and Puma (4 processes, 16 threads):
521
+ Then start comparing servers. Here are the settings I used to compare iodine and Puma (4 processes, 4 threads):
527
522
 
528
523
  ```bash
529
- $ RACK_ENV=production iodine -p 3000 -t 16 -w 4
524
+ $ RACK_ENV=production iodine -p 3000 -t 4 -w 4
530
525
  # vs.
531
- $ RACK_ENV=production puma -p 3000 -t 16 -w 4
526
+ $ RACK_ENV=production puma -p 3000 -t 4 -w 4
532
527
  # Review the `iodine -?` help for more command line options.
533
528
  ```
534
529
 
535
530
  It's recommended that the servers (Iodine/Puma) and the client (`wrk`/`ab`) run on separate machines.
536
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
+
537
536
  ### A few notes
538
537
 
539
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.
@@ -577,8 +576,8 @@ Iodine is written in C and allows some compile-time customizations, such as:
577
576
  These options can be used, for example, like so:
578
577
 
579
578
  ```bash
580
- $ CFLAGS="-DFIO_FORCE_MALLOC=1 -DHTTP_MAX_HEADER_COUNT=64" \
581
- gem install iodine
579
+ gem install iodine -- \
580
+ --with-cflags=\"-DHTTP_MAX_HEADER_LENGTH=48000 -DFIO_FORCE_MALLOC=1 -DHTTP_MAX_HEADER_COUNT=64\"
582
581
  ```
583
582
 
584
583
  More possible compile time options can be found in the [facil.io documentation](http://facil.io).
@@ -770,6 +769,14 @@ Yes, please, here are some thoughts:
770
769
 
771
770
  * If you love the project or thought the code was nice, maybe helped you in your own project, drop me a line. I'd love to know.
772
771
 
772
+ ### Running the Tests
773
+
774
+ Running this task will compile the C extensions then run RSpec tests:
775
+
776
+ ```sh
777
+ bundle exec rake spec
778
+ ```
779
+
773
780
  ## License
774
781
 
775
782
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -3,10 +3,12 @@ require "rake/extensiontask"
3
3
 
4
4
  begin
5
5
  require 'rspec/core/rake_task'
6
- RSpec::Core::RakeTask.new(:spec)
6
+ RSpec::Core::RakeTask.new(:rspec)
7
7
  rescue LoadError
8
8
  end
9
9
 
10
+ task :spec => [:compile, :rspec]
11
+
10
12
  task :default => [:compile, :spec]
11
13
 
12
14
  Rake::ExtensionTask.new "iodine" do |ext|
@@ -1,66 +1,106 @@
1
- # Ruby Pub/Sub API Specification Draft
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
- The pub/sub design is idiomatic to WebSocket and EventSource approaches as well as other reactive programming techniques.
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 to offer a recommendation for pub/sub design that will allow applications to be implementation agnostic (not care which pub/sub extension is used)\*.
13
+ The purpose of this specification is:
8
14
 
9
- Simply put, applications will not have to worry about the chosen pub/sub implementation or about inter-process communication.
15
+ 1. To keep separation of concerns by avoiding inter-process-communication logic (IPC) in the application code base.
10
16
 
11
- This should simplify the idiomatic `subscribe` / `publish` approach to real-time data pushing.
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
- \* The pub/sub extension could be implemented by any external library as long as the API conforms to the specification. The extension will have to manage the fact that some servers `fork` and manage inter-process communication for pub/sub propagation (or limit it's support to specific servers). Also, servers that opt to implement the pub/sub layer, could perform optimizations related to connection handling and pub/sub lifetimes.
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
- ## Pub/Sub handling
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
- Conforming Pub/Sub implementations **MUST** implement the following pub/sub related methods within the WebSocket/SSE `client` object (as defined in the Rack WebSockets / EventSource specification draft):
23
+ 3. To provide common considerations and guidelines for pub/sub implementors to consider when implementing their pub/sub modules.
18
24
 
19
- * `subscribe(to, opt = {}) { |from, message| optional_block }` where `to` is a named *channel* and `opt` is a Hash object that *SHOULD* support the following possible keys (unsupported keys *MUST* be ignored):
25
+ Some concerns are common for pub/sub implementors, such as integrating third party message brokers (Redis, RabbitMQ, Cassandra)
20
26
 
21
- * `:match` indicates a matching algorithm should be applied to the `to` variable (`to` is a pattern).
22
-
23
- Possible (suggested) values should include [`:redis`](https://github.com/antirez/redis/blob/398b2084af067ae4d669e0ce5a63d3bc89c639d3/src/util.c#L46-L167), [`:nats`](https://nats.io/documentation/faq/#wildcards) or [`:rabbitmq`](https://www.rabbitmq.com/tutorials/tutorial-five-ruby.html). Pub/Sub implementations *MAY* support none, some or all of these common pattern resolution schemes.
24
-
25
- * `:handler` is an alternative to the optional block. It should accept Proc like objects (objects that answer to `.call(from, msg)`).
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
- * If an optional `block` (or `:handler`) is provided, if will be called when a publication was received. Otherwise, the message alone (**not** the channel data) **MUST** be sent directly to the WebSocket / EventSource client.
33
+ * `subscribe(to, is_pattern = false) { |from, message| optional_block }` where:
28
34
 
29
- * `:as` accepts either `:text` or `:binary` Symbol objects.
35
+ * `to` is a named **channel** (or **pattern**).
30
36
 
31
- This option is only valid if the optional `block` is missing and the connection is a WebSocket connection. Note that SSE connections are limited to text data by design.
37
+ The implementation **MAY** support pattern matching for named channels (`to`). The pattern matching algorithm used, if any, **SHOULD** be documented.
32
38
 
33
- This will dictate the encoding for outgoing WebSocket message when publications are directly sent to the client (as a text message or a binary blob). `:text` will be the default value for a missing `:as` option.
39
+ If the implementation does **NOT** support pattern matching and `is_pattern` is truthful, the implementation **MUST** raise and exception.
34
40
 
35
- Servers *MAY* ignore this value if they set the message type (text/binary) based on UTF-8 validation.
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
- If a subscription to `to` already exists, it should be *replaced* by the new subscription (the old subscription should be canceled / unsubscribed).
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
- When the `subscribe` method is called within a WebSocket / SSE Callback object, the subscription must be closed automatically when the connection is closed.
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
- A global variation for this method (allowing global subscriptions to be created) **SHOULD** be made available as `Rack::PubSub.subscribe`.
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
- When the `subscribe` method isn't called from within a connection, it should be considered a global (non connection related) subscription and an exception should be raised if a `block` or `:handler` isn't provided by the user.
65
+ * `to` is a named channel, same as detailed in `subscribe`.
46
66
 
47
- * `unsubscribe(from)` should cancel a subscription to the `from` named channel / pattern.
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
- * `publish(to, message, engine = nil)` (preferably supporting named arguments) where:
69
+ * `message` a String containing the data to be published.
50
70
 
51
- * `to` a String that identifies the channel / stream / subject for the publication ("channel" is the semantic used by Redis, it is similar to "subject" or "stream" in other pub/sub systems).
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
- * `message` a String with containing the data to be published.
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 Pub/Sub Engine (see later on). If none is specified, the default engine should be used. If `false` is specified, the message should be forwarded to all subscribed clients.
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 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`.
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
- Implementations **MUST** implement the following methods in one of their public classes / modules (iodine implements these under `Iodine::PubSub`):
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
- Removes an engine from the pub/sub system. The opposite of `attach`.
115
+ The implementation **MUST** remove the engine from the attached engine list. The opposite of `attach`.
76
116
 
77
- * `default = engine` sets a default pub/sub engine, where `engine` is a PubSubEngine object as described in this specification.
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
- Implementations **MAY** implement pub/sub internally (in which case the `default` engine is the server itself or a server's module).
127
+ A `PubSubEngine` instance object **MUST** implement the following methods:
88
128
 
89
- However, servers **MUST** support external pub/sub "engines" as described above, using PubSubEngine objects.
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
- `PubSubEngine` objects **MUST** implement the following methods:
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
- * `subscribe(channel, match=nil)` this method performs the subscription to the specified channel.
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
- If `match` is a Symbol that the engine recognizes (i.e., `:redis`, `:nats`, etc'), the engine should behave accordingly. i.e., the value `:redis` on a Redis engine will invoke the PSUBSCRIBE Redis command.
135
+ This method **MUST NOT** raise an exception.
96
136
 
97
- The method must return `true` if a subscription was scheduled (or performed) or `false` if the subscription is known to fail.
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
- This method will be called by the server (for each registered engine). The engine may assume that the method would never be called directly by an application.
139
+ The method's semantics are similar to `subscribe` only is performs the opposite action.
100
140
 
101
- * `unsubscribe(channel, match=nil)` this method performs closes the subscription to the specified channel.
141
+ This method **MUST NOT** raise an exception.
102
142
 
103
- The method's semantics are similar to `subscribe`.
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
- This method will be called by the server (for each registered engine). The engine may assume that the method would never be called directly by an application.
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
- The engine **MUST** assume that the method might get called directly by an application.
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
- When a PubSubEngine object receives a published message, it *should* call:
155
+ i.e., if the implementation's global `publish` method is in a class called `Iodine`:
114
156
 
115
157
  ```ruby
116
- Foo::PubSub.publish channel, message, false
158
+ Iodine.publish channel, message, false
117
159
  ```