FEATURE: `MessageBus.base_route`

FEATURE: MessageBus.base_route

Allow the base route to be altered Resolves #202

diff --git a/CHANGELOG b/CHANGELOG
index 3f68156..cb407d2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+- Unreleased
+
+   - FEATURE: `MessageBus.base_route=` to alter the route that message bus will listen on.
+
 07-05-2020
 
 - Version 3.2.0
diff --git a/README.md b/README.md
index 059a543..10f0bdb 100644
--- a/README.md
+++ b/README.md
@@ -319,7 +319,7 @@ minPollInterval|100|When polling requests succeed, this is the minimum amount of
 maxPollInterval|180000|If request to the server start failing, MessageBus will backoff, this is the upper limit of the backoff.
 alwaysLongPoll|false|For debugging you may want to disable the "is browser in background" check and always long-poll
 shouldLongPollCallback|undefined|A callback returning true or false that determines if we should long-poll or not, if unset ignore and simply depend on window visibility.
-baseUrl|/|If message bus is mounted at a sub-path or different domain, you may configure it to perform requests there
+baseUrl|/|If message bus is mounted at a sub-path or different domain, you may configure it to perform requests there.  See `MessageBus.base_route=` on how to configure the MessageBus server to listen on a sub-path.
 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
 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.
 minHiddenPollInterval|1500|Time to wait between poll requests performed by background or hidden tabs and windows, shared state via localStorage
diff --git a/lib/message_bus.rb b/lib/message_bus.rb
index 181265d..841ac36 100644
--- a/lib/message_bus.rb
+++ b/lib/message_bus.rb
@@ -149,6 +149,19 @@ module MessageBus::Implementation
     @config[:long_polling_interval] || 25 * 1000
   end
 
+  # @param [String] route Message bus will listen to requests on this route.
+  # @return [void]
+  def base_route=(route)
+    configure(base_route: route.gsub(Regexp.new('\A(?!/)|(?<!/)\Z|//+'), "/"))
+  end
+
+  # @return [String] the route that message bus will respond to. If not
+  #   explicitly set, defaults to "/". Requests to "#{base_route}message-bus/*" will be handled
+  #   by the message bus server.
+  def base_route
+    @config[:base_route] || "/"
+  end
+
   # @return [Boolean] whether the bus is disabled or not
   def off?
     @off
diff --git a/lib/message_bus/rack/diagnostics.rb b/lib/message_bus/rack/diagnostics.rb
index 6908c17..c3f7110 100644
--- a/lib/message_bus/rack/diagnostics.rb
+++ b/lib/message_bus/rack/diagnostics.rb
@@ -16,9 +16,9 @@ class MessageBus::Rack::Diagnostics
   # Process an HTTP request from a subscriber client
   # @param [Rack::Request::Env] env the request environment
   def call(env)
-    return @app.call(env) unless env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
+    return @app.call(env) unless env['PATH_INFO'].start_with? "#{@bus.base_route}message-bus/_diagnostics"
 
-    route = env['PATH_INFO'].split('/message-bus/_diagnostics')[1]
+    route = env['PATH_INFO'].split("#{@bus.base_route}message-bus/_diagnostics")[1]
 
     if @bus.is_admin_lookup.nil? || !@bus.is_admin_lookup.call(env)
       return [403, {}, ['not allowed']]
diff --git a/lib/message_bus/rack/middleware.rb b/lib/message_bus/rack/middleware.rb
index 2bc3d39..4f735f0 100644
--- a/lib/message_bus/rack/middleware.rb
+++ b/lib/message_bus/rack/middleware.rb
@@ -39,6 +39,7 @@ class MessageBus::Rack::Middleware
     @bus = config[:message_bus] || MessageBus
     @connection_manager = MessageBus::ConnectionManager.new(@bus)
     @started_listener = false
+    @client_id_regexp = Regexp.new(@bus.base_route + 'message-bus/(?<client_id>[[:alnum:]]+)')
     start_listener unless @bus.off?
   end
 
@@ -54,7 +55,7 @@ class MessageBus::Rack::Middleware
   # Process an HTTP request from a subscriber client
   # @param [Rack::Request::Env] env the request environment
   def call(env)
-    return @app.call(env) unless env['PATH_INFO'] =~ /^\/message-bus\//
+    return @app.call(env) unless env['PATH_INFO'] =~ /^#{@bus.base_route}message-bus\//
 
     handle_request(env)
   end
@@ -63,18 +64,18 @@ class MessageBus::Rack::Middleware
 
   def handle_request(env)
     # special debug/test route
-    if @bus.allow_broadcast? && env['PATH_INFO'] == '/message-bus/broadcast'
+    if @bus.allow_broadcast? && env['PATH_INFO'] == "#{@bus.base_route}message-bus/broadcast"
       parsed = Rack::Request.new(env)
       @bus.publish parsed["channel"], parsed["data"]
       return [200, { "Content-Type" => "text/html" }, ["sent"]]
     end
 
-    if env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
+    if env['PATH_INFO'].start_with? "#{@bus.base_route}message-bus/_diagnostics"
       diags = MessageBus::Rack::Diagnostics.new(@app, message_bus: @bus)
       return diags.call(env)
     end
 
-    client_id = env['PATH_INFO'].split("/")[2]
+    client_id = env['PATH_INFO'].match(@client_id_regexp) { |m| m[:client_id] }
     return [404, {}, ["not found"]] unless client_id
 
     user_id = @bus.user_id_lookup.call(env) if @bus.user_id_lookup
diff --git a/spec/lib/message_bus/rack/middleware_spec.rb b/spec/lib/message_bus/rack/middleware_spec.rb
index 6226fed..d83ffc0 100644
--- a/spec/lib/message_bus/rack/middleware_spec.rb
+++ b/spec/lib/message_bus/rack/middleware_spec.rb
@@ -50,6 +50,19 @@ describe MessageBus::Rack::Middleware do
       last_response.ok?.must_equal true
     end
 
+    it "should respond ok when the base route is altered" do
+      @bus.base_route = "/base/route/"
+
+      post "/base/route/message-bus/ABC?dlp=t", '/foo1' => 0
+      @async_middleware.in_async?.must_equal false
+      last_response.ok?.must_equal true
+    end
+
+    it "should respond with a 404 if the client_id is missing" do
+      post "/message-bus/?dlp=t", '/foo1' => 0
+      last_response.not_found?.must_equal true
+    end
+
     it "should respond right away to long polls that are polling on -1 with the last_id" do
       post "/message-bus/ABC", '/foo' => -1
       last_response.ok?.must_equal true
@@ -141,6 +154,16 @@ describe MessageBus::Rack::Middleware do
       last_response.status.must_equal 200
     end
 
+    it "should get a 200 with html for an authorized user on a different base route" do
+      def @bus.is_admin_lookup
+        proc { |_| true }
+      end
+      @bus.base_route = "/base/route/"
+
+      get "/base/route/message-bus/_diagnostics"
+      last_response.status.must_equal 200
+    end
+
     it "should get the script it asks for" do
 
       def @bus.is_admin_lookup
@@ -243,6 +266,38 @@ describe MessageBus::Rack::Middleware do
       parsed[1]["data"].must_equal "borbs"
     end
 
+    it "should use the correct client ID" do
+      id = @bus.last_id('/foo')
+
+      client_id = "aBc123"
+      @bus.publish("/foo", "msg1", client_ids: [client_id])
+      @bus.publish("/foo", "msg2", client_ids: ["not_me#{client_id}"])
+
+      post "/message-bus/#{client_id}",
+           '/foo' => id
+
+      parsed = JSON.parse(last_response.body)
+      parsed.length.must_equal 2
+      parsed[0]["data"].must_equal("msg1")
+      parsed[1]["data"].wont_equal("msg2")
+    end
+
+    it "should use the correct client ID with additional path" do
+      id = @bus.last_id('/foo')
+
+      client_id = "aBc123"
+      @bus.publish("/foo", "msg1", client_ids: [client_id])
+      @bus.publish("/foo", "msg2", client_ids: ["not_me#{client_id}"])
+
+      post "/message-bus/#{client_id}/path/not/needed",
+           '/foo' => id
+
+      parsed = JSON.parse(last_response.body)
+      parsed.length.must_equal 2
+      parsed[0]["data"].must_equal("msg1")

[... diff too long, it was truncated ...]

GitHub sha: 1071466e

This commit appears in #222 which was merged by SamSaffron.