actioncable 6.1.7.9 → 7.2.2.1
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 +4 -4
- data/CHANGELOG.md +33 -160
- data/MIT-LICENSE +1 -1
- data/README.md +5 -5
- data/app/assets/javascripts/action_cable.js +239 -302
- data/app/assets/javascripts/actioncable.esm.js +512 -0
- data/app/assets/javascripts/actioncable.js +510 -0
- data/lib/action_cable/channel/base.rb +114 -90
- data/lib/action_cable/channel/broadcasting.rb +25 -16
- data/lib/action_cable/channel/callbacks.rb +39 -0
- data/lib/action_cable/channel/naming.rb +10 -7
- data/lib/action_cable/channel/periodic_timers.rb +7 -7
- data/lib/action_cable/channel/streams.rb +81 -68
- data/lib/action_cable/channel/test_case.rb +133 -87
- data/lib/action_cable/connection/authorization.rb +4 -1
- data/lib/action_cable/connection/base.rb +71 -43
- data/lib/action_cable/connection/callbacks.rb +57 -0
- data/lib/action_cable/connection/client_socket.rb +3 -1
- data/lib/action_cable/connection/identification.rb +10 -6
- data/lib/action_cable/connection/internal_channel.rb +7 -2
- data/lib/action_cable/connection/message_buffer.rb +4 -1
- data/lib/action_cable/connection/stream.rb +2 -2
- data/lib/action_cable/connection/stream_event_loop.rb +4 -4
- data/lib/action_cable/connection/subscriptions.rb +8 -3
- data/lib/action_cable/connection/tagged_logger_proxy.rb +14 -9
- data/lib/action_cable/connection/test_case.rb +67 -55
- data/lib/action_cable/connection/web_socket.rb +11 -7
- data/lib/action_cable/deprecator.rb +9 -0
- data/lib/action_cable/engine.rb +28 -9
- data/lib/action_cable/gem_version.rb +7 -5
- data/lib/action_cable/helpers/action_cable_helper.rb +21 -18
- data/lib/action_cable/remote_connections.rb +25 -13
- data/lib/action_cable/server/base.rb +29 -14
- data/lib/action_cable/server/broadcasting.rb +24 -16
- data/lib/action_cable/server/configuration.rb +28 -14
- data/lib/action_cable/server/connections.rb +13 -5
- data/lib/action_cable/server/worker/active_record_connection_management.rb +4 -2
- data/lib/action_cable/server/worker.rb +7 -7
- data/lib/action_cable/subscription_adapter/async.rb +1 -1
- data/lib/action_cable/subscription_adapter/base.rb +2 -0
- data/lib/action_cable/subscription_adapter/channel_prefix.rb +2 -0
- data/lib/action_cable/subscription_adapter/inline.rb +2 -0
- data/lib/action_cable/subscription_adapter/postgresql.rb +6 -5
- data/lib/action_cable/subscription_adapter/redis.rb +101 -25
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +2 -0
- data/lib/action_cable/subscription_adapter/test.rb +7 -6
- data/lib/action_cable/test_case.rb +2 -0
- data/lib/action_cable/test_helper.rb +89 -59
- data/lib/action_cable/version.rb +3 -1
- data/lib/action_cable.rb +30 -12
- data/lib/rails/generators/channel/USAGE +14 -8
- data/lib/rails/generators/channel/channel_generator.rb +95 -20
- data/lib/rails/generators/channel/templates/javascript/index.js.tt +1 -5
- data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
- metadata +29 -15
- data/lib/action_cable/channel.rb +0 -17
- data/lib/action_cable/connection.rb +0 -22
- data/lib/action_cable/server.rb +0 -16
- data/lib/action_cable/subscription_adapter.rb +0 -12
@@ -1,14 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "rack"
|
6
|
+
|
3
7
|
module ActionCable
|
4
8
|
module Server
|
5
|
-
#
|
6
|
-
#
|
9
|
+
# # Action Cable Server Configuration
|
10
|
+
#
|
11
|
+
# An instance of this configuration object is available via
|
12
|
+
# ActionCable.server.config, which allows you to tweak Action Cable
|
13
|
+
# configuration in a Rails config initializer.
|
7
14
|
class Configuration
|
8
15
|
attr_accessor :logger, :log_tags
|
9
16
|
attr_accessor :connection_class, :worker_pool_size
|
10
|
-
attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
|
17
|
+
attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host, :filter_parameters
|
11
18
|
attr_accessor :cable, :url, :mount_path
|
19
|
+
attr_accessor :precompile_assets
|
20
|
+
attr_accessor :health_check_path, :health_check_application
|
12
21
|
|
13
22
|
def initialize
|
14
23
|
@log_tags = []
|
@@ -18,30 +27,35 @@ module ActionCable
|
|
18
27
|
|
19
28
|
@disable_request_forgery_protection = false
|
20
29
|
@allow_same_origin_as_host = true
|
30
|
+
@filter_parameters = []
|
31
|
+
|
32
|
+
@health_check_application = ->(env) {
|
33
|
+
[200, { Rack::CONTENT_TYPE => "text/html", "date" => Time.now.httpdate }, []]
|
34
|
+
}
|
21
35
|
end
|
22
36
|
|
23
|
-
# Returns constant of subscription adapter specified in config/cable.yml.
|
24
|
-
#
|
25
|
-
#
|
37
|
+
# Returns constant of subscription adapter specified in config/cable.yml. If the
|
38
|
+
# adapter cannot be found, this will default to the Redis adapter. Also makes
|
39
|
+
# sure proper dependencies are required.
|
26
40
|
def pubsub_adapter
|
27
41
|
adapter = (cable.fetch("adapter") { "redis" })
|
28
42
|
|
29
43
|
# Require the adapter itself and give useful feedback about
|
30
|
-
#
|
31
|
-
#
|
44
|
+
# 1. Missing adapter gems and
|
45
|
+
# 2. Adapter gems' missing dependencies.
|
32
46
|
path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
|
33
47
|
begin
|
34
48
|
require path_to_adapter
|
35
49
|
rescue LoadError => e
|
36
|
-
# We couldn't require the adapter itself. Raise an exception that
|
37
|
-
#
|
50
|
+
# We couldn't require the adapter itself. Raise an exception that points out
|
51
|
+
# config typos and missing gems.
|
38
52
|
if e.path == path_to_adapter
|
39
|
-
# We can assume that a non-builtin adapter was specified, so it's
|
40
|
-
#
|
53
|
+
# We can assume that a non-builtin adapter was specified, so it's either
|
54
|
+
# misspelled or missing from Gemfile.
|
41
55
|
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
56
|
|
43
|
-
# Bubbled up from the adapter require. Prefix the exception message
|
44
|
-
#
|
57
|
+
# Bubbled up from the adapter require. Prefix the exception message with some
|
58
|
+
# guidance about how to address it and reraise.
|
45
59
|
else
|
46
60
|
raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace
|
47
61
|
end
|
@@ -1,9 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Server
|
5
|
-
#
|
6
|
-
#
|
7
|
+
# # Action Cable Server Connections
|
8
|
+
#
|
9
|
+
# Collection class for all the connections that have been established on this
|
10
|
+
# specific server. Remember, usually you'll run many Action Cable servers, so
|
11
|
+
# you can't use this collection as a full list of all of the connections
|
12
|
+
# established against your application. Instead, use RemoteConnections for that.
|
7
13
|
module Connections # :nodoc:
|
8
14
|
BEAT_INTERVAL = 3
|
9
15
|
|
@@ -19,12 +25,14 @@ module ActionCable
|
|
19
25
|
connections.delete connection
|
20
26
|
end
|
21
27
|
|
22
|
-
# WebSocket connection implementations differ on when they'll mark a connection
|
23
|
-
#
|
28
|
+
# WebSocket connection implementations differ on when they'll mark a connection
|
29
|
+
# as stale. We basically never want a connection to go stale, as you then can't
|
30
|
+
# rely on being able to communicate with the connection. To solve this, a 3
|
31
|
+
# second heartbeat runs on all connections. If the beat fails, we automatically
|
24
32
|
# disconnect.
|
25
33
|
def setup_heartbeat_timer
|
26
34
|
@heartbeat_timer ||= event_loop.timer(BEAT_INTERVAL) do
|
27
|
-
event_loop.post { connections.
|
35
|
+
event_loop.post { connections.each(&:beat) }
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Server
|
5
7
|
class Worker
|
@@ -12,8 +14,8 @@ module ActionCable
|
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
15
|
-
def with_database_connections
|
16
|
-
connection.logger.tag(ActiveRecord::Base.logger)
|
17
|
+
def with_database_connections(&block)
|
18
|
+
connection.logger.tag(ActiveRecord::Base.logger, &block)
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "active_support/callbacks"
|
4
6
|
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
5
|
-
require "action_cable/server/worker/active_record_connection_management"
|
6
7
|
require "concurrent"
|
7
8
|
|
8
9
|
module ActionCable
|
@@ -19,14 +20,15 @@ module ActionCable
|
|
19
20
|
|
20
21
|
def initialize(max_size: 5)
|
21
22
|
@executor = Concurrent::ThreadPoolExecutor.new(
|
23
|
+
name: "ActionCable",
|
22
24
|
min_threads: 1,
|
23
25
|
max_threads: max_size,
|
24
26
|
max_queue: 0,
|
25
27
|
)
|
26
28
|
end
|
27
29
|
|
28
|
-
# Stop processing work: any work that has not already started
|
29
|
-
#
|
30
|
+
# Stop processing work: any work that has not already started running will be
|
31
|
+
# discarded from the queue
|
30
32
|
def halt
|
31
33
|
@executor.shutdown
|
32
34
|
end
|
@@ -35,12 +37,10 @@ module ActionCable
|
|
35
37
|
@executor.shuttingdown?
|
36
38
|
end
|
37
39
|
|
38
|
-
def work(connection)
|
40
|
+
def work(connection, &block)
|
39
41
|
self.connection = connection
|
40
42
|
|
41
|
-
run_callbacks :work
|
42
|
-
yield
|
43
|
-
end
|
43
|
+
run_callbacks :work, &block
|
44
44
|
ensure
|
45
45
|
self.connection = nil
|
46
46
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
gem "pg", "~> 1.1"
|
4
6
|
require "pg"
|
5
|
-
require "
|
6
|
-
require "digest/sha1"
|
7
|
+
require "openssl"
|
7
8
|
|
8
9
|
module ActionCable
|
9
10
|
module SubscriptionAdapter
|
@@ -35,8 +36,8 @@ module ActionCable
|
|
35
36
|
|
36
37
|
def with_subscriptions_connection(&block) # :nodoc:
|
37
38
|
ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
|
38
|
-
# Action Cable is taking ownership over this database connection, and
|
39
|
-
#
|
39
|
+
# Action Cable is taking ownership over this database connection, and will
|
40
|
+
# perform the necessary cleanup tasks
|
40
41
|
ActiveRecord::Base.connection_pool.remove(conn)
|
41
42
|
end
|
42
43
|
pg_conn = ar_conn.raw_connection
|
@@ -58,7 +59,7 @@ module ActionCable
|
|
58
59
|
|
59
60
|
private
|
60
61
|
def channel_identifier(channel)
|
61
|
-
channel.size > 63 ? Digest::SHA1.hexdigest(channel) : channel
|
62
|
+
channel.size > 63 ? OpenSSL::Digest::SHA1.hexdigest(channel) : channel
|
62
63
|
end
|
63
64
|
|
64
65
|
def listener
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# :markup: markdown
|
4
4
|
|
5
|
-
gem "redis", ">=
|
5
|
+
gem "redis", ">= 4", "< 6"
|
6
6
|
require "redis"
|
7
7
|
|
8
8
|
require "active_support/core_ext/hash/except"
|
@@ -12,8 +12,9 @@ module ActionCable
|
|
12
12
|
class Redis < Base # :nodoc:
|
13
13
|
prepend ChannelPrefix
|
14
14
|
|
15
|
-
# Overwrite this factory method for Redis connections if you want to use a
|
16
|
-
# This is needed, for example, when
|
15
|
+
# Overwrite this factory method for Redis connections if you want to use a
|
16
|
+
# different Redis library than the redis gem. This is needed, for example, when
|
17
|
+
# using Makara proxies for distributed Redis.
|
17
18
|
cattr_accessor :redis_connector, default: ->(config) do
|
18
19
|
::Redis.new(config.except(:adapter, :channel_prefix))
|
19
20
|
end
|
@@ -46,7 +47,7 @@ module ActionCable
|
|
46
47
|
|
47
48
|
private
|
48
49
|
def listener
|
49
|
-
@listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) }
|
50
|
+
@listener || @server.mutex.synchronize { @listener ||= Listener.new(self, config_options, @server.event_loop) }
|
50
51
|
end
|
51
52
|
|
52
53
|
def redis_connection_for_broadcasts
|
@@ -56,11 +57,15 @@ module ActionCable
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def redis_connection
|
59
|
-
self.class.redis_connector.call(
|
60
|
+
self.class.redis_connector.call(config_options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def config_options
|
64
|
+
@config_options ||= @server.config.cable.deep_symbolize_keys.merge(id: identifier)
|
60
65
|
end
|
61
66
|
|
62
67
|
class Listener < SubscriberMap
|
63
|
-
def initialize(adapter, event_loop)
|
68
|
+
def initialize(adapter, config_options, event_loop)
|
64
69
|
super()
|
65
70
|
|
66
71
|
@adapter = adapter
|
@@ -69,7 +74,12 @@ module ActionCable
|
|
69
74
|
@subscribe_callbacks = Hash.new { |h, k| h[k] = [] }
|
70
75
|
@subscription_lock = Mutex.new
|
71
76
|
|
72
|
-
@
|
77
|
+
@reconnect_attempt = 0
|
78
|
+
# Use the same config as used by Redis conn
|
79
|
+
@reconnect_attempts = config_options.fetch(:reconnect_attempts, 1)
|
80
|
+
@reconnect_attempts = Array.new(@reconnect_attempts, 0) if @reconnect_attempts.is_a?(Integer)
|
81
|
+
|
82
|
+
@subscribed_client = nil
|
73
83
|
|
74
84
|
@when_connected = []
|
75
85
|
|
@@ -78,13 +88,14 @@ module ActionCable
|
|
78
88
|
|
79
89
|
def listen(conn)
|
80
90
|
conn.without_reconnect do
|
81
|
-
original_client = conn
|
91
|
+
original_client = extract_subscribed_client(conn)
|
82
92
|
|
83
93
|
conn.subscribe("_action_cable_internal") do |on|
|
84
94
|
on.subscribe do |chan, count|
|
85
95
|
@subscription_lock.synchronize do
|
86
96
|
if count == 1
|
87
|
-
@
|
97
|
+
@reconnect_attempt = 0
|
98
|
+
@subscribed_client = original_client
|
88
99
|
|
89
100
|
until @when_connected.empty?
|
90
101
|
@when_connected.shift.call
|
@@ -106,7 +117,7 @@ module ActionCable
|
|
106
117
|
on.unsubscribe do |chan, count|
|
107
118
|
if count == 0
|
108
119
|
@subscription_lock.synchronize do
|
109
|
-
@
|
120
|
+
@subscribed_client = nil
|
110
121
|
end
|
111
122
|
end
|
112
123
|
end
|
@@ -119,8 +130,8 @@ module ActionCable
|
|
119
130
|
return if @thread.nil?
|
120
131
|
|
121
132
|
when_connected do
|
122
|
-
|
123
|
-
@
|
133
|
+
@subscribed_client.unsubscribe
|
134
|
+
@subscribed_client = nil
|
124
135
|
end
|
125
136
|
end
|
126
137
|
|
@@ -131,13 +142,13 @@ module ActionCable
|
|
131
142
|
@subscription_lock.synchronize do
|
132
143
|
ensure_listener_running
|
133
144
|
@subscribe_callbacks[channel] << on_success
|
134
|
-
when_connected {
|
145
|
+
when_connected { @subscribed_client.subscribe(channel) }
|
135
146
|
end
|
136
147
|
end
|
137
148
|
|
138
149
|
def remove_channel(channel)
|
139
150
|
@subscription_lock.synchronize do
|
140
|
-
when_connected {
|
151
|
+
when_connected { @subscribed_client.unsubscribe(channel) }
|
141
152
|
end
|
142
153
|
end
|
143
154
|
|
@@ -150,28 +161,93 @@ module ActionCable
|
|
150
161
|
@thread ||= Thread.new do
|
151
162
|
Thread.current.abort_on_exception = true
|
152
163
|
|
153
|
-
|
154
|
-
|
164
|
+
begin
|
165
|
+
conn = @adapter.redis_connection_for_subscriptions
|
166
|
+
listen conn
|
167
|
+
rescue ConnectionError
|
168
|
+
reset
|
169
|
+
if retry_connecting?
|
170
|
+
when_connected { resubscribe }
|
171
|
+
retry
|
172
|
+
end
|
173
|
+
end
|
155
174
|
end
|
156
175
|
end
|
157
176
|
|
158
177
|
def when_connected(&block)
|
159
|
-
if @
|
178
|
+
if @subscribed_client
|
160
179
|
block.call
|
161
180
|
else
|
162
181
|
@when_connected << block
|
163
182
|
end
|
164
183
|
end
|
165
184
|
|
166
|
-
def
|
167
|
-
@
|
185
|
+
def retry_connecting?
|
186
|
+
@reconnect_attempt += 1
|
187
|
+
|
188
|
+
return false if @reconnect_attempt > @reconnect_attempts.size
|
189
|
+
|
190
|
+
sleep_t = @reconnect_attempts[@reconnect_attempt - 1]
|
191
|
+
|
192
|
+
sleep(sleep_t) if sleep_t > 0
|
193
|
+
|
194
|
+
true
|
195
|
+
end
|
196
|
+
|
197
|
+
def resubscribe
|
198
|
+
channels = @sync.synchronize do
|
199
|
+
@subscribers.keys
|
200
|
+
end
|
201
|
+
@subscribed_client.subscribe(*channels) unless channels.empty?
|
202
|
+
end
|
203
|
+
|
204
|
+
def reset
|
205
|
+
@subscription_lock.synchronize do
|
206
|
+
@subscribed_client = nil
|
207
|
+
@subscribe_callbacks.clear
|
208
|
+
@when_connected.clear
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
if ::Redis::VERSION < "5"
|
213
|
+
ConnectionError = ::Redis::BaseConnectionError
|
214
|
+
|
215
|
+
class SubscribedClient
|
216
|
+
def initialize(raw_client)
|
217
|
+
@raw_client = raw_client
|
218
|
+
end
|
219
|
+
|
220
|
+
def subscribe(*channel)
|
221
|
+
send_command("subscribe", *channel)
|
222
|
+
end
|
223
|
+
|
224
|
+
def unsubscribe(*channel)
|
225
|
+
send_command("unsubscribe", *channel)
|
226
|
+
end
|
168
227
|
|
169
|
-
|
170
|
-
|
171
|
-
|
228
|
+
private
|
229
|
+
def send_command(*command)
|
230
|
+
@raw_client.write(command)
|
231
|
+
|
232
|
+
very_raw_connection =
|
233
|
+
@raw_client.connection.instance_variable_defined?(:@connection) &&
|
234
|
+
@raw_client.connection.instance_variable_get(:@connection)
|
235
|
+
|
236
|
+
if very_raw_connection && very_raw_connection.respond_to?(:flush)
|
237
|
+
very_raw_connection.flush
|
238
|
+
end
|
239
|
+
nil
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def extract_subscribed_client(conn)
|
244
|
+
SubscribedClient.new(conn._client)
|
245
|
+
end
|
246
|
+
else
|
247
|
+
ConnectionError = RedisClient::ConnectionError
|
172
248
|
|
173
|
-
|
174
|
-
|
249
|
+
def extract_subscribed_client(conn)
|
250
|
+
conn
|
175
251
|
end
|
176
252
|
end
|
177
253
|
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# :markup: markdown
|
4
4
|
|
5
5
|
module ActionCable
|
6
6
|
module SubscriptionAdapter
|
7
|
-
#
|
7
|
+
# ## Test adapter for Action Cable
|
8
8
|
#
|
9
9
|
# The test adapter should be used only in testing. Along with
|
10
|
-
#
|
10
|
+
# ActionCable::TestHelper it makes a great tool to test your Rails application.
|
11
11
|
#
|
12
|
-
# To use the test adapter set
|
12
|
+
# To use the test adapter set `adapter` value to `test` in your
|
13
|
+
# `config/cable.yml` file.
|
13
14
|
#
|
14
|
-
# NOTE: Test adapter extends the
|
15
|
-
# so it could be used in system tests too.
|
15
|
+
# NOTE: `Test` adapter extends the `ActionCable::SubscriptionAdapter::Async`
|
16
|
+
# adapter, so it could be used in system tests too.
|
16
17
|
class Test < Async
|
17
18
|
def broadcast(channel, payload)
|
18
19
|
broadcasts(channel) << payload
|