actioncable 5.0.1 → 6.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +31 -117
- data/MIT-LICENSE +1 -1
- data/README.md +4 -535
- data/app/assets/javascripts/action_cable.js +517 -0
- data/lib/action_cable.rb +20 -10
- data/lib/action_cable/channel.rb +3 -0
- data/lib/action_cable/channel/base.rb +31 -23
- data/lib/action_cable/channel/broadcasting.rb +22 -10
- data/lib/action_cable/channel/callbacks.rb +4 -2
- data/lib/action_cable/channel/naming.rb +5 -2
- data/lib/action_cable/channel/periodic_timers.rb +4 -3
- data/lib/action_cable/channel/streams.rb +39 -11
- data/lib/action_cable/channel/test_case.rb +310 -0
- data/lib/action_cable/connection.rb +3 -2
- data/lib/action_cable/connection/authorization.rb +8 -6
- data/lib/action_cable/connection/base.rb +34 -26
- data/lib/action_cable/connection/client_socket.rb +20 -18
- data/lib/action_cable/connection/identification.rb +5 -4
- data/lib/action_cable/connection/internal_channel.rb +4 -2
- data/lib/action_cable/connection/message_buffer.rb +3 -2
- data/lib/action_cable/connection/stream.rb +9 -5
- data/lib/action_cable/connection/stream_event_loop.rb +4 -2
- data/lib/action_cable/connection/subscriptions.rb +14 -13
- data/lib/action_cable/connection/tagged_logger_proxy.rb +4 -2
- data/lib/action_cable/connection/test_case.rb +234 -0
- data/lib/action_cable/connection/web_socket.rb +7 -5
- data/lib/action_cable/engine.rb +7 -5
- data/lib/action_cable/gem_version.rb +5 -3
- data/lib/action_cable/helpers/action_cable_helper.rb +6 -4
- data/lib/action_cable/remote_connections.rb +9 -4
- data/lib/action_cable/server.rb +2 -1
- data/lib/action_cable/server/base.rb +17 -10
- data/lib/action_cable/server/broadcasting.rb +9 -3
- data/lib/action_cable/server/configuration.rb +21 -22
- data/lib/action_cable/server/connections.rb +2 -0
- data/lib/action_cable/server/worker.rb +11 -11
- data/lib/action_cable/server/worker/active_record_connection_management.rb +2 -0
- data/lib/action_cable/subscription_adapter.rb +4 -0
- data/lib/action_cable/subscription_adapter/async.rb +3 -1
- data/lib/action_cable/subscription_adapter/base.rb +6 -0
- data/lib/action_cable/subscription_adapter/channel_prefix.rb +28 -0
- data/lib/action_cable/subscription_adapter/inline.rb +2 -0
- data/lib/action_cable/subscription_adapter/postgresql.rb +40 -14
- data/lib/action_cable/subscription_adapter/redis.rb +19 -11
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +3 -1
- data/lib/action_cable/subscription_adapter/test.rb +40 -0
- data/lib/action_cable/test_case.rb +11 -0
- data/lib/action_cable/test_helper.rb +133 -0
- data/lib/action_cable/version.rb +3 -1
- data/lib/rails/generators/channel/USAGE +5 -6
- data/lib/rails/generators/channel/channel_generator.rb +16 -11
- data/lib/rails/generators/channel/templates/application_cable/{channel.rb → channel.rb.tt} +0 -0
- data/lib/rails/generators/channel/templates/application_cable/{connection.rb → connection.rb.tt} +0 -0
- data/lib/rails/generators/channel/templates/{channel.rb → channel.rb.tt} +0 -0
- data/lib/rails/generators/channel/templates/{assets/channel.js → javascript/channel.js.tt} +6 -4
- data/lib/rails/generators/channel/templates/javascript/consumer.js.tt +6 -0
- data/lib/rails/generators/channel/templates/javascript/index.js.tt +5 -0
- data/lib/rails/generators/test_unit/channel_generator.rb +20 -0
- data/lib/rails/generators/test_unit/templates/channel_test.rb.tt +8 -0
- metadata +46 -38
- data/lib/action_cable/connection/faye_client_socket.rb +0 -48
- data/lib/action_cable/connection/faye_event_loop.rb +0 -44
- data/lib/action_cable/subscription_adapter/evented_redis.rb +0 -79
- data/lib/assets/compiled/action_cable.js +0 -597
- data/lib/rails/generators/channel/templates/assets/cable.js +0 -13
- data/lib/rails/generators/channel/templates/assets/channel.coffee +0 -14
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionCable
|
2
4
|
module Server
|
3
5
|
# Broadcasting is how other parts of your application can send messages to a channel's subscribers. As explained in Channel, most of the time, these
|
@@ -38,9 +40,13 @@ module ActionCable
|
|
38
40
|
end
|
39
41
|
|
40
42
|
def broadcast(message)
|
41
|
-
server.logger.
|
42
|
-
|
43
|
-
|
43
|
+
server.logger.debug { "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}" }
|
44
|
+
|
45
|
+
payload = { broadcasting: broadcasting, message: message, coder: coder }
|
46
|
+
ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do
|
47
|
+
encoded = coder ? coder.encode(message) : message
|
48
|
+
server.pubsub.broadcast broadcasting, encoded
|
49
|
+
end
|
44
50
|
end
|
45
51
|
end
|
46
52
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionCable
|
2
4
|
module Server
|
3
5
|
# An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak Action Cable configuration
|
4
6
|
# in a Rails config initializer.
|
5
7
|
class Configuration
|
6
8
|
attr_accessor :logger, :log_tags
|
7
|
-
attr_accessor :
|
9
|
+
attr_accessor :connection_class, :worker_pool_size
|
8
10
|
attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
|
9
11
|
attr_accessor :cable, :url, :mount_path
|
10
12
|
|
@@ -22,36 +24,33 @@ module ActionCable
|
|
22
24
|
# If the adapter cannot be found, this will default to the Redis adapter.
|
23
25
|
# Also makes sure proper dependencies are required.
|
24
26
|
def pubsub_adapter
|
25
|
-
adapter = (cable.fetch(
|
27
|
+
adapter = (cable.fetch("adapter") { "redis" })
|
28
|
+
|
29
|
+
# Require the adapter itself and give useful feedback about
|
30
|
+
# 1. Missing adapter gems and
|
31
|
+
# 2. Adapter gems' missing dependencies.
|
26
32
|
path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
|
27
33
|
begin
|
28
34
|
require path_to_adapter
|
29
|
-
rescue Gem::LoadError => e
|
30
|
-
raise Gem::LoadError, "Specified '#{adapter}' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by Action Cable)."
|
31
35
|
rescue LoadError => e
|
32
|
-
|
36
|
+
# We couldn't require the adapter itself. Raise an exception that
|
37
|
+
# points out config typos and missing gems.
|
38
|
+
if e.path == path_to_adapter
|
39
|
+
# We can assume that a non-builtin adapter was specified, so it's
|
40
|
+
# either misspelled or missing from Gemfile.
|
41
|
+
raise e.class, "Could not load the '#{adapter}' Action Cable pubsub adapter. Ensure that the adapter is spelled correctly in config/cable.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
|
42
|
+
|
43
|
+
# Bubbled up from the adapter require. Prefix the exception message
|
44
|
+
# with some guidance about how to address it and reraise.
|
45
|
+
else
|
46
|
+
raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace
|
47
|
+
end
|
33
48
|
end
|
34
49
|
|
35
50
|
adapter = adapter.camelize
|
36
|
-
adapter =
|
51
|
+
adapter = "PostgreSQL" if adapter == "Postgresql"
|
37
52
|
"ActionCable::SubscriptionAdapter::#{adapter}".constantize
|
38
53
|
end
|
39
|
-
|
40
|
-
def event_loop_class
|
41
|
-
if use_faye
|
42
|
-
ActionCable::Connection::FayeEventLoop
|
43
|
-
else
|
44
|
-
ActionCable::Connection::StreamEventLoop
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def client_socket_class
|
49
|
-
if use_faye
|
50
|
-
ActionCable::Connection::FayeClientSocket
|
51
|
-
else
|
52
|
-
ActionCable::Connection::ClientSocket
|
53
|
-
end
|
54
|
-
end
|
55
54
|
end
|
56
55
|
end
|
57
56
|
end
|
@@ -1,6 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/callbacks"
|
4
|
+
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
5
|
+
require "action_cable/server/worker/active_record_connection_management"
|
6
|
+
require "concurrent"
|
4
7
|
|
5
8
|
module ActionCable
|
6
9
|
module Server
|
@@ -54,19 +57,16 @@ module ActionCable
|
|
54
57
|
|
55
58
|
def invoke(receiver, method, *args, connection:, &block)
|
56
59
|
work(connection) do
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
logger.error e.backtrace.join("\n")
|
60
|
+
receiver.send method, *args, &block
|
61
|
+
rescue Exception => e
|
62
|
+
logger.error "There was an exception - #{e.class}(#{e.message})"
|
63
|
+
logger.error e.backtrace.join("\n")
|
62
64
|
|
63
|
-
|
64
|
-
end
|
65
|
+
receiver.handle_exception if receiver.respond_to?(:handle_exception)
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
68
69
|
private
|
69
|
-
|
70
70
|
def logger
|
71
71
|
ActionCable.server.logger
|
72
72
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionCable
|
2
4
|
module SubscriptionAdapter
|
3
5
|
class Base
|
@@ -23,6 +25,10 @@ module ActionCable
|
|
23
25
|
def shutdown
|
24
26
|
raise NotImplementedError
|
25
27
|
end
|
28
|
+
|
29
|
+
def identifier
|
30
|
+
@server.config.cable[:id] ||= "ActionCable-PID-#{$$}"
|
31
|
+
end
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionCable
|
4
|
+
module SubscriptionAdapter
|
5
|
+
module ChannelPrefix # :nodoc:
|
6
|
+
def broadcast(channel, payload)
|
7
|
+
channel = channel_with_prefix(channel)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(channel, callback, success_callback = nil)
|
12
|
+
channel = channel_with_prefix(channel)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def unsubscribe(channel, callback)
|
17
|
+
channel = channel_with_prefix(channel)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
# Returns the channel name, including channel_prefix specified in cable.yml
|
23
|
+
def channel_with_prefix(channel)
|
24
|
+
[@server.config.cable[:channel_prefix], channel].compact.join(":")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,50 +1,76 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem "pg", "~> 1.1"
|
4
|
+
require "pg"
|
5
|
+
require "thread"
|
6
|
+
require "digest/sha1"
|
4
7
|
|
5
8
|
module ActionCable
|
6
9
|
module SubscriptionAdapter
|
7
10
|
class PostgreSQL < Base # :nodoc:
|
11
|
+
prepend ChannelPrefix
|
12
|
+
|
8
13
|
def initialize(*)
|
9
14
|
super
|
10
15
|
@listener = nil
|
11
16
|
end
|
12
17
|
|
13
18
|
def broadcast(channel, payload)
|
14
|
-
|
15
|
-
pg_conn.exec("NOTIFY #{pg_conn.escape_identifier(channel)}, '#{pg_conn.escape_string(payload)}'")
|
19
|
+
with_broadcast_connection do |pg_conn|
|
20
|
+
pg_conn.exec("NOTIFY #{pg_conn.escape_identifier(channel_identifier(channel))}, '#{pg_conn.escape_string(payload)}'")
|
16
21
|
end
|
17
22
|
end
|
18
23
|
|
19
24
|
def subscribe(channel, callback, success_callback = nil)
|
20
|
-
listener.add_subscriber(channel, callback, success_callback)
|
25
|
+
listener.add_subscriber(channel_identifier(channel), callback, success_callback)
|
21
26
|
end
|
22
27
|
|
23
28
|
def unsubscribe(channel, callback)
|
24
|
-
listener.remove_subscriber(channel, callback)
|
29
|
+
listener.remove_subscriber(channel_identifier(channel), callback)
|
25
30
|
end
|
26
31
|
|
27
32
|
def shutdown
|
28
33
|
listener.shutdown
|
29
34
|
end
|
30
35
|
|
31
|
-
def
|
32
|
-
ActiveRecord::Base.connection_pool.
|
33
|
-
|
36
|
+
def with_subscriptions_connection(&block) # :nodoc:
|
37
|
+
ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
|
38
|
+
# Action Cable is taking ownership over this database connection, and
|
39
|
+
# will perform the necessary cleanup tasks
|
40
|
+
ActiveRecord::Base.connection_pool.remove(conn)
|
41
|
+
end
|
42
|
+
pg_conn = ar_conn.raw_connection
|
34
43
|
|
35
|
-
|
36
|
-
|
37
|
-
|
44
|
+
verify!(pg_conn)
|
45
|
+
pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(identifier)}")
|
46
|
+
yield pg_conn
|
47
|
+
ensure
|
48
|
+
ar_conn.disconnect!
|
49
|
+
end
|
38
50
|
|
51
|
+
def with_broadcast_connection(&block) # :nodoc:
|
52
|
+
ActiveRecord::Base.connection_pool.with_connection do |ar_conn|
|
53
|
+
pg_conn = ar_conn.raw_connection
|
54
|
+
verify!(pg_conn)
|
39
55
|
yield pg_conn
|
40
56
|
end
|
41
57
|
end
|
42
58
|
|
43
59
|
private
|
60
|
+
def channel_identifier(channel)
|
61
|
+
channel.size > 63 ? Digest::SHA1.hexdigest(channel) : channel
|
62
|
+
end
|
63
|
+
|
44
64
|
def listener
|
45
65
|
@listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) }
|
46
66
|
end
|
47
67
|
|
68
|
+
def verify!(pg_conn)
|
69
|
+
unless pg_conn.is_a?(PG::Connection)
|
70
|
+
raise "The Active Record database must be PostgreSQL in order to use the PostgreSQL Action Cable storage adapter"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
48
74
|
class Listener < SubscriberMap
|
49
75
|
def initialize(adapter, event_loop)
|
50
76
|
super()
|
@@ -60,7 +86,7 @@ module ActionCable
|
|
60
86
|
end
|
61
87
|
|
62
88
|
def listen
|
63
|
-
@adapter.
|
89
|
+
@adapter.with_subscriptions_connection do |pg_conn|
|
64
90
|
catch :shutdown do
|
65
91
|
loop do
|
66
92
|
until @queue.empty?
|
@@ -1,14 +1,22 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require "thread"
|
4
|
+
|
5
|
+
gem "redis", ">= 3", "< 5"
|
6
|
+
require "redis"
|
7
|
+
|
8
|
+
require "active_support/core_ext/hash/except"
|
5
9
|
|
6
10
|
module ActionCable
|
7
11
|
module SubscriptionAdapter
|
8
12
|
class Redis < Base # :nodoc:
|
9
|
-
|
13
|
+
prepend ChannelPrefix
|
14
|
+
|
15
|
+
# Overwrite this factory method for Redis connections if you want to use a different Redis library than the redis gem.
|
10
16
|
# This is needed, for example, when using Makara proxies for distributed Redis.
|
11
|
-
cattr_accessor
|
17
|
+
cattr_accessor :redis_connector, default: ->(config) do
|
18
|
+
::Redis.new(config.except(:adapter, :channel_prefix))
|
19
|
+
end
|
12
20
|
|
13
21
|
def initialize(*)
|
14
22
|
super
|
@@ -48,7 +56,7 @@ module ActionCable
|
|
48
56
|
end
|
49
57
|
|
50
58
|
def redis_connection
|
51
|
-
self.class.redis_connector.call(@server.config.cable)
|
59
|
+
self.class.redis_connector.call(@server.config.cable.merge(id: identifier))
|
52
60
|
end
|
53
61
|
|
54
62
|
class Listener < SubscriberMap
|
@@ -70,9 +78,9 @@ module ActionCable
|
|
70
78
|
|
71
79
|
def listen(conn)
|
72
80
|
conn.without_reconnect do
|
73
|
-
original_client = conn.client
|
81
|
+
original_client = conn.respond_to?(:_client) ? conn._client : conn.client
|
74
82
|
|
75
|
-
conn.subscribe(
|
83
|
+
conn.subscribe("_action_cable_internal") do |on|
|
76
84
|
on.subscribe do |chan, count|
|
77
85
|
@subscription_lock.synchronize do
|
78
86
|
if count == 1
|
@@ -111,7 +119,7 @@ module ActionCable
|
|
111
119
|
return if @thread.nil?
|
112
120
|
|
113
121
|
when_connected do
|
114
|
-
send_command(
|
122
|
+
send_command("unsubscribe")
|
115
123
|
@raw_client = nil
|
116
124
|
end
|
117
125
|
end
|
@@ -123,13 +131,13 @@ module ActionCable
|
|
123
131
|
@subscription_lock.synchronize do
|
124
132
|
ensure_listener_running
|
125
133
|
@subscribe_callbacks[channel] << on_success
|
126
|
-
when_connected { send_command(
|
134
|
+
when_connected { send_command("subscribe", channel) }
|
127
135
|
end
|
128
136
|
end
|
129
137
|
|
130
138
|
def remove_channel(channel)
|
131
139
|
@subscription_lock.synchronize do
|
132
|
-
when_connected { send_command(
|
140
|
+
when_connected { send_command("unsubscribe", channel) }
|
133
141
|
end
|
134
142
|
end
|
135
143
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "async"
|
4
|
+
|
5
|
+
module ActionCable
|
6
|
+
module SubscriptionAdapter
|
7
|
+
# == Test adapter for Action Cable
|
8
|
+
#
|
9
|
+
# The test adapter should be used only in testing. Along with
|
10
|
+
# <tt>ActionCable::TestHelper</tt> it makes a great tool to test your Rails application.
|
11
|
+
#
|
12
|
+
# To use the test adapter set +adapter+ value to +test+ in your +config/cable.yml+ file.
|
13
|
+
#
|
14
|
+
# NOTE: Test adapter extends the <tt>ActionCable::SubscriptionsAdapter::Async</tt> adapter,
|
15
|
+
# so it could be used in system tests too.
|
16
|
+
class Test < Async
|
17
|
+
def broadcast(channel, payload)
|
18
|
+
broadcasts(channel) << payload
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def broadcasts(channel)
|
23
|
+
channels_data[channel] ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear_messages(channel)
|
27
|
+
channels_data[channel] = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear
|
31
|
+
@channels_data = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def channels_data
|
36
|
+
@channels_data ||= {}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|