diff options
Diffstat (limited to 'actioncable')
16 files changed, 125 insertions, 133 deletions
diff --git a/actioncable/app/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb index 210a3ae17e..e0758dae72 100644 --- a/actioncable/app/assets/javascripts/action_cable.coffee.erb +++ b/actioncable/app/assets/javascripts/action_cable.coffee.erb @@ -4,6 +4,8 @@ @ActionCable = INTERNAL: <%= ActionCable::INTERNAL.to_json %> + WebSocket: window.WebSocket + logger: window.console createConsumer: (url) -> url ?= @getConfig("url") ? @INTERNAL.default_mount_path @@ -33,4 +35,4 @@ log: (messages...) -> if @debugging messages.push(Date.now()) - console.log("[ActionCable]", messages...) + @logger.log("[ActionCable]", messages...) diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee index d6a6397804..29ad676290 100644 --- a/actioncable/app/assets/javascripts/action_cable/connection.coffee +++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee @@ -27,7 +27,7 @@ class ActionCable.Connection else ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}") @uninstallEventHandlers() if @webSocket? - @webSocket = new WebSocket(@consumer.url, protocols) + @webSocket = new ActionCable.WebSocket(@consumer.url, protocols) @installEventHandlers() @monitor.start() true diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index 3742f248d1..6051818bfb 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -26,12 +26,12 @@ module ActionCable id_key = data['identifier'] id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access - subscription_klass = connection.server.channel_classes[id_options[:channel]] + subscription_klass = id_options[:channel].safe_constantize - if subscription_klass + if subscription_klass && ActionCable::Channel::Base >= subscription_klass subscriptions[id_key] ||= subscription_klass.new(connection, id_key, id_options) else - logger.error "Subscription class not found (#{data.inspect})" + logger.error "Subscription class not found: #{id_options[:channel].inspect}" end end diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb index 8ce1b24962..96176e0014 100644 --- a/actioncable/lib/action_cable/engine.rb +++ b/actioncable/lib/action_cable/engine.rb @@ -31,11 +31,8 @@ module ActionCable self.cable = Rails.application.config_for(config_path).with_indifferent_access end - if 'ApplicationCable::Connection'.safe_constantize - self.connection_class = ApplicationCable::Connection - end - - self.channel_paths = Rails.application.paths['app/channels'].existent + previous_connection_class = self.connection_class + self.connection_class = -> { 'ApplicationCable::Connection'.safe_constantize || previous_connection_class.call } options.each { |k,v| send("#{k}=", v) } end diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb index 717a60fe4f..0ad1e408a9 100644 --- a/actioncable/lib/action_cable/server/base.rb +++ b/actioncable/lib/action_cable/server/base.rb @@ -19,13 +19,13 @@ module ActionCable def initialize @mutex = Monitor.new - @remote_connections = @event_loop = @worker_pool = @channel_classes = @pubsub = nil + @remote_connections = @event_loop = @worker_pool = @pubsub = nil end # Called by Rack to setup the server. def call(env) setup_heartbeat_timer - config.connection_class.new(self, env).process + config.connection_class.call.new(self, env).process end # Disconnect all the connections identified by `identifiers` on this server or any others via RemoteConnections. @@ -67,16 +67,6 @@ module ActionCable @worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) } end - # Requires and returns a hash of all of the channel class constants, which are keyed by name. - def channel_classes - @channel_classes || @mutex.synchronize do - @channel_classes ||= begin - config.channel_paths.each { |channel_path| require channel_path } - config.channel_class_names.each_with_object({}) { |name, hash| hash[name] = name.constantize } - end - end - end - # Adapter used for all streams/broadcasting. def pubsub @pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) } @@ -84,7 +74,7 @@ module ActionCable # All of the identifiers applied to the connection class associated with this server. def connection_identifiers - config.connection_class.identifiers + config.connection_class.call.identifiers end end diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb index 0bb378cf03..ada1ac22cc 100644 --- a/actioncable/lib/action_cable/server/configuration.rb +++ b/actioncable/lib/action_cable/server/configuration.rb @@ -8,23 +8,15 @@ module ActionCable attr_accessor :disable_request_forgery_protection, :allowed_request_origins attr_accessor :cable, :url, :mount_path - attr_accessor :channel_paths # :nodoc: - def initialize @log_tags = [] - @connection_class = ActionCable::Connection::Base + @connection_class = -> { ActionCable::Connection::Base } @worker_pool_size = 4 @disable_request_forgery_protection = false end - def channel_class_names - @channel_class_names ||= channel_paths.collect do |channel_path| - Pathname.new(channel_path).basename.to_s.split('.').first.camelize - end - end - # Returns constant of subscription adapter specified in config/cable.yml. # If the adapter cannot be found, this will default to the Redis adapter. # Also makes sure proper dependencies are required. diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index 0b0c72ccf6..38543920d3 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -128,10 +128,6 @@ module ActionCable::StreamTests setup do @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Inline) @server.config.allowed_request_origins = %w( http://rubyonrails.com ) - @server.stubs(:channel_classes).returns( - ChatChannel.name => ChatChannel, - UserCallbackChannel.name => UserCallbackChannel, - ) end test 'custom encoder' do diff --git a/actioncable/test/client/echo_channel.rb b/actioncable/test/client/echo_channel.rb deleted file mode 100644 index 5a7bac25c5..0000000000 --- a/actioncable/test/client/echo_channel.rb +++ /dev/null @@ -1,22 +0,0 @@ -class EchoChannel < ActionCable::Channel::Base - def subscribed - stream_from "global" - end - - def unsubscribed - 'Goodbye from EchoChannel!' - end - - def ding(data) - transmit(dong: data['message']) - end - - def delay(data) - sleep 1 - transmit(dong: data['message']) - end - - def bulk(data) - ActionCable.server.broadcast "global", wide: data['message'] - end -end diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index fe503fd703..f4b4a53aea 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -11,6 +11,29 @@ class ClientTest < ActionCable::TestCase WAIT_WHEN_EXPECTING_EVENT = 8 WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5 + class EchoChannel < ActionCable::Channel::Base + def subscribed + stream_from "global" + end + + def unsubscribed + 'Goodbye from EchoChannel!' + end + + def ding(data) + transmit(dong: data['message']) + end + + def delay(data) + sleep 1 + transmit(dong: data['message']) + end + + def bulk(data) + ActionCable.server.broadcast "global", wide: data['message'] + end + end + def setup ActionCable.instance_variable_set(:@server, nil) server = ActionCable.server @@ -21,7 +44,6 @@ class ClientTest < ActionCable::TestCase # and now the "real" setup for our test: server.config.disable_request_forgery_protection = true - server.config.channel_paths = [ File.expand_path('client/echo_channel.rb', __dir__) ] Thread.new { EventMachine.run } unless EventMachine.reactor_running? Thread.pass until EventMachine.reactor_running? @@ -148,10 +170,10 @@ class ClientTest < ActionCable::TestCase with_puma_server do |port| c = faye_client(port) assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack - c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "message"=>{"dong"=>"hello"}}, c.read_message) + c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello') + assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "message"=>{"dong"=>"hello"}}, c.read_message) c.close end end @@ -165,12 +187,12 @@ class ClientTest < ActionCable::TestCase clients.map {|c| Concurrent::Future.execute { assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack - c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel') + assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) barrier_1.wait WAIT_WHEN_EXPECTING_EVENT - c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'bulk', message: 'hello') + c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'bulk', message: 'hello') barrier_2.wait WAIT_WHEN_EXPECTING_EVENT assert_equal clients.size, c.read_messages(clients.size).size } }.each(&:wait!) @@ -185,10 +207,10 @@ class ClientTest < ActionCable::TestCase clients.map {|c| Concurrent::Future.execute { assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack - c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel') + assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) } }.each(&:wait!) clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) @@ -199,17 +221,17 @@ class ClientTest < ActionCable::TestCase with_puma_server do |port| c = faye_client(port) assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack - c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'delay', message: 'hello') + c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'delay', message: 'hello') c.close # disappear before write c = faye_client(port) assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack - c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) - c.send_message command: 'message', identifier: JSON.generate(channel: 'EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello') - assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.generate(channel: 'ClientTest::EchoChannel'), data: JSON.generate(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) c.close # disappear before read end end @@ -217,12 +239,12 @@ class ClientTest < ActionCable::TestCase def test_unsubscribe_client with_puma_server do |port| app = ActionCable.server - identifier = JSON.generate(channel: 'EchoChannel') + identifier = JSON.generate(channel: 'ClientTest::EchoChannel') c = faye_client(port) assert_equal({"type" => "welcome"}, c.read_message) c.send_message command: 'subscribe', identifier: identifier - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) assert_equal(1, app.connections.count) assert(app.remote_connections.where(identifier: identifier)) @@ -242,8 +264,8 @@ class ClientTest < ActionCable::TestCase with_puma_server do |port| c = faye_client(port) assert_equal({"type" => "welcome"}, c.read_message) - c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'EchoChannel') - assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'subscribe', identifier: JSON.generate(channel: 'ClientTest::EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) ActionCable.server.restart c.wait_for_close diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb index 53e8547245..a5b1e5dcf3 100644 --- a/actioncable/test/connection/subscriptions_test.rb +++ b/actioncable/test/connection/subscriptions_test.rb @@ -24,7 +24,6 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase setup do @server = TestServer.new - @server.stubs(:channel_classes).returns(ChatChannel.name => ChatChannel) @chat_identifier = ActiveSupport::JSON.encode(id: 1, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel') end diff --git a/actioncable/test/javascript/src/test.coffee b/actioncable/test/javascript/src/test.coffee index 3ce88c7789..eb95fb2604 100644 --- a/actioncable/test/javascript/src/test.coffee +++ b/actioncable/test/javascript/src/test.coffee @@ -1,3 +1,3 @@ #= require action_cable -#= require_tree ./test_helpers +#= require ./test_helpers #= require_tree ./unit diff --git a/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee new file mode 100644 index 0000000000..6b145dede8 --- /dev/null +++ b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee @@ -0,0 +1,37 @@ +#= require mock-socket + +{TestHelpers} = ActionCable + +TestHelpers.consumerTest = (name, options = {}, callback) -> + unless callback? + callback = options + options = {} + + options.url ?= TestHelpers.testURL + + QUnit.test name, (assert) -> + doneAsync = assert.async() + + ActionCable.WebSocket = MockWebSocket + server = new MockServer options.url + consumer = ActionCable.createConsumer(options.url) + + server.on "connection", -> + clients = server.clients() + assert.equal clients.length, 1 + assert.equal clients[0].readyState, WebSocket.OPEN + + done = -> + consumer.disconnect() + server.close() + doneAsync() + + testData = {assert, consumer, server, done} + + if options.connect is false + callback(testData) + else + server.on "connection", -> + testData.client = server.clients()[0] + callback(testData) + consumer.connect() diff --git a/actioncable/test/javascript/src/test_helpers/index.coffee b/actioncable/test/javascript/src/test_helpers/index.coffee index e0d1e412cd..d36524d9cc 100644 --- a/actioncable/test/javascript/src/test_helpers/index.coffee +++ b/actioncable/test/javascript/src/test_helpers/index.coffee @@ -3,3 +3,6 @@ ActionCable.TestHelpers = testURL: "ws://cable.example.com/" + +originalWebSocket = ActionCable.WebSocket +QUnit.testDone -> ActionCable.WebSocket = originalWebSocket diff --git a/actioncable/test/javascript/src/test_helpers/mock_websocket.coffee b/actioncable/test/javascript/src/test_helpers/mock_websocket.coffee deleted file mode 100644 index b7f86f18f6..0000000000 --- a/actioncable/test/javascript/src/test_helpers/mock_websocket.coffee +++ /dev/null @@ -1,21 +0,0 @@ -#= require mock-socket - -NativeWebSocket = window.WebSocket - -server = null -consumer = null - -ActionCable.TestHelpers.createConsumer = (url, callback) -> - window.WebSocket = MockWebSocket - server = new MockServer url - consumer = ActionCable.createConsumer(url) - callback(consumer, server) - -QUnit.testDone -> - if consumer? - consumer.disconnect() - - if server? - server.clients().forEach (client) -> client.close() - server.close() - window.WebSocket = NativeWebSocket diff --git a/actioncable/test/javascript/src/unit/action_cable_test.coffee b/actioncable/test/javascript/src/unit/action_cable_test.coffee index f9eff64769..3944f3a7f6 100644 --- a/actioncable/test/javascript/src/unit/action_cable_test.coffee +++ b/actioncable/test/javascript/src/unit/action_cable_test.coffee @@ -2,6 +2,23 @@ {testURL} = ActionCable.TestHelpers module "ActionCable", -> + module "Adapters", -> + module "WebSocket", -> + test "default is window.WebSocket", (assert) -> + assert.equal ActionCable.WebSocket, window.WebSocket + + test "configurable", (assert) -> + ActionCable.WebSocket = "" + assert.equal ActionCable.WebSocket, "" + + module "logger", -> + test "default is window.console", (assert) -> + assert.equal ActionCable.logger, window.console + + test "configurable", (assert) -> + ActionCable.logger = "" + assert.equal ActionCable.logger, "" + module "#createConsumer", -> test "uses specified URL", (assert) -> consumer = ActionCable.createConsumer(testURL) diff --git a/actioncable/test/javascript/src/unit/consumer_test.coffee b/actioncable/test/javascript/src/unit/consumer_test.coffee index d8b1450ad8..cf8a592255 100644 --- a/actioncable/test/javascript/src/unit/consumer_test.coffee +++ b/actioncable/test/javascript/src/unit/consumer_test.coffee @@ -1,31 +1,11 @@ {module, test} = QUnit -{testURL, createConsumer} = ActionCable.TestHelpers +{consumerTest} = ActionCable.TestHelpers module "ActionCable.Consumer", -> - test "#connect", (assert) -> - done = assert.async() + consumerTest "#connect", connect: false, ({consumer, server, done}) -> + server.on("connection", done) + consumer.connect() - createConsumer testURL, (consumer, server) -> - server.on "connection", -> - clients = server.clients() - assert.equal clients.length, 1 - assert.equal clients[0].readyState, WebSocket.OPEN - done() - - consumer.connect() - - test "#disconnect", (assert) -> - done = assert.async() - - createConsumer testURL, (consumer, server) -> - server.on "connection", -> - clients = server.clients() - assert.equal clients.length, 1 - - clients[0].addEventListener "close", (event) -> - assert.equal event.type, "close" - done() - - consumer.disconnect() - - consumer.connect() + consumerTest "#disconnect", ({consumer, client, done}) -> + client.addEventListener("close", done) + consumer.disconnect() |