From b8b50e6b043a5b1900922629edf250ccb6006085 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 5 Jul 2015 22:34:23 +0200 Subject: [PATCH] Extract Server configuration into a Configuration object --- lib/action_cable/connection/base.rb | 2 +- lib/action_cable/connection/subscriptions.rb | 4 +- lib/action_cable/server.rb | 1 + lib/action_cable/server/base.rb | 55 +++++++++----------- lib/action_cable/server/broadcasting.rb | 2 +- lib/action_cable/server/configuration.rb | 51 ++++++++++++++++++ test/server_test.rb | 2 +- 7 files changed, 82 insertions(+), 35 deletions(-) create mode 100644 lib/action_cable/server/configuration.rb diff --git a/lib/action_cable/connection/base.rb b/lib/action_cable/connection/base.rb index 69c0db9167..09bbc73e2d 100644 --- a/lib/action_cable/connection/base.rb +++ b/lib/action_cable/connection/base.rb @@ -117,7 +117,7 @@ module ActionCable # Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags. def new_tagged_logger TaggedLoggerProxy.new server.logger, - tags: server.log_tags.map { |tag| tag.respond_to?(:call) ? tag.call(request) : tag.to_s.camelize } + tags: server.config.log_tags.map { |tag| tag.respond_to?(:call) ? tag.call(request) : tag.to_s.camelize } end def started_request_message diff --git a/lib/action_cable/connection/subscriptions.rb b/lib/action_cable/connection/subscriptions.rb index 24ab1bdfbf..800474eee5 100644 --- a/lib/action_cable/connection/subscriptions.rb +++ b/lib/action_cable/connection/subscriptions.rb @@ -22,8 +22,8 @@ module ActionCable id_key = data['identifier'] id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access - subscription_klass = connection.server.registered_channels.detect do |channel_klass| - channel_klass == id_options[:channel].safe_constantize + subscription_klass = connection.server.channel_classes.detect do |channel_class| + channel_class == id_options[:channel].safe_constantize end if subscription_klass diff --git a/lib/action_cable/server.rb b/lib/action_cable/server.rb index a3d6ce6c31..e7cc70b68d 100644 --- a/lib/action_cable/server.rb +++ b/lib/action_cable/server.rb @@ -3,6 +3,7 @@ module ActionCable autoload :Base, 'action_cable/server/base' autoload :Broadcasting, 'action_cable/server/broadcasting' autoload :Connections, 'action_cable/server/connections' + autoload :Configuration, 'action_cable/server/configuration' autoload :Worker, 'action_cable/server/worker' end end diff --git a/lib/action_cable/server/base.rb b/lib/action_cable/server/base.rb index fd3e5e4020..3d1d9e71ff 100644 --- a/lib/action_cable/server/base.rb +++ b/lib/action_cable/server/base.rb @@ -4,43 +4,26 @@ module ActionCable include ActionCable::Server::Broadcasting include ActionCable::Server::Connections - cattr_accessor(:logger, instance_reader: true) { Rails.logger } + cattr_accessor(:config, instance_accessor: true) { ActionCable::Server::Configuration.new } + + def self.logger; config.logger; end + delegate :logger, to: :config - attr_accessor :registered_channels, :redis_config, :log_tags - - def initialize(redis_config:, channels:, worker_pool_size: 100, connection: Connection, log_tags: [ 'ActionCable' ]) - @redis_config = redis_config.with_indifferent_access - @registered_channels = Set.new(channels) - @worker_pool_size = worker_pool_size - @connection_class = connection - @log_tags = log_tags - - @connections = [] - - logger.info "[ActionCable] Initialized server (redis_config: #{@redis_config.inspect}, worker_pool_size: #{@worker_pool_size})" + def initialize end def call(env) - @connection_class.new(self, env).process + config.connection_class.new(self, env).process end def worker_pool - @worker_pool ||= ActionCable::Server::Worker.pool(size: @worker_pool_size) + @worker_pool ||= ActionCable::Server::Worker.pool(size: config.worker_pool_size) end - def pubsub - @pubsub ||= redis.pubsub - end - - def redis - @redis ||= begin - redis = EM::Hiredis.connect(@redis_config[:url]) - redis.on(:reconnect_failed) do - logger.info "[ActionCable] Redis reconnect failed." - # logger.info "[ActionCable] Redis reconnected. Closing all the open connections." - # @connections.map &:close - end - redis + def channel_classes + @channel_classes ||= begin + config.channel_paths.each { |channel_path| require channel_path } + config.channel_class_names.collect { |name| name.constantize } end end @@ -48,10 +31,22 @@ module ActionCable @remote_connections ||= RemoteConnections.new(self) end - def connection_identifiers - @connection_class.identifiers + def pubsub + @pubsub ||= redis.pubsub end + def redis + @redis ||= EM::Hiredis.connect(config.redis[:url]).tap do |redis| + redis.on(:reconnect_failed) do + logger.info "[ActionCable] Redis reconnect failed." + # logger.info "[ActionCable] Redis reconnected. Closing all the open connections." + # @connections.map &:close + end + end + end + + def connection_identifiers + config.connection_class.identifiers end end end diff --git a/lib/action_cable/server/broadcasting.rb b/lib/action_cable/server/broadcasting.rb index 6376e88de0..105ccb2b5c 100644 --- a/lib/action_cable/server/broadcasting.rb +++ b/lib/action_cable/server/broadcasting.rb @@ -10,7 +10,7 @@ module ActionCable end def broadcasting_redis - @broadcasting_redis ||= Redis.new(redis_config) + @broadcasting_redis ||= Redis.new(config.redis) end private diff --git a/lib/action_cable/server/configuration.rb b/lib/action_cable/server/configuration.rb new file mode 100644 index 0000000000..c5783f30e0 --- /dev/null +++ b/lib/action_cable/server/configuration.rb @@ -0,0 +1,51 @@ +module ActionCable + module Server + class Configuration + attr_accessor :logger, :log_tags + attr_accessor :connection_class, :worker_pool_size + attr_accessor :redis_path, :channels_path + + def initialize + @logger = Rails.logger + @log_tags = [] + + @connection_class = ApplicationCable::Connection + @worker_pool_size = 100 + + @redis_path = Rails.root.join('config/redis/cable.yml') + @channels_path = Rails.root.join('app/channels') + end + + def channel_paths + @channels ||= Dir["#{channels_path}/**/*_channel.rb"] + 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 + + def redis + @redis ||= config_for(redis_path).with_indifferent_access + end + + private + # FIXME: Extract this from Rails::Application in a way it can be used here. + def config_for(path) + if path.exist? + require "yaml" + require "erb" + (YAML.load(ERB.new(path.read).result) || {})[Rails.env] || {} + else + raise "Could not load configuration. No such file - #{path}" + end + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{path}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + end + end + end +end + diff --git a/test/server_test.rb b/test/server_test.rb index 1e02497f61..636fa37cf7 100644 --- a/test/server_test.rb +++ b/test/server_test.rb @@ -17,7 +17,7 @@ class ServerTest < ActionCableTest end test "channel registration" do - assert_equal ChatServer.registered_channels, Set.new([ ChatChannel ]) + assert_equal ChatServer.channel_classes, Set.new([ ChatChannel ]) end test "subscribing to a channel with valid params" do