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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 00b86d8f7631445d13999cf03c8474c94a5c6df0
4
- data.tar.gz: da3f677b77b8695d27cb5d6997c22894ea33bd5b
3
+ metadata.gz: 82d57b58ca16d1422134657e6b185c5192b0f3c8
4
+ data.tar.gz: 9d87638ef14e32e57c5b3062f0cfab969260fe51
5
5
  SHA512:
6
- metadata.gz: 7bba577513285f36a95898d524e2cc60f832be1e1c7bb92d63313fe82b5862dd3ddeb48930fff071bde92d85be03eea102836af19513633f23e0962c645e8d1d
7
- data.tar.gz: bbb1d8c326c2e8c85ddacce97a37aabdf0562ac15b0bd7153ce1fec6ebebbd7c7d68d14a36ec45e1afaa2a1b911ca2e01facf4a86da521669251bf86e97143e2
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.client_filter("/channel") do |user_id, message|
62
- # return message if client is allowed to see this message
63
- # allows you to inject server side filtering of messages based on arbitrary rules
64
- # also allows you to override the message a clients will see
65
- # be sure to .dup the message if you wish to change it
66
- end
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
 
@@ -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","ms"];
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
- return me.ajax({
104
- url: me.baseUrl + "message-bus/" + me.clientId + "/poll?" + (!shouldLongPoll() || !me.enableLongPolling ? "dlp=t" : ""),
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: 'json',
169
+ dataType: dataType,
108
170
  type: 'POST',
109
- headers: {
110
- 'X-SILENCE-LOGGER': 'true'
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
- failCount = 0;
114
- if (paused) {
115
- if (messages) {
116
- for (var i=0; i<messages.length; i++) {
117
- later.push(messages[i]);
118
- }
119
- }
120
- } else {
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,
@@ -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: "samsaffron/discourse_base:1.0.11"
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 unicorn
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/unicorn/run
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 unicorn -p 8080 -E production
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
- location @chat{
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/logster && git pull | grep "Already up-to-date"`
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 bootstrap logster && ./launcher destroy logster && ./launcher start logster
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