message_bus 2.1.6 → 2.2.0.pre
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of message_bus might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -92
- data/.rubocop_todo.yml +659 -0
- data/.travis.yml +1 -1
- data/CHANGELOG +61 -0
- data/Dockerfile +18 -0
- data/Gemfile +3 -1
- data/Guardfile +0 -1
- data/README.md +188 -101
- data/Rakefile +12 -1
- data/assets/message-bus.js +1 -1
- data/docker-compose.yml +46 -0
- data/examples/bench/config.ru +8 -9
- data/examples/bench/unicorn.conf.rb +1 -1
- data/examples/chat/chat.rb +150 -153
- data/examples/minimal/config.ru +2 -3
- data/lib/message_bus.rb +224 -36
- data/lib/message_bus/backends.rb +7 -0
- data/lib/message_bus/backends/base.rb +184 -0
- data/lib/message_bus/backends/memory.rb +304 -226
- data/lib/message_bus/backends/postgres.rb +359 -318
- data/lib/message_bus/backends/redis.rb +380 -337
- data/lib/message_bus/client.rb +99 -41
- data/lib/message_bus/connection_manager.rb +29 -21
- data/lib/message_bus/diagnostics.rb +50 -41
- data/lib/message_bus/distributed_cache.rb +5 -7
- data/lib/message_bus/message.rb +2 -2
- data/lib/message_bus/rack/diagnostics.rb +65 -55
- data/lib/message_bus/rack/middleware.rb +64 -44
- data/lib/message_bus/rack/thin_ext.rb +13 -9
- data/lib/message_bus/rails/railtie.rb +2 -0
- data/lib/message_bus/timer_thread.rb +2 -2
- data/lib/message_bus/version.rb +2 -1
- data/message_bus.gemspec +3 -2
- data/spec/assets/support/jasmine_helper.rb +1 -1
- data/spec/lib/fake_async_middleware.rb +1 -6
- data/spec/lib/message_bus/assets/asset_encoding_spec.rb +3 -3
- data/spec/lib/message_bus/backend_spec.rb +409 -0
- data/spec/lib/message_bus/client_spec.rb +8 -11
- data/spec/lib/message_bus/connection_manager_spec.rb +8 -14
- data/spec/lib/message_bus/distributed_cache_spec.rb +0 -4
- data/spec/lib/message_bus/multi_process_spec.rb +6 -7
- data/spec/lib/message_bus/rack/middleware_spec.rb +47 -43
- data/spec/lib/message_bus/timer_thread_spec.rb +0 -2
- data/spec/lib/message_bus_spec.rb +59 -43
- data/spec/spec_helper.rb +16 -4
- metadata +12 -9
- data/spec/lib/message_bus/backends/postgres_spec.rb +0 -221
- data/spec/lib/message_bus/backends/redis_spec.rb +0 -271
data/.travis.yml
CHANGED
data/CHANGELOG
CHANGED
@@ -1,3 +1,64 @@
|
|
1
|
+
30-11-2018
|
2
|
+
|
3
|
+
- Version 2.2.0.pre
|
4
|
+
|
5
|
+
- FIX: In redis backend we now expire the key used to track channel id this can cause a redis key leak
|
6
|
+
with large amounts of subscriptions that go away
|
7
|
+
- FEATURE: Much extra implementation documentation, and some improvements to usage documentation.
|
8
|
+
- FEATURE: Improvements to development workflow:
|
9
|
+
- Fully docker-based development and testing, with no other dependencies.
|
10
|
+
- More stringent Rubocop enforcement and greater compliance.
|
11
|
+
- Testing supported Ruby versions
|
12
|
+
- Better test coverage of some features
|
13
|
+
- Improved testing and assertion of the differences between backends
|
14
|
+
- More stable tests - now pass pretty much every run
|
15
|
+
- Documentation syntax is verified as part of testing
|
16
|
+
- FEATURE: Ruby 2.2.0 is no longer supported as it is EOL
|
17
|
+
- FEATURE: Better feature parity between backends:
|
18
|
+
- Adds support for backlog expiry to memory backend
|
19
|
+
- Support setting backlog expiry on publication w/ Postgres
|
20
|
+
- Supports setting backlog size on publication for memory/postgres
|
21
|
+
- FEATURE: `MessageBus.off` now prevents the server subscription from starting up.
|
22
|
+
- FEATURE: Trims unused parts of the public API:
|
23
|
+
- Methods removed:
|
24
|
+
* ConnectionManager#stats (never used and the ConnectionManager is not exposed to application code)
|
25
|
+
* Client#cancel (effectively duplicate of Client#close and the Client is only available via the ConnectionManager, thus not available to application code)
|
26
|
+
|
27
|
+
- Methods made private:
|
28
|
+
* MessageBus::Implementation#encode_channel_name
|
29
|
+
* MessageBus::Implementation#decode_channel_name
|
30
|
+
* Client#in_async?
|
31
|
+
* Client#ensure_closed!
|
32
|
+
* ConnectionManager#subscribe_client
|
33
|
+
* Diagnostics.full_process_path
|
34
|
+
* Diagnostics.hostname
|
35
|
+
* MessageBus::Rack::Diagnostics#js_asset
|
36
|
+
* MessageBus::Rack::Diagnostics#generate_script_tag
|
37
|
+
* MessageBus::Rack::Diagnostics#file_hash
|
38
|
+
* MessageBus::Rack::Diagnostics#asset_contents
|
39
|
+
* MessageBus::Rack::Diagnostics#asset_path
|
40
|
+
* MessageBus::Rack::Diagnostics#index
|
41
|
+
* MessageBus::Rack::Diagnostics#translate_handlebars
|
42
|
+
* MessageBus::Rack::Diagnostics#indent
|
43
|
+
* MessageBus::Rack::Middleware#start_listener
|
44
|
+
* MessageBus::Rack::Middleware#close_db_connection!
|
45
|
+
* MessageBus::Rack::Middleware#add_client_with_timeout
|
46
|
+
|
47
|
+
- Methods switched from protected to private:
|
48
|
+
* MessageBus::Implementation#global?
|
49
|
+
* MessageBus::Implementation#decode_message!
|
50
|
+
* MessageBus::Implementation#replay_backlog
|
51
|
+
* MessageBus::Implementation#subscribe_impl
|
52
|
+
* MessageBus::Implementation#unsubscribe_impl
|
53
|
+
* MessageBus::Implementation#ensure_subscriber_thread
|
54
|
+
* MessageBus::Implementation#new_subscriber_thread
|
55
|
+
* MessageBus::Implementation#global_subscribe_thread
|
56
|
+
* MessageBus::Implementation#multi_each
|
57
|
+
* Client#write_headers
|
58
|
+
* Client#write_chunk
|
59
|
+
* Client#write_and_close
|
60
|
+
* Client#messages_to_json
|
61
|
+
|
1
62
|
15-10-2018
|
2
63
|
|
3
64
|
- Version 2.1.6
|
data/Dockerfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
FROM ruby:2.5
|
2
|
+
|
3
|
+
RUN cd /tmp && \
|
4
|
+
wget --quiet https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 && \
|
5
|
+
tar -xf phantomjs-2.1.1-linux-x86_64.tar.bz2 && \
|
6
|
+
mv phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin && \
|
7
|
+
rm -rf phantomjs*
|
8
|
+
|
9
|
+
WORKDIR /usr/src/app
|
10
|
+
|
11
|
+
RUN mkdir -p ./lib/message_bus
|
12
|
+
COPY lib/message_bus/version.rb ./lib/message_bus
|
13
|
+
COPY Gemfile Gemfile.lock *.gemspec ./
|
14
|
+
RUN bundle install
|
15
|
+
|
16
|
+
COPY . .
|
17
|
+
|
18
|
+
CMD ["rake"]
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -4,34 +4,23 @@ A reliable, robust messaging bus for Ruby processes and web clients.
|
|
4
4
|
|
5
5
|
MessageBus implements a Server to Server channel based protocol and Server to Web Client protocol (using polling, long-polling or long-polling + streaming)
|
6
6
|
|
7
|
-
|
7
|
+
Since long-polling is implemented using Rack Hijack and Thin::Async, all common Ruby web servers (Thin, Puma, Unicorn, Passenger) can run MessageBus and handle a large number of concurrent connections that wait on messages.
|
8
8
|
|
9
|
-
MessageBus is implemented as Rack middleware and can be used by
|
9
|
+
MessageBus is implemented as Rack middleware and can be used by any Rails / Sinatra or pure Rack application.
|
10
10
|
|
11
|
-
|
11
|
+
## Try it out!
|
12
12
|
|
13
13
|
Live chat demo per [examples/chat](https://github.com/SamSaffron/message_bus/tree/master/examples/chat) is at:
|
14
14
|
|
15
15
|
### http://chat.samsaffron.com
|
16
16
|
|
17
|
-
## Want to help?
|
18
|
-
|
19
|
-
If you are looking to contribute to this project here are some ideas
|
20
|
-
|
21
|
-
- MAKE THIS README BETTER!
|
22
|
-
- Build backends for other providers (zeromq, rabbitmq, disque) - currently we support pg and redis.
|
23
|
-
- Improve and properly document admin dashboard (add opt-in stats, better diagnostics into queues)
|
24
|
-
- Improve general documentation (Add examples, refine existing examples)
|
25
|
-
- Make MessageBus a nice website
|
26
|
-
- Add optional transports for websocket and shared web workers
|
27
|
-
|
28
17
|
## Ruby version support
|
29
18
|
|
30
|
-
MessageBus only support officially supported versions of Ruby
|
19
|
+
MessageBus only support officially supported versions of Ruby; as of [2018-06-20](https://www.ruby-lang.org/en/news/2018/06/20/support-of-ruby-2-2-has-ended/) this means we only support Ruby version 2.3 and up.
|
31
20
|
|
32
21
|
## Can you handle concurrent requests?
|
33
22
|
|
34
|
-
**Yes**, MessageBus uses Rack Hijack
|
23
|
+
**Yes**, MessageBus uses Rack Hijack and this interface allows us to take control of the underlying socket. MessageBus can handle thousands of concurrent long polls on all popular Ruby webservers. MessageBus runs as middleware in your Rack (or by extension Rails) application and does not require a dedicated server. Background work is minimized to ensure it does not interfere with existing non-MessageBus traffic.
|
35
24
|
|
36
25
|
## Is this used in production at scale?
|
37
26
|
|
@@ -58,9 +47,6 @@ Server to Server messaging
|
|
58
47
|
```ruby
|
59
48
|
message_id = MessageBus.publish "/channel", "message"
|
60
49
|
|
61
|
-
# last id in a channel
|
62
|
-
id = MessageBus.last_id("/channel")
|
63
|
-
|
64
50
|
# in another process / spot
|
65
51
|
|
66
52
|
MessageBus.subscribe "/channel" do |msg|
|
@@ -76,10 +62,17 @@ end
|
|
76
62
|
MessageBus.subscribe "/channel", 5 do |msg|
|
77
63
|
# block called in a background thread when message is received
|
78
64
|
end
|
65
|
+
```
|
79
66
|
|
67
|
+
```ruby
|
68
|
+
# get the ID of the last message on a channel
|
69
|
+
id = MessageBus.last_id("/channel")
|
70
|
+
|
71
|
+
# returns all messages after some id
|
80
72
|
MessageBus.backlog "/channel", id
|
81
|
-
|
73
|
+
```
|
82
74
|
|
75
|
+
```ruby
|
83
76
|
# messages can be targetted at particular users or groups
|
84
77
|
MessageBus.publish "/channel", "hello", user_ids: [1,2,3], group_ids: [4,5,6]
|
85
78
|
|
@@ -101,12 +94,6 @@ MessageBus.configure(group_ids_lookup: proc do |env|
|
|
101
94
|
# can be nil or []
|
102
95
|
end)
|
103
96
|
|
104
|
-
MessageBus.configure(on_middleware_error: proc do |env, e|
|
105
|
-
# If you wish to add special handling based on error
|
106
|
-
# return a rack result array: [status, headers, body]
|
107
|
-
# If you just want to pass it on return nil
|
108
|
-
end)
|
109
|
-
|
110
97
|
# example of message bus to set user_ids from an initializer in Rails and Devise:
|
111
98
|
# config/inializers/message_bus.rb
|
112
99
|
MessageBus.user_id_lookup do |env|
|
@@ -118,17 +105,29 @@ MessageBus.user_id_lookup do |env|
|
|
118
105
|
end
|
119
106
|
```
|
120
107
|
|
108
|
+
```ruby
|
109
|
+
MessageBus.configure(on_middleware_error: proc do |env, e|
|
110
|
+
# If you wish to add special handling based on error
|
111
|
+
# return a rack result array: [status, headers, body]
|
112
|
+
# If you just want to pass it on return nil
|
113
|
+
end)
|
114
|
+
```
|
115
|
+
|
116
|
+
#### Disabling message_bus
|
117
|
+
|
118
|
+
In certain cases, it is undesirable for message_bus to start up on application start, for example in a Rails application during the `db:create` rake task when using the Postgres backend (which will error trying to connect to the non-existent database to subscribe). You can invoke `MessageBus.off` before the middleware chain is loaded in order to prevent subscriptions and publications from happening; in a Rails app you might do this in an initializer based on some environment variable or some other conditional means.
|
119
|
+
|
121
120
|
### Debugging
|
122
121
|
|
123
|
-
When setting up MessageBus, it's
|
122
|
+
When setting up MessageBus, it's useful to manually inspect channels before integrating a client application.
|
124
123
|
|
125
|
-
You can `curl` MessageBus
|
124
|
+
You can `curl` MessageBus; this is helpful when trying to debug what may be going wrong. This example uses https://chat.samsaffron.com:
|
126
125
|
|
127
126
|
```
|
128
127
|
curl -H "Content-Type: application/x-www-form-urlencoded" -X POST --data "/message=0" https://chat.samsaffron.com/message-bus/client-id/poll\?dlp\=t
|
129
128
|
```
|
130
129
|
|
131
|
-
You should see a reply with the messages of that channel you requested
|
130
|
+
You should see a reply with the messages of that channel you requested (in this case `/message`) starting at the message ID you requested (`0`). The URL parameter `dlp=t` disables long-polling: we do not want this request to stay open.
|
132
131
|
|
133
132
|
### Transport
|
134
133
|
|
@@ -138,42 +137,42 @@ MessageBus ships with 3 transport mechanisms.
|
|
138
137
|
2. Long Polling
|
139
138
|
3. Polling
|
140
139
|
|
141
|
-
Long Polling with chunked encoding allows a single connection to stream multiple messages to a client, this requires HTTP/1.1
|
140
|
+
Long Polling with chunked encoding allows a single connection to stream multiple messages to a client, and this requires HTTP/1.1.
|
142
141
|
|
143
142
|
Chunked encoding provides all the benefits of [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) with greater browser support (as it works on IE10 and up as well)
|
144
143
|
|
145
|
-
To setup NGINX to proxy to your app correctly be sure to enable HTTP1.1 and disable buffering
|
144
|
+
To setup NGINX to proxy to your app correctly be sure to enable HTTP1.1 and disable buffering:
|
146
145
|
|
147
146
|
```
|
148
147
|
location /message-bus/ {
|
149
148
|
...
|
150
|
-
proxy_buffering off;
|
151
149
|
proxy_http_version 1.1;
|
150
|
+
proxy_buffering off;
|
152
151
|
...
|
153
152
|
}
|
154
153
|
```
|
155
154
|
|
156
155
|
**NOTE**: do not set proxy_buffering off globally, it may have unintended consequences.
|
157
156
|
|
158
|
-
|
157
|
+
In order to disable chunked encoding for a specific client in Javascript:
|
159
158
|
|
160
|
-
```
|
161
|
-
MessageBus.enableChunkedEncoding = false;
|
159
|
+
```javascript
|
160
|
+
MessageBus.enableChunkedEncoding = false;
|
162
161
|
```
|
163
162
|
|
164
|
-
|
163
|
+
or as a server-side policy in Ruby for all clients:
|
165
164
|
|
166
165
|
```ruby
|
167
|
-
MessageBus.configure(chunked_encoding_enabled: false)
|
166
|
+
MessageBus.configure(chunked_encoding_enabled: false)
|
168
167
|
```
|
169
168
|
|
170
|
-
Long Polling requires no special setup
|
169
|
+
Long Polling requires no special setup; as soon as new data arrives on the channel the server delivers the data and closes the connection.
|
171
170
|
|
172
|
-
Polling also requires no special setup
|
171
|
+
Polling also requires no special setup; MessageBus will fallback to polling after a tab becomes inactive and remains inactive for a period.
|
173
172
|
|
174
173
|
### Multisite support
|
175
174
|
|
176
|
-
MessageBus can be used in an environment that hosts multiple sites by multiplexing channels. To use this mode
|
175
|
+
MessageBus can be used in an environment that hosts multiple sites by multiplexing channels. To use this mode:
|
177
176
|
|
178
177
|
```ruby
|
179
178
|
# define a site_id lookup method, which is executed
|
@@ -185,33 +184,33 @@ end)
|
|
185
184
|
# you may post messages just to this site
|
186
185
|
MessageBus.publish "/channel", "some message"
|
187
186
|
|
188
|
-
# you can also choose to pass the
|
187
|
+
# you can also choose to pass the `:site_id`.
|
189
188
|
# This takes precendence over whatever `site_id_lookup`
|
190
189
|
# returns
|
191
190
|
MessageBus.publish "/channel", "some message", site_id: "site-id"
|
192
191
|
|
193
192
|
# you may publish messages to ALL sites using the /global/ prefix
|
194
193
|
MessageBus.publish "/global/channel", "will go to all sites"
|
195
|
-
|
196
194
|
```
|
197
195
|
|
198
196
|
### Client support
|
199
197
|
|
200
198
|
MessageBus ships a simple ~300 line JavaScript library which provides an API to interact with the server.
|
201
199
|
|
202
|
-
|
203
|
-
JavaScript can listen on any channel (and receive notification via polling or long polling):
|
200
|
+
JavaScript clients can listen on any channel and receive messages via polling or long polling. You may simply include the source file (located in `assets/` within the message_bus source code):
|
204
201
|
|
205
202
|
```html
|
206
203
|
<script src="message-bus.js" type="text/javascript"></script>
|
207
204
|
```
|
208
|
-
Note, the message-bus.js file is located in the assets folder.
|
209
205
|
|
210
|
-
|
206
|
+
or when used in a Rails application, import it through the asset pipeline:
|
207
|
+
|
211
208
|
```javascript
|
212
209
|
//= require message-bus
|
213
210
|
```
|
214
211
|
|
212
|
+
In your application Javascript, you can then subscribe to particular channels and define callback functions to be executed when messages are received:
|
213
|
+
|
215
214
|
```javascript
|
216
215
|
MessageBus.start(); // call once at startup
|
217
216
|
|
@@ -223,13 +222,11 @@ MessageBus.subscribe("/channel", function(data){
|
|
223
222
|
// data shipped from server
|
224
223
|
});
|
225
224
|
|
226
|
-
|
227
225
|
// you will get all new messages sent to channel (-1 is implicit)
|
228
226
|
MessageBus.subscribe("/channel", function(data){
|
229
227
|
// data shipped from server
|
230
228
|
}, -1);
|
231
229
|
|
232
|
-
|
233
230
|
// all messages AFTER message id 7 AND all new messages
|
234
231
|
MessageBus.subscribe("/channel", function(data){
|
235
232
|
// data shipped from server
|
@@ -241,68 +238,59 @@ MessageBus.subscribe("/channel", function(data){
|
|
241
238
|
}, -3);
|
242
239
|
```
|
243
240
|
|
244
|
-
There is also a Ruby implementation of the client library
|
245
|
-
[message_bus-client](https://github.com/lowjoel/message_bus-client) with the API very similar to
|
246
|
-
that of the JavaScript client.
|
247
|
-
|
248
|
-
**Client settings**:
|
241
|
+
There is also [a Ruby implementation of the client library](https://github.com/lowjoel/message_bus-client) available with an API very similar to that of the JavaScript client.
|
249
242
|
|
243
|
+
#### Client settings
|
250
244
|
|
251
245
|
All client settings are settable via `MessageBus.OPTION`
|
252
246
|
|
253
247
|
Setting|Default|Info
|
254
248
|
----|---|---|
|
255
|
-
enableLongPolling|true|Allow long-polling (provided it is
|
249
|
+
enableLongPolling|true|Allow long-polling (provided it is enabled by the server)
|
256
250
|
callbackInterval|15000|Safeguard to ensure background polling does not exceed this interval (in milliseconds)
|
257
251
|
backgroundCallbackInterval|60000|Interval to poll when long polling is disabled (either explicitly or due to browser being in background)
|
258
252
|
minPollInterval|100|When polling requests succeed, this is the minimum amount of time to wait before making the next request.
|
259
253
|
maxPollInterval|180000|If request to the server start failing, MessageBus will backoff, this is the upper limit of the backoff.
|
260
254
|
alwaysLongPoll|false|For debugging you may want to disable the "is browser in background" check and always long-poll
|
261
|
-
baseUrl|/|If message bus is mounted
|
262
|
-
ajax|$.ajax
|
263
|
-
headers|{}|Extra headers to be include with
|
255
|
+
baseUrl|/|If message bus is mounted at a sub-path or different domain, you may configure it to perform requests there
|
256
|
+
ajax|$.ajax falling back to XMLHttpRequest|MessageBus will first attempt to use jQuery and then fallback to a plain XMLHttpRequest version that's contained in the `message-bus-ajax.js` file. `message-bus-ajax.js` must be loaded after `message-bus.js` for it to be used. You may override this option with a function that implements an ajax request by some other means
|
257
|
+
headers|{}|Extra headers to be include with requests. Properties and values of object must be valid values for HTTP Headers, i.e. no spaces or control characters.
|
264
258
|
minHiddenPollInterval|1500|Time to wait between poll requests performed by background or hidden tabs and windows, shared state via localStorage
|
265
|
-
enableChunkedEncoding|true|
|
259
|
+
enableChunkedEncoding|true|Allows streaming of message bus data over the HTTP connection without closing the connection after each message.
|
266
260
|
|
267
|
-
|
261
|
+
#### Client API
|
268
262
|
|
269
|
-
`MessageBus.
|
263
|
+
`MessageBus.start()` : Starts up the MessageBus poller
|
270
264
|
|
271
|
-
`MessageBus.
|
265
|
+
`MessageBus.subscribe(channel,func,lastId)` : Subscribes to a channel. You may optionally specify the id of the last message you received in the channel. The callback receives three arguments on message delivery: `func(payload, globalId, messageId)`. You may save globalId or messageId of received messages and use then at a later time when client needs to subscribe, receiving the backlog since that id.
|
272
266
|
|
273
|
-
`MessageBus.
|
267
|
+
`MessageBus.unsubscribe(channel,func)` : Removes a subscription from a particular channel that was defined with a particular callback function (optional).
|
274
268
|
|
275
|
-
`MessageBus.
|
269
|
+
`MessageBus.pause()` : Pauses all MessageBus activity
|
276
270
|
|
277
|
-
`MessageBus.
|
271
|
+
`MessageBus.resume()` : Resumes MessageBus activity
|
278
272
|
|
279
|
-
`MessageBus.
|
273
|
+
`MessageBus.stop()` : Stops all MessageBus activity
|
280
274
|
|
281
|
-
`MessageBus.
|
275
|
+
`MessageBus.status()` : Returns status (started, paused, stopped)
|
282
276
|
|
283
|
-
`MessageBus.
|
284
|
-
|
285
|
-
`MessageBus.noConflict()` : Removes MessageBus from the global namespace by replacing it with whatever was present before MessageBus was loaded. Returns a reference to the MessageBus object.
|
286
|
-
|
287
|
-
## Running tests
|
288
|
-
|
289
|
-
To run tests you need both Postgres and Redis installed. By default we will connect to the database `message_bus_test` with the current username. If you wish to override this:
|
290
|
-
|
291
|
-
```
|
292
|
-
PGUSER=some_user PGDATABASE=some_db bundle exec rake
|
293
|
-
```
|
277
|
+
`MessageBus.noConflict()` : Removes MessageBus from the global namespace by replacing it with whatever was present before MessageBus was loaded. Returns a reference to the MessageBus object.
|
294
278
|
|
279
|
+
`MessageBus.diagnostics()` : Returns a log that may be used for diagnostics on the status of message bus.
|
295
280
|
|
296
281
|
## Configuration
|
297
282
|
|
283
|
+
message_bus can be configured to use one of several available storage backends, and each has its own configuration options.
|
284
|
+
|
298
285
|
### Redis
|
299
286
|
|
300
|
-
|
287
|
+
message_bus supports using Redis as a storage backend, and you can configure message_bus to use redis in `config/initializers/message_bus.rb`, like so:
|
301
288
|
|
302
289
|
```ruby
|
303
290
|
MessageBus.configure(backend: :redis, url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
|
304
291
|
```
|
305
|
-
|
292
|
+
|
293
|
+
The redis client message_bus uses is [redis-rb](https://github.com/redis/redis-rb), so you can visit it's repo to see what other options you can pass besides a `url`.
|
306
294
|
|
307
295
|
#### Data Retention
|
308
296
|
|
@@ -319,24 +307,23 @@ MessageBus.reliable_pub_sub.max_global_backlog_size = 100
|
|
319
307
|
|
320
308
|
# flush per-channel backlog after 100 seconds of inactivity
|
321
309
|
MessageBus.reliable_pub_sub.max_backlog_age = 100
|
322
|
-
|
323
310
|
```
|
324
311
|
|
325
312
|
### PostgreSQL
|
326
313
|
|
327
|
-
message_bus also supports PostgreSQL as
|
314
|
+
message_bus also supports PostgreSQL as a backend, and can be configured like so:
|
328
315
|
|
329
316
|
```ruby
|
330
317
|
MessageBus.configure(backend: :postgres, backend_options: {user: 'message_bus', dbname: 'message_bus'})
|
331
318
|
```
|
332
319
|
|
333
|
-
The PostgreSQL client message_bus uses is [ruby-pg](https://bitbucket.org/ged/ruby-pg), so you can visit it's repo to see what options you can
|
320
|
+
The PostgreSQL client message_bus uses is [ruby-pg](https://bitbucket.org/ged/ruby-pg), so you can visit it's repo to see what options you can include in `:backend_options`.
|
334
321
|
|
335
|
-
A `:clear_every` option is also supported, which
|
322
|
+
A `:clear_every` option is also supported, which limits backlog trimming frequency to the specified number of publications. If you set `clear_every: 100`, the backlog will only be cleared every 100 publications. This can improve performance in cases where exact backlog length limiting is not required.
|
336
323
|
|
337
324
|
### Memory
|
338
325
|
|
339
|
-
message_bus also supports an in-memory backend.
|
326
|
+
message_bus also supports an in-memory backend. This can be used for testing or simple single-process environments that do not require persistence or horizontal scalability.
|
340
327
|
|
341
328
|
```ruby
|
342
329
|
MessageBus.configure(backend: :memory)
|
@@ -346,9 +333,10 @@ The `:clear_every` option supported by the PostgreSQL backend is also supported
|
|
346
333
|
|
347
334
|
### Forking/threading app servers
|
348
335
|
|
349
|
-
If you're using a forking or threading app server and you're not getting immediate
|
336
|
+
If you're using a forking or threading app server and you're not getting immediate delivery of published messages, you might need to configure your web server to re-connect to the message_bus backend
|
350
337
|
|
351
338
|
#### Passenger
|
339
|
+
|
352
340
|
```ruby
|
353
341
|
# Rails: config/application.rb or config.ru
|
354
342
|
if defined?(PhusionPassenger)
|
@@ -365,22 +353,25 @@ end
|
|
365
353
|
|
366
354
|
MessageBus uses long polling which needs to be configured in Passenger
|
367
355
|
|
368
|
-
|
356
|
+
For passenger version < 5.0.21, add the following to `application.rb`:
|
369
357
|
|
370
|
-
|
358
|
+
```ruby
|
359
|
+
PhusionPassenger.advertised_concurrency_level = 0
|
360
|
+
```
|
371
361
|
|
372
|
-
|
362
|
+
For passenger version > 5.0.21, add the following to `nginx.conf`:
|
373
363
|
|
374
364
|
```
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
365
|
+
location /message-bus {
|
366
|
+
passenger_app_group_name foo_websocket;
|
367
|
+
passenger_force_max_concurrent_requests_per_process 0;
|
368
|
+
}
|
379
369
|
```
|
380
|
-
|
381
|
-
For more information see [Passenger documentation](https://www.phusionpassenger.com/library/config/nginx/tuning_sse_and_websockets/)
|
370
|
+
|
371
|
+
For more information see the [Passenger documentation](https://www.phusionpassenger.com/library/config/nginx/tuning_sse_and_websockets/) on long-polling.
|
382
372
|
|
383
373
|
#### Puma
|
374
|
+
|
384
375
|
```ruby
|
385
376
|
# path/to/your/config/puma.rb
|
386
377
|
require 'message_bus' # omit this line for Rails 5
|
@@ -390,6 +381,7 @@ end
|
|
390
381
|
```
|
391
382
|
|
392
383
|
#### Unicorn
|
384
|
+
|
393
385
|
```ruby
|
394
386
|
# path/to/your/config/unicorn.rb
|
395
387
|
require 'message_bus'
|
@@ -404,7 +396,7 @@ MessageBus middleware has to show up after the session middleware, but depending
|
|
404
396
|
|
405
397
|
For APIs or apps that have `ActionDispatch::Flash` deleted from the stack the middleware is pushed to the bottom.
|
406
398
|
|
407
|
-
Should you
|
399
|
+
Should you wish to manipulate the default behavior please refer to [Rails MiddlewareStackProxy documentation](http://api.rubyonrails.org/classes/Rails/Configuration/MiddlewareStackProxy.html) and alter the order of the middlewares in stack in `app/config/initializers/message_bus.rb`
|
408
400
|
|
409
401
|
```ruby
|
410
402
|
# config/initializers/message_bus.rb
|
@@ -415,8 +407,7 @@ end
|
|
415
407
|
|
416
408
|
### A Distributed Cache
|
417
409
|
|
418
|
-
MessageBus ships with an optional DistributedCache
|
419
|
-
It allows you a simple and efficient way of synchronizing a cache between processes.
|
410
|
+
MessageBus ships with an optional `DistributedCache` API which provides a simple and efficient way of synchronizing a cache between processes, based on the core of message_bus:
|
420
411
|
|
421
412
|
```ruby
|
422
413
|
require 'message_bus/distributed_cache'
|
@@ -444,10 +435,9 @@ cache["frogs"] = nil
|
|
444
435
|
|
445
436
|
puts cache["frogs"]
|
446
437
|
# => nil
|
447
|
-
|
448
438
|
```
|
449
439
|
|
450
|
-
|
440
|
+
You can automatically expire the cache on application code changes by scoping the cache to a specific version of the application:
|
451
441
|
|
452
442
|
```ruby
|
453
443
|
cache = MessageBus::DistributedCache.new("cache name", app_version: "12.1.7.ABDEB")
|
@@ -461,7 +451,7 @@ puts cache["a"]
|
|
461
451
|
|
462
452
|
#### Error Handling
|
463
453
|
|
464
|
-
The internet is a chaotic environment and clients can drop off for a variety of reasons. If this happens while
|
454
|
+
The internet is a chaotic environment and clients can drop off for a variety of reasons. If this happens while message_bus is trying to write a message to the client you may see something like this in your logs:
|
465
455
|
|
466
456
|
```
|
467
457
|
Errno::EPIPE: Broken pipe
|
@@ -477,7 +467,7 @@ Errno::EPIPE: Broken pipe
|
|
477
467
|
...
|
478
468
|
```
|
479
469
|
|
480
|
-
The user doesn't see anything, but depending on your traffic you may acquire quite a few of these in your logs.
|
470
|
+
The user doesn't see anything, but depending on your traffic you may acquire quite a few of these in your logs or exception tracking tool.
|
481
471
|
|
482
472
|
You can rescue from errors that occur in MessageBus's middleware stack by adding a config option:
|
483
473
|
|
@@ -493,4 +483,101 @@ MessageBus.configure(on_middleware_error: proc do |env, e|
|
|
493
483
|
end)
|
494
484
|
```
|
495
485
|
|
486
|
+
## How it works
|
487
|
+
|
488
|
+
MessageBus provides durable messaging following the publish-subscribe (pubsub) pattern to subscribers who track their own subscriptions. Durability is by virtue of the persistence of messages in backlogs stored in the selected backend implementation (Redis, Postgres, etc) which can be queried up until a configurable expiry. Subscribers must keep track of the ID of the last message they processed, and request only more-recent messages in subsequent connections.
|
489
|
+
|
490
|
+
The MessageBus implementation consists of several key parts:
|
491
|
+
|
492
|
+
* Backend implementations - these provide a consistent API over a variety of options for persisting published messages. The API they present is around the publication to and reading of messages from those backlogs in a manner consistent with message_bus' philosophy. Each of these inherits from `MessageBus::Backends::Base` and implements the interface it documents.
|
493
|
+
* `MessageBus::Rack::Middleware` - which accepts requests from subscribers, validates and authenticates them, delivers existing messages from the backlog and informs a `MessageBus::ConnectionManager` of a connection which is remaining open.
|
494
|
+
* `MessageBus::ConnectionManager` - manages a set of subscribers with active connections to the server, such that messages which are published during the connection may be dispatched.
|
495
|
+
* `MessageBus::Client` - represents a connected subscriber and delivers published messages over its connected socket.
|
496
|
+
* `MessageBus::Message` - represents a published message and its encoding for persistence.
|
497
|
+
|
498
|
+
The public API is all defined on the `MessageBus` module itself.
|
499
|
+
|
500
|
+
### Subscriber protocol
|
501
|
+
|
502
|
+
The message_bus protocol for subscribing clients is based on HTTP, optionally with long-polling and chunked encoding, as specified by the HTTP/1.1 spec in RFC7230 and RFC7231.
|
503
|
+
|
504
|
+
The protocol consists of a single HTTP end-point at `/message-bus/[client_id]/poll`, which responds to `POST` and `OPTIONS`. In the course of a `POST` request, the client must indicate the channels from which messages are desired, along with the last message ID the client received for each channel, and an incrementing integer sequence number for each request (used to detect out of order requests and close those with the same client ID and lower sequence numbers).
|
505
|
+
|
506
|
+
Clients' specification of requested channels can be submitted in either JSON format (with a `Content-Type` of `application/json`) or as HTML form data (using `application/x-www-form-urlencoded`). An example request might look like:
|
507
|
+
|
508
|
+
```
|
509
|
+
POST /message-bus/3314c3f12b1e45b4b1fdf1a6e42ba826/poll HTTP/1.1
|
510
|
+
Host: foo.com
|
511
|
+
Content-Type: application/json
|
512
|
+
Content-Length: 37
|
513
|
+
|
514
|
+
{"/foo/bar":3,"/doo/dah":0,"__seq":7}
|
515
|
+
```
|
516
|
+
|
517
|
+
If there are messages more recent than the client-specified IDs in any of the requested channels, those messages will be immediately delivered to the client. If the server is configured for long-polling, the client has not requested to disable it (by specifying the `dlp=t` query parameter), and no new messages are available, the connection will remain open for the configured long-polling interval (25 seconds by default); if a message becomes available in that time, it will be delivered, else the connection will close. If chunked encoding is enabled, message delivery will not automatically end the connection, and messages will be continuously delivered during the life of the connection, separated by `"\r\n|\r\n"`.
|
518
|
+
|
519
|
+
The format for delivered messages is a JSON array of message objects like so:
|
520
|
+
|
521
|
+
```json
|
522
|
+
[
|
523
|
+
{
|
524
|
+
"global_id": 12,
|
525
|
+
"message_id": 1,
|
526
|
+
"channel": "/some/channel/name",
|
527
|
+
"data": [the message as published]
|
528
|
+
}
|
529
|
+
]
|
530
|
+
```
|
531
|
+
|
532
|
+
The `global_id` field here indicates the ID of the message in the global backlog, while the `message_id` is the ID of the message in the channel-specific backlog. The ID used for subscriptions is always the channel-specific one.
|
533
|
+
|
534
|
+
In certain conditions, a status message will be delivered and look like this:
|
535
|
+
|
536
|
+
```json
|
537
|
+
{
|
538
|
+
"global_id": -1,
|
539
|
+
"message_id": -1,
|
540
|
+
"channel": "/__status",
|
541
|
+
"data": {
|
542
|
+
"/some/channel":5,
|
543
|
+
"/other/channel":9
|
544
|
+
}
|
545
|
+
}
|
546
|
+
```
|
547
|
+
|
548
|
+
This message indicates the last ID in the backlog for each channel that the client subscribed to. It is sent in the following circumstances:
|
549
|
+
|
550
|
+
* When the client subscribes to a channel starting from `-1`. When long-polling, this message will be delivered immediately.
|
551
|
+
* When the client subscribes to a channel starting from a message ID that is beyond the last message on that channel.
|
552
|
+
* When delivery of messages to a client is skipped because the message is filtered to other users/groups.
|
553
|
+
|
554
|
+
The values provided in this status message can be used by the client to skip requesting messages it will never receive and move forward in polling.
|
555
|
+
|
556
|
+
## Contributing
|
557
|
+
|
558
|
+
If you are looking to contribute to this project here are some ideas
|
559
|
+
|
560
|
+
- MAKE THIS README BETTER!
|
561
|
+
- Build backends for other providers (zeromq, rabbitmq, disque) - currently we support pg and redis.
|
562
|
+
- Improve and properly document admin dashboard (add opt-in stats, better diagnostics into queues)
|
563
|
+
- Improve general documentation (Add examples, refine existing examples)
|
564
|
+
- Make MessageBus a nice website
|
565
|
+
- Add optional transports for websocket and shared web workers
|
566
|
+
|
567
|
+
When submitting a PR, please be sure to include notes on it in the `Unreleased` section of the changelog, but do not bump the version number.
|
568
|
+
|
569
|
+
### Running tests
|
570
|
+
|
571
|
+
To run tests you need both Postgres and Redis installed. By default on Redis the tests connect to `localhost:6379` and on Postgres connect the database `localhost:5432/message_bus_test` with the system username; if you wish to override this, you can set alternative values:
|
572
|
+
|
573
|
+
```
|
574
|
+
PGUSER=some_user PGDATABASE=some_db bundle exec rake
|
575
|
+
```
|
576
|
+
|
577
|
+
We include a Docker Compose configuration to run test suite in isolation, or if you do not have Redis or Postgres installed natively. To execute it, do `docker-compose run tests`.
|
578
|
+
|
579
|
+
### Generating the documentation
|
580
|
+
|
581
|
+
Run `rake yard` (or `docker-compose run docs rake yard`) in order to generate the implementation's API docs in HTML format, and `open doc/index.html` to view them.
|
496
582
|
|
583
|
+
While working on documentation, it is useful to automatically re-build it as you make changes. You can do `yard server --reload` (or `docker-compose up docs`) and `open http://localhost:8808` to browse live-built docs as you edit them.
|