message_bus 1.1.1 → 2.0.0.beta.1
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/CHANGELOG +8 -0
- data/README.md +49 -8
- data/assets/message-bus.js +111 -25
- data/examples/chat/chat.rb +2 -0
- data/examples/chat/docker_container/chat.yml +22 -10
- data/examples/chat/docker_container/update_chat +2 -2
- data/lib/message_bus.rb +10 -13
- data/lib/message_bus/client.rb +105 -27
- data/lib/message_bus/connection_manager.rb +10 -27
- data/lib/message_bus/rack/middleware.rb +29 -8
- data/lib/message_bus/rails/railtie.rb +6 -3
- data/lib/message_bus/version.rb +1 -1
- data/spec/lib/fake_async_middleware.rb +7 -0
- data/spec/lib/{assets → message_bus/assets}/asset_encoding_spec.rb +1 -1
- data/spec/lib/{client_spec.rb → message_bus/client_spec.rb} +78 -0
- data/spec/lib/{connection_manager_spec.rb → message_bus/connection_manager_spec.rb} +0 -0
- data/spec/lib/{multi_process_spec.rb → message_bus/multi_process_spec.rb} +0 -0
- data/spec/lib/{middleware_spec.rb → message_bus/rack/middleware_spec.rb} +2 -89
- data/spec/lib/{redis → message_bus/redis}/reliable_pub_sub_spec.rb +4 -3
- data/spec/lib/{timer_thread_spec.rb → message_bus/timer_thread_spec.rb} +6 -22
- data/spec/spec_helper.rb +6 -1
- data/vendor/assets/javascripts/message-bus.js +111 -25
- metadata +19 -24
- data/lib/message_bus/message_handler.rb +0 -26
- data/spec/lib/handlers/demo_message_handler.rb +0 -5
- data/spec/lib/message_handler_spec.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82d57b58ca16d1422134657e6b185c5192b0f3c8
|
4
|
+
data.tar.gz: 9d87638ef14e32e57c5b3062f0cfab969260fe51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: defc3054b3f7bee5ac3b6ef58953153e51c39cde11deaa6876f9befa9953af1ebd7fd870253dfe5a4fec8bca6e8c8e5b09192c0ea075db26e6139c65f8c5edb5
|
7
|
+
data.tar.gz: aa4762a17e508d234889e3ea37fea18bd3a3e758e4d3ee5cf81d0c121c94f5763dff71bff22b476130a2ac1ef4cabd7085267f7a776dbaf8de61a866b6c27504
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
03-01-2016
|
2
|
+
|
3
|
+
- Version 2.0.0.beta.1
|
4
|
+
- Feature: add support for chunked encoding transport, this significnatlly improves long polling performance
|
5
|
+
- Feature: strip around_filters and client filters, which were speculative
|
6
|
+
- Fix: Message delivery race condition when a message was published just as long poll started. Ben Langfeld
|
7
|
+
- Feature: strip support for message_handlers, speculative feature that was not used
|
8
|
+
|
1
9
|
09-12-2015
|
2
10
|
|
3
11
|
- Version 1.1.1
|
data/README.md
CHANGED
@@ -2,12 +2,21 @@
|
|
2
2
|
|
3
3
|
A reliable, robust messaging bus for Ruby processes and web clients built on Redis.
|
4
4
|
|
5
|
-
MessageBus implements a Server to Server channel based protocol and Server to Web Client protocol (using polling or long-polling)
|
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
|
Long-polling is implemented using Rack Hijack and Thin::Async, all common Ruby web server can run MessageBus (Thin, Puma, Unicorn) and handle a large amount of concurrent connections that wait on messages.
|
8
8
|
|
9
9
|
MessageBus is implemented as Rack middleware and can be used by and Rails / Sinatra or pure Rack application.
|
10
10
|
|
11
|
+
# Try it out!
|
12
|
+
|
13
|
+
Live chat demo per [examples/chat](https://github.com/SamSaffron/message_bus/tree/master/examples/chat) is at:
|
14
|
+
|
15
|
+
### http://chat.samsaffron.com
|
16
|
+
|
17
|
+
## Can you handle concurrent requests?
|
18
|
+
|
19
|
+
**Yes**, MessageBus uses Rack Hijack, 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.
|
11
20
|
|
12
21
|
## Installation
|
13
22
|
|
@@ -39,7 +48,6 @@ end
|
|
39
48
|
MessageBus.backlog "/channel", id
|
40
49
|
# returns all messages after the id
|
41
50
|
|
42
|
-
|
43
51
|
# messages can be targetted at particular users or groups
|
44
52
|
MessageBus.publish "/channel", user_ids: [1,2,3], group_ids: [4,5,6]
|
45
53
|
|
@@ -56,17 +64,49 @@ MessageBus.group_ids_lookup do |env|
|
|
56
64
|
# return the group ids the user belongs to
|
57
65
|
# can be nil or []
|
58
66
|
end
|
67
|
+
```
|
59
68
|
|
69
|
+
### Transport
|
60
70
|
|
61
|
-
MessageBus
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
71
|
+
MessageBus ships with 3 transport mechanisms.
|
72
|
+
|
73
|
+
1. Long Polling with chunked encoding (streaming)
|
74
|
+
2. Long Polling
|
75
|
+
3. Polling
|
76
|
+
|
77
|
+
Long Polling with chunked encoding allows a single connection to stream multiple messages to a client, this requires HTTP/1.1
|
67
78
|
|
79
|
+
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)
|
80
|
+
|
81
|
+
To setup NGINX to proxy to your app correctly be sure to enable HTTP1.1 and disable buffering
|
82
|
+
|
83
|
+
```
|
84
|
+
location /message-bus/ {
|
85
|
+
...
|
86
|
+
proxy_buffering off;
|
87
|
+
proxy_http_version 1.1;
|
88
|
+
...
|
89
|
+
}
|
68
90
|
```
|
69
91
|
|
92
|
+
**NOTE**: do not set proxy_buffering off globally, it may have unintended consequences.
|
93
|
+
|
94
|
+
If you wish to disable chunked encoding run:
|
95
|
+
|
96
|
+
```
|
97
|
+
MessageBus.enableChunkedEncoding = false; // in your JavaScript
|
98
|
+
```
|
99
|
+
|
100
|
+
Or
|
101
|
+
|
102
|
+
```
|
103
|
+
MessageBus.chunked_encoding_enabled = false // in Ruby
|
104
|
+
```
|
105
|
+
|
106
|
+
Long Polling requires no special setup, as soon as new data arrives on the channel the server delivers the data and closes the connection.
|
107
|
+
|
108
|
+
Polling also requires no special setup, MessageBus will fallback to polling after a tab becomes inactive and remains inactive for a period.
|
109
|
+
|
70
110
|
### Multisite support
|
71
111
|
|
72
112
|
MessageBus can be used in an environment that hosts multiple sites by multiplexing channels. To use this mode
|
@@ -195,5 +235,6 @@ If you are looking to contribute to this project here are some ideas
|
|
195
235
|
- Build a PostgreSQL backend using NOTIFY and LISTEN
|
196
236
|
- Improve general documentation
|
197
237
|
- Port the test suite to MiniTest
|
238
|
+
- Make MessageBus a nice website
|
198
239
|
|
199
240
|
|
data/assets/message-bus.js
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
/*jshint bitwise: false*/
|
2
|
+
"use strict;"
|
2
3
|
|
3
|
-
/**
|
4
|
-
Message Bus functionality.
|
5
|
-
|
6
|
-
@class MessageBus
|
7
|
-
@namespace Discourse
|
8
|
-
@module Discourse
|
9
|
-
**/
|
10
4
|
window.MessageBus = (function() {
|
11
5
|
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
12
6
|
var callbacks, clientId, failCount, shouldLongPoll, queue, responseCallbacks, uniqueId, baseUrl;
|
@@ -30,12 +24,13 @@ window.MessageBus = (function() {
|
|
30
24
|
baseUrl = "/";
|
31
25
|
paused = false;
|
32
26
|
later = [];
|
27
|
+
chunkedBackoff = 0;
|
33
28
|
|
34
29
|
var hiddenProperty;
|
35
30
|
|
36
31
|
|
37
32
|
(function(){
|
38
|
-
var prefixes = ["","webkit","ms","moz"
|
33
|
+
var prefixes = ["","webkit","ms","moz"];
|
39
34
|
for(var i=0; i<prefixes.length; i++) {
|
40
35
|
var prefix = prefixes[i];
|
41
36
|
var check = prefix + (prefix === "" ? "hidden" : "Hidden");
|
@@ -53,6 +48,11 @@ window.MessageBus = (function() {
|
|
53
48
|
}
|
54
49
|
};
|
55
50
|
|
51
|
+
var hasonprogress = (new XMLHttpRequest()).onprogress === null;
|
52
|
+
var allowChunked = function(){
|
53
|
+
return me.enableChunkedEncoding && hasonprogress;
|
54
|
+
};
|
55
|
+
|
56
56
|
shouldLongPoll = function() {
|
57
57
|
return me.alwaysLongPoll || !isHidden();
|
58
58
|
};
|
@@ -65,7 +65,6 @@ window.MessageBus = (function() {
|
|
65
65
|
var gotData = false;
|
66
66
|
if (!messages) return false; // server unexpectedly closed connection
|
67
67
|
|
68
|
-
|
69
68
|
for (var i=0; i<messages.length; i++) {
|
70
69
|
var message = messages[i];
|
71
70
|
gotData = true;
|
@@ -93,6 +92,20 @@ window.MessageBus = (function() {
|
|
93
92
|
return gotData;
|
94
93
|
};
|
95
94
|
|
95
|
+
var reqSuccess = function(messages) {
|
96
|
+
failCount = 0;
|
97
|
+
if (paused) {
|
98
|
+
if (messages) {
|
99
|
+
for (var i=0; i<messages.length; i++) {
|
100
|
+
later.push(messages[i]);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
} else {
|
104
|
+
return processMessages(messages);
|
105
|
+
}
|
106
|
+
return false;
|
107
|
+
};
|
108
|
+
|
96
109
|
longPoller = function(poll,data){
|
97
110
|
var gotData = false;
|
98
111
|
var aborted = false;
|
@@ -100,27 +113,97 @@ window.MessageBus = (function() {
|
|
100
113
|
totalAjaxCalls += 1;
|
101
114
|
data.__seq = totalAjaxCalls;
|
102
115
|
|
103
|
-
|
104
|
-
|
116
|
+
var longPoll = shouldLongPoll() && me.enableLongPolling;
|
117
|
+
var chunked = longPoll && allowChunked();
|
118
|
+
if (chunkedBackoff > 0) {
|
119
|
+
chunkedBackoff--;
|
120
|
+
chunked = false;
|
121
|
+
}
|
122
|
+
|
123
|
+
var headers = {
|
124
|
+
'X-SILENCE-LOGGER': 'true'
|
125
|
+
};
|
126
|
+
|
127
|
+
if (!chunked){
|
128
|
+
headers["Dont-Chunk"] = 'true';
|
129
|
+
}
|
130
|
+
|
131
|
+
var dataType = chunked ? "text" : "json";
|
132
|
+
|
133
|
+
var handle_progress = function(payload, position) {
|
134
|
+
|
135
|
+
var separator = "\r\n|\r\n";
|
136
|
+
var endChunk = payload.indexOf(separator, position);
|
137
|
+
|
138
|
+
if (endChunk === -1) {
|
139
|
+
return position;
|
140
|
+
}
|
141
|
+
|
142
|
+
var chunk = payload.substring(position, endChunk);
|
143
|
+
chunk = chunk.replace(/\r\n\|\|\r\n/g, separator);
|
144
|
+
|
145
|
+
try {
|
146
|
+
console.log(chunk);
|
147
|
+
reqSuccess(JSON.parse(chunk));
|
148
|
+
} catch(e) {
|
149
|
+
if (console.log) {
|
150
|
+
console.log("FAILED TO PARSE CHUNKED REPLY");
|
151
|
+
console.log(data);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
return handle_progress(payload, endChunk + separator.length);
|
156
|
+
}
|
157
|
+
|
158
|
+
var disableChunked = function(){
|
159
|
+
if (me.longPoll) {
|
160
|
+
me.longPoll.abort();
|
161
|
+
chunkedBackoff = 30;
|
162
|
+
}
|
163
|
+
};
|
164
|
+
|
165
|
+
var req = me.ajax({
|
166
|
+
url: me.baseUrl + "message-bus/" + me.clientId + "/poll?" + (!longPoll ? "dlp=t" : ""),
|
105
167
|
data: data,
|
106
168
|
cache: false,
|
107
|
-
dataType:
|
169
|
+
dataType: dataType,
|
108
170
|
type: 'POST',
|
109
|
-
headers:
|
110
|
-
|
171
|
+
headers: headers,
|
172
|
+
xhr: function() {
|
173
|
+
var xhr = jQuery.ajaxSettings.xhr();
|
174
|
+
|
175
|
+
if (!chunked) {
|
176
|
+
return xhr;
|
177
|
+
}
|
178
|
+
|
179
|
+
var position = 0;
|
180
|
+
|
181
|
+
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
182
|
+
// this is messing with us, so just backoff from using chunked for now
|
183
|
+
var chunkedTimeout = setTimeout(disableChunked,3000);
|
184
|
+
|
185
|
+
xhr.onprogress = function () {
|
186
|
+
clearTimeout(chunkedTimeout);
|
187
|
+
|
188
|
+
if(xhr.getResponseHeader('Content-Type') === 'application/json; charset=utf-8') {
|
189
|
+
// not chunked we are sending json back
|
190
|
+
chunked = false;
|
191
|
+
return;
|
192
|
+
}
|
193
|
+
position = handle_progress(xhr.responseText, position);
|
194
|
+
}
|
195
|
+
|
196
|
+
return xhr;
|
111
197
|
},
|
112
198
|
success: function(messages) {
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
gotData = processMessages(messages);
|
122
|
-
}
|
123
|
-
},
|
199
|
+
if (!chunked) {
|
200
|
+
// we may have requested text so jQuery will not parse
|
201
|
+
if (typeof(messages) === "string") {
|
202
|
+
messages = JSON.parse(messages);
|
203
|
+
}
|
204
|
+
gotData = reqSuccess(messages);
|
205
|
+
}
|
206
|
+
},
|
124
207
|
error: function(xhr, textStatus, err) {
|
125
208
|
if(textStatus === "abort") {
|
126
209
|
aborted = true;
|
@@ -161,9 +244,12 @@ window.MessageBus = (function() {
|
|
161
244
|
me.longPoll = null;
|
162
245
|
}
|
163
246
|
});
|
247
|
+
|
248
|
+
return req;
|
164
249
|
};
|
165
250
|
|
166
251
|
me = {
|
252
|
+
enableChunkedEncoding: true,
|
167
253
|
enableLongPolling: true,
|
168
254
|
callbackInterval: 15000,
|
169
255
|
backgroundCallbackInterval: 60000,
|
data/examples/chat/chat.rb
CHANGED
@@ -7,6 +7,7 @@ require 'json'
|
|
7
7
|
|
8
8
|
$online = Hash.new
|
9
9
|
|
10
|
+
|
10
11
|
MessageBus.subscribe "/presence" do |msg|
|
11
12
|
if user = msg.data["enter"]
|
12
13
|
$online[user] = Time.now
|
@@ -17,6 +18,7 @@ MessageBus.subscribe "/presence" do |msg|
|
|
17
18
|
end
|
18
19
|
|
19
20
|
MessageBus.user_id_lookup do |env|
|
21
|
+
MessageBus.logger = env['rack.logger']
|
20
22
|
name = env["HTTP_X_NAME"]
|
21
23
|
if name
|
22
24
|
unless $online[name]
|
@@ -1,4 +1,4 @@
|
|
1
|
-
base_image: "
|
1
|
+
base_image: "discourse/base:1.0.15"
|
2
2
|
|
3
3
|
update_pups: false
|
4
4
|
|
@@ -28,23 +28,18 @@ hooks:
|
|
28
28
|
- exec: cd /var/www && git clone --depth 1 https://github.com/SamSaffron/message_bus.git
|
29
29
|
- exec:
|
30
30
|
cmd:
|
31
|
-
- gem install
|
31
|
+
- gem install puma
|
32
32
|
- gem install redis
|
33
33
|
- gem install sinatra
|
34
|
-
- gem install rbtrace
|
35
|
-
- replace:
|
36
|
-
- filename: $home/examples/chat/chat.rb
|
37
|
-
- from: "require 'json'"
|
38
|
-
- to: "require 'json'\nrequire 'rbtrace'"
|
39
34
|
- file:
|
40
|
-
path: /etc/service/
|
35
|
+
path: /etc/service/puma/run
|
41
36
|
chmod: "+x"
|
42
37
|
contents: |
|
43
38
|
#!/bin/bash
|
44
39
|
exec 2>&1
|
45
40
|
# redis
|
46
41
|
cd $home/examples/chat
|
47
|
-
exec sudo -E -u chat LD_PRELOAD=/usr/lib/libjemalloc.so.1
|
42
|
+
exec sudo -E -u chat LD_PRELOAD=/usr/lib/libjemalloc.so.1 puma -p 8080 -e production
|
48
43
|
- exec: rm /etc/nginx/sites-enabled/default
|
49
44
|
- replace:
|
50
45
|
filename: /etc/nginx/nginx.conf
|
@@ -63,15 +58,32 @@ hooks:
|
|
63
58
|
gzip_min_length 1000;
|
64
59
|
server_name chat.samsaffron.com;
|
65
60
|
keepalive_timeout 65;
|
61
|
+
|
62
|
+
location /message-bus/ {
|
63
|
+
proxy_set_header Host $http_host;
|
64
|
+
proxy_set_header X-Real-IP $remote_addr;
|
65
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
66
|
+
proxy_set_header X-Forwarded-Proto http;
|
67
|
+
proxy_http_version 1.1;
|
68
|
+
proxy_buffering off;
|
69
|
+
proxy_pass http://chat;
|
70
|
+
break;
|
71
|
+
}
|
72
|
+
|
73
|
+
|
66
74
|
location / {
|
67
75
|
try_files $uri @chat;
|
76
|
+
break;
|
68
77
|
}
|
69
|
-
|
78
|
+
|
79
|
+
location @chat {
|
70
80
|
proxy_set_header Host $http_host;
|
71
81
|
proxy_set_header X-Real-IP $remote_addr;
|
72
82
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
73
83
|
proxy_set_header X-Forwarded-Proto http;
|
84
|
+
proxy_http_version 1.1;
|
74
85
|
proxy_pass http://chat;
|
86
|
+
break;
|
75
87
|
}
|
76
88
|
}
|
77
89
|
- file:
|
@@ -1,9 +1,9 @@
|
|
1
1
|
#!/bin/bash
|
2
2
|
|
3
|
-
result=`cd /root/
|
3
|
+
result=`cd /root/message_bus && git pull | grep "Already up-to-date"`
|
4
4
|
|
5
5
|
if [ -z "$result" ]; then
|
6
6
|
echo "updating..."
|
7
|
-
cd /var/docker && ./launcher
|
7
|
+
cd /var/docker && ./launcher rebuild chat --skip-prereqs
|
8
8
|
fi
|
9
9
|
|
data/lib/message_bus.rb
CHANGED
@@ -4,7 +4,6 @@ require "message_bus/version"
|
|
4
4
|
require "message_bus/message"
|
5
5
|
require "message_bus/client"
|
6
6
|
require "message_bus/connection_manager"
|
7
|
-
require "message_bus/message_handler"
|
8
7
|
require "message_bus/diagnostics"
|
9
8
|
require "message_bus/rack/middleware"
|
10
9
|
require "message_bus/rack/diagnostics"
|
@@ -51,6 +50,16 @@ module MessageBus::Implementation
|
|
51
50
|
return @logger if @logger
|
52
51
|
require 'logger'
|
53
52
|
@logger = Logger.new(STDOUT)
|
53
|
+
@logger.level = Logger::INFO
|
54
|
+
@logger
|
55
|
+
end
|
56
|
+
|
57
|
+
def chunked_encoding_enabled?
|
58
|
+
@chunked_encoding_enabled == false ? false : true
|
59
|
+
end
|
60
|
+
|
61
|
+
def chunked_encoding_enabled=(val)
|
62
|
+
@chunked_encoding_enabled = val
|
54
63
|
end
|
55
64
|
|
56
65
|
def long_polling_enabled?
|
@@ -143,18 +152,6 @@ module MessageBus::Implementation
|
|
143
152
|
@extra_response_headers_lookup
|
144
153
|
end
|
145
154
|
|
146
|
-
def client_filter(channel, &blk)
|
147
|
-
@client_filters ||= {}
|
148
|
-
@client_filters[channel] = blk if blk
|
149
|
-
@client_filters[channel]
|
150
|
-
end
|
151
|
-
|
152
|
-
def around_client_batch(channel, &blk)
|
153
|
-
@around_client_batches ||= {}
|
154
|
-
@around_client_batches[channel] = blk if blk
|
155
|
-
@around_client_batches[channel]
|
156
|
-
end
|
157
|
-
|
158
155
|
def on_connect(&blk)
|
159
156
|
@on_connect = blk if blk
|
160
157
|
@on_connect
|