From aa9b3bb35accce498438e22344a3c352a9bc6592 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 24 Oct 2014 13:38:00 +1100 Subject: [PATCH] FEATURE: allow long polling to go to a different url Added the site setting long_polling_base_url , this allows you to farm long polling to a different server. This setting is very important if a CDN is serving dynamic content. --- Gemfile.lock | 2 +- .../subscribe-user-notifications.js.es6 | 17 ++++++++++++++--- app/helpers/application_helper.rb | 11 +++++++++++ app/views/layouts/application.html.erb | 4 ++++ config/initializers/04-message_bus.rb | 8 ++++++++ config/locales/server.en.yml | 1 + config/site_settings.yml | 3 +++ lib/auth/default_current_user_provider.rb | 11 +++++++++++ 8 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0673cfb16..0fe4be8d3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -163,7 +163,7 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) memory_profiler (0.0.4) - message_bus (1.0.0) + message_bus (1.0.4) eventmachine rack (>= 1.1.3) redis diff --git a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 index 0104f6b2d..fe79cb98e 100644 --- a/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 +++ b/app/assets/javascripts/discourse/initializers/subscribe-user-notifications.js.es6 @@ -2,7 +2,7 @@ Subscribes to user events on the message bus **/ export default { - name: "subscribe-user-notifications", + name: 'subscribe-user-notifications', after: 'message-bus', initialize: function(container) { var user = Discourse.User.current(); @@ -13,14 +13,25 @@ export default { var bus = Discourse.MessageBus; bus.callbackInterval = siteSettings.anon_polling_interval; bus.backgroundCallbackInterval = siteSettings.background_polling_interval; + bus.baseUrl = siteSettings.long_polling_base_url; + + if (bus.baseUrl !== '/') { + // zepto compatible, 1 param only + bus.ajax = function(opts){ + opts.headers = opts.headers || {}; + opts.headers['X-Shared-Session-Key'] = $('meta[name=shared_session_key]').attr('content'); + return $.ajax(opts); + }; + } else { + bus.baseUrl = Discourse.getURL('/'); + } if (user) { bus.callbackInterval = siteSettings.polling_interval; bus.enableLongPolling = true; - bus.baseUrl = Discourse.getURL("/"); if (user.admin || user.moderator) { - bus.subscribe("/flagged_counts", function(data) { + bus.subscribe('/flagged_counts', function(data) { user.set('site_flagged_posts_count', data.total); }); } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ee4f386ac..4493cd92b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -11,6 +11,17 @@ module ApplicationHelper include CanonicalURL::Helpers include ConfigurableUrls + def shared_session_key + if SiteSetting.long_polling_base_url != '/'.freeze && current_user + sk = "shared_session_key" + return request.env[sk] if request.env[sk] + + request.env[sk] = key = (session[sk] ||= SecureRandom.hex) + $redis.setex "#{sk}_#{key}", 7.days, current_user.id.to_s + key + end + end + def script(*args) if SiteSetting.enable_cdn_js_debugging && GlobalSetting.cdn_url tags = javascript_include_tag(*args, "crossorigin" => "anonymous") diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 67dd5a727..b1d4532e0 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -10,6 +10,10 @@ <%- end %> + <%- if shared_session_key %> + + <%- end %> + <%= script "preload_store" %> <%= script "locales/#{I18n.locale}" %> <%= script "vendor" %> diff --git a/config/initializers/04-message_bus.rb b/config/initializers/04-message_bus.rb index efc333f2d..18c4f0c87 100644 --- a/config/initializers/04-message_bus.rb +++ b/config/initializers/04-message_bus.rb @@ -2,6 +2,14 @@ MessageBus.site_id_lookup do RailsMultisite::ConnectionManagement.current_db end +MessageBus.extra_response_headers_lookup do |env| + { + "Access-Control-Allow-Origin" => Discourse.base_url, + "Access-Control-Allow-Methods" => "GET, POST", + "Access-Control-Allow-Headers" => "X-SILENCE-LOGGER, X-Shared-Session-Key" + } +end + MessageBus.user_id_lookup do |env| user = CurrentUser.lookup_from_env(env) user.id if user diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index df85e0096..ac0b5543e 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -698,6 +698,7 @@ en: enable_private_messages: "Allow trust level 1 users to create private messages and reply to private messages" enable_long_polling: "Message bus used for notification can use long polling" + long_polling_base_url: "Base URL used for long polling (when a CDN is serving dynamic content, be sure to set this to origin pull) eg: http://origin.site.com" long_polling_interval: "Amount of time the server should wait before responding to clients when there is no data to send (logged on users only)" polling_interval: "When not long polling, how often should logged on clients poll in milliseconds" anon_polling_interval: "How often should anonymous clients poll in milliseconds" diff --git a/config/site_settings.yml b/config/site_settings.yml index 9189bd1ab..d34d8faa2 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -616,6 +616,9 @@ developer: client: true default: true long_polling_interval: 25000 + long_polling_base_url: + client: true + default: '/' background_polling_interval: client: true default: 60000 diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb index 4b71c9682..94c7a8e91 100644 --- a/lib/auth/default_current_user_provider.rb +++ b/lib/auth/default_current_user_provider.rb @@ -18,6 +18,17 @@ class Auth::DefaultCurrentUserProvider def current_user return @env[CURRENT_USER_KEY] if @env.key?(CURRENT_USER_KEY) + # bypass if we have the shared session header + if shared_key = @env['HTTP_X_SHARED_SESSION_KEY'] + uid = $redis.get("shared_session_key_#{shared_key}") + user = nil + if uid + user = User.find_by(id: uid.to_i) + end + @env[CURRENT_USER_KEY] = user + return user + end + request = @request auth_token = request.cookies[TOKEN_COOKIE]