actioncable 5.0.7.2 → 5.1.0.beta1
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 +13 -169
- data/MIT-LICENSE +1 -1
- data/README.md +18 -15
- data/lib/action_cable.rb +9 -9
- data/lib/action_cable/channel/base.rb +17 -19
- data/lib/action_cable/channel/broadcasting.rb +2 -2
- data/lib/action_cable/channel/callbacks.rb +1 -1
- data/lib/action_cable/channel/naming.rb +2 -1
- data/lib/action_cable/channel/periodic_timers.rb +1 -1
- data/lib/action_cable/channel/streams.rb +7 -7
- data/lib/action_cable/connection.rb +0 -2
- data/lib/action_cable/connection/authorization.rb +6 -6
- data/lib/action_cable/connection/base.rb +20 -21
- data/lib/action_cable/connection/client_socket.rb +16 -16
- data/lib/action_cable/connection/identification.rb +1 -1
- data/lib/action_cable/connection/internal_channel.rb +2 -2
- data/lib/action_cable/connection/message_buffer.rb +2 -0
- data/lib/action_cable/connection/stream.rb +5 -5
- data/lib/action_cable/connection/stream_event_loop.rb +2 -2
- data/lib/action_cable/connection/subscriptions.rb +12 -10
- data/lib/action_cable/connection/tagged_logger_proxy.rb +2 -2
- data/lib/action_cable/connection/web_socket.rb +5 -3
- data/lib/action_cable/engine.rb +4 -4
- data/lib/action_cable/gem_version.rb +3 -3
- data/lib/action_cable/helpers/action_cable_helper.rb +1 -1
- data/lib/action_cable/remote_connections.rb +2 -2
- data/lib/action_cable/server.rb +1 -1
- data/lib/action_cable/server/base.rb +5 -5
- data/lib/action_cable/server/broadcasting.rb +7 -3
- data/lib/action_cable/server/configuration.rb +3 -19
- data/lib/action_cable/server/worker.rb +3 -3
- data/lib/action_cable/subscription_adapter.rb +1 -0
- data/lib/action_cable/subscription_adapter/async.rb +1 -1
- data/lib/action_cable/subscription_adapter/channel_prefix.rb +26 -0
- data/lib/action_cable/subscription_adapter/evented_redis.rb +13 -5
- data/lib/action_cable/subscription_adapter/postgresql.rb +4 -4
- data/lib/action_cable/subscription_adapter/redis.rb +9 -7
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +1 -1
- data/lib/action_cable/version.rb +1 -1
- data/lib/assets/compiled/action_cable.js +554 -567
- data/lib/rails/generators/channel/USAGE +2 -2
- data/lib/rails/generators/channel/channel_generator.rb +9 -9
- data/lib/rails/generators/channel/templates/assets/cable.js +1 -1
- metadata +13 -33
- data/lib/action_cable/connection/faye_client_socket.rb +0 -48
- data/lib/action_cable/connection/faye_event_loop.rb +0 -44
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "active_support/core_ext/object/to_param"
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Channel
|
@@ -16,7 +16,7 @@ module ActionCable
|
|
16
16
|
def broadcasting_for(model) #:nodoc:
|
17
17
|
case
|
18
18
|
when model.is_a?(Array)
|
19
|
-
model.map { |m| broadcasting_for(m) }.join(
|
19
|
+
model.map { |m| broadcasting_for(m) }.join(":")
|
20
20
|
when model.respond_to?(:to_gid_param)
|
21
21
|
model.to_gid_param
|
22
22
|
else
|
@@ -10,8 +10,9 @@ module ActionCable
|
|
10
10
|
#
|
11
11
|
# ChatChannel.channel_name # => 'chat'
|
12
12
|
# Chats::AppearancesChannel.channel_name # => 'chats:appearances'
|
13
|
+
# FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
|
13
14
|
def channel_name
|
14
|
-
@channel_name ||= name.sub(/Channel$/,
|
15
|
+
@channel_name ||= name.sub(/Channel$/, "").gsub("::", ":").underscore
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
@@ -30,7 +30,7 @@ module ActionCable
|
|
30
30
|
def periodically(callback_or_method_name = nil, every:, &block)
|
31
31
|
callback =
|
32
32
|
if block_given?
|
33
|
-
raise ArgumentError,
|
33
|
+
raise ArgumentError, "Pass a block or provide a callback arg, not both" if callback_or_method_name
|
34
34
|
block
|
35
35
|
else
|
36
36
|
case callback_or_method_name
|
@@ -19,14 +19,14 @@ module ActionCable
|
|
19
19
|
# end
|
20
20
|
#
|
21
21
|
# Based on the above example, the subscribers of this channel will get whatever data is put into the,
|
22
|
-
# let's say,
|
22
|
+
# let's say, <tt>comments_for_45</tt> broadcasting as soon as it's put there.
|
23
23
|
#
|
24
24
|
# An example broadcasting for this channel looks like so:
|
25
25
|
#
|
26
26
|
# ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell'
|
27
27
|
#
|
28
28
|
# If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel.
|
29
|
-
# The following example would subscribe to a broadcasting like
|
29
|
+
# The following example would subscribe to a broadcasting like <tt>comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE</tt>.
|
30
30
|
#
|
31
31
|
# class CommentsChannel < ApplicationCable::Channel
|
32
32
|
# def subscribed
|
@@ -69,8 +69,8 @@ module ActionCable
|
|
69
69
|
|
70
70
|
# Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
|
71
71
|
# instead of the default of just transmitting the updates straight to the subscriber.
|
72
|
-
# Pass
|
73
|
-
# Defaults to
|
72
|
+
# Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
|
73
|
+
# Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
|
74
74
|
def stream_from(broadcasting, callback = nil, coder: nil, &block)
|
75
75
|
broadcasting = String(broadcasting)
|
76
76
|
|
@@ -94,8 +94,8 @@ module ActionCable
|
|
94
94
|
# <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
|
95
95
|
# to the subscriber.
|
96
96
|
#
|
97
|
-
# Pass
|
98
|
-
# Defaults to
|
97
|
+
# Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
|
98
|
+
# Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
|
99
99
|
def stream_for(model, callback = nil, coder: nil, &block)
|
100
100
|
stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder)
|
101
101
|
end
|
@@ -138,7 +138,7 @@ module ActionCable
|
|
138
138
|
end
|
139
139
|
|
140
140
|
# May be overridden to change the default stream handling behavior
|
141
|
-
# which decodes JSON and transmits to client.
|
141
|
+
# which decodes JSON and transmits to the client.
|
142
142
|
#
|
143
143
|
# TODO: Tests demonstrating this.
|
144
144
|
#
|
@@ -3,11 +3,11 @@ module ActionCable
|
|
3
3
|
module Authorization
|
4
4
|
class UnauthorizedError < StandardError; end
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
private
|
7
|
+
def reject_unauthorized_connection
|
8
|
+
logger.error "An unauthorized connection attempt was rejected"
|
9
|
+
raise UnauthorizedError
|
10
|
+
end
|
11
11
|
end
|
12
12
|
end
|
13
|
-
end
|
13
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require "action_dispatch"
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Connection
|
5
|
-
# For every WebSocket the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent
|
5
|
+
# For every WebSocket connection the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent
|
6
6
|
# of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
|
7
7
|
# based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond
|
8
8
|
# authentication and authorization.
|
@@ -22,13 +22,10 @@ module ActionCable
|
|
22
22
|
# # Any cleanup work needed when the cable connection is cut.
|
23
23
|
# end
|
24
24
|
#
|
25
|
-
#
|
25
|
+
# private
|
26
26
|
# def find_verified_user
|
27
|
-
#
|
28
|
-
# current_user
|
29
|
-
# else
|
27
|
+
# User.find_by_identity(cookies.signed[:identity_id]) ||
|
30
28
|
# reject_unauthorized_connection
|
31
|
-
# end
|
32
29
|
# end
|
33
30
|
# end
|
34
31
|
# end
|
@@ -57,7 +54,7 @@ module ActionCable
|
|
57
54
|
@worker_pool = server.worker_pool
|
58
55
|
@logger = new_tagged_logger
|
59
56
|
|
60
|
-
@websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop
|
57
|
+
@websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop)
|
61
58
|
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
|
62
59
|
@message_buffer = ActionCable::Connection::MessageBuffer.new(self)
|
63
60
|
|
@@ -105,14 +102,14 @@ module ActionCable
|
|
105
102
|
worker_pool.async_invoke(self, method, *arguments)
|
106
103
|
end
|
107
104
|
|
108
|
-
# Return a basic hash of statistics for the connection keyed with
|
105
|
+
# Return a basic hash of statistics for the connection keyed with <tt>identifier</tt>, <tt>started_at</tt>, <tt>subscriptions</tt>, and <tt>request_id</tt>.
|
109
106
|
# This can be returned by a health check against the connection.
|
110
107
|
def statistics
|
111
108
|
{
|
112
109
|
identifier: connection_identifier,
|
113
110
|
started_at: @started_at,
|
114
111
|
subscriptions: subscriptions.identifiers,
|
115
|
-
request_id: @env[
|
112
|
+
request_id: @env["action_dispatch.request_id"]
|
116
113
|
}
|
117
114
|
end
|
118
115
|
|
@@ -136,9 +133,15 @@ module ActionCable
|
|
136
133
|
send_async :handle_close
|
137
134
|
end
|
138
135
|
|
136
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
137
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
139
138
|
protected
|
139
|
+
attr_reader :websocket
|
140
|
+
attr_reader :message_buffer
|
141
|
+
|
142
|
+
private
|
140
143
|
# The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
|
141
|
-
def request
|
144
|
+
def request # :doc:
|
142
145
|
@request ||= begin
|
143
146
|
environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
|
144
147
|
ActionDispatch::Request.new(environment || env)
|
@@ -146,14 +149,10 @@ module ActionCable
|
|
146
149
|
end
|
147
150
|
|
148
151
|
# The cookies of the request that initiated the WebSocket connection. Useful for performing authorization checks.
|
149
|
-
def cookies
|
152
|
+
def cookies # :doc:
|
150
153
|
request.cookie_jar
|
151
154
|
end
|
152
155
|
|
153
|
-
attr_reader :websocket
|
154
|
-
attr_reader :message_buffer
|
155
|
-
|
156
|
-
private
|
157
156
|
def encode(cable_message)
|
158
157
|
@coder.encode cable_message
|
159
158
|
end
|
@@ -216,7 +215,7 @@ module ActionCable
|
|
216
215
|
|
217
216
|
logger.error invalid_request_message
|
218
217
|
logger.info finished_request_message
|
219
|
-
[ 404, {
|
218
|
+
[ 404, { "Content-Type" => "text/plain" }, [ "Page not found" ] ]
|
220
219
|
end
|
221
220
|
|
222
221
|
# Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags.
|
@@ -229,7 +228,7 @@ module ActionCable
|
|
229
228
|
'Started %s "%s"%s for %s at %s' % [
|
230
229
|
request.request_method,
|
231
230
|
request.filtered_path,
|
232
|
-
websocket.possible? ?
|
231
|
+
websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
|
233
232
|
request.ip,
|
234
233
|
Time.now.to_s ]
|
235
234
|
end
|
@@ -237,19 +236,19 @@ module ActionCable
|
|
237
236
|
def finished_request_message
|
238
237
|
'Finished "%s"%s for %s at %s' % [
|
239
238
|
request.filtered_path,
|
240
|
-
websocket.possible? ?
|
239
|
+
websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
|
241
240
|
request.ip,
|
242
241
|
Time.now.to_s ]
|
243
242
|
end
|
244
243
|
|
245
244
|
def invalid_request_message
|
246
|
-
|
245
|
+
"Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [
|
247
246
|
env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
|
248
247
|
]
|
249
248
|
end
|
250
249
|
|
251
250
|
def successful_request_message
|
252
|
-
|
251
|
+
"Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [
|
253
252
|
env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
|
254
253
|
]
|
255
254
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "websocket/driver"
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Connection
|
@@ -8,16 +8,16 @@ module ActionCable
|
|
8
8
|
# Copyright (c) 2010-2015 James Coglan
|
9
9
|
class ClientSocket # :nodoc:
|
10
10
|
def self.determine_url(env)
|
11
|
-
scheme = secure_request?(env) ?
|
11
|
+
scheme = secure_request?(env) ? "wss:" : "ws:"
|
12
12
|
"#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.secure_request?(env)
|
16
|
-
return true if env[
|
17
|
-
return true if env[
|
18
|
-
return true if env[
|
19
|
-
return true if env[
|
20
|
-
return true if env[
|
16
|
+
return true if env["HTTPS"] == "on"
|
17
|
+
return true if env["HTTP_X_FORWARDED_SSL"] == "on"
|
18
|
+
return true if env["HTTP_X_FORWARDED_SCHEME"] == "https"
|
19
|
+
return true if env["HTTP_X_FORWARDED_PROTO"] == "https"
|
20
|
+
return true if env["rack.url_scheme"] == "https"
|
21
21
|
|
22
22
|
return false
|
23
23
|
end
|
@@ -37,7 +37,7 @@ module ActionCable
|
|
37
37
|
@url = ClientSocket.determine_url(@env)
|
38
38
|
|
39
39
|
@driver = @driver_started = nil
|
40
|
-
@close_params = [
|
40
|
+
@close_params = ["", 1006]
|
41
41
|
|
42
42
|
@ready_state = CONNECTING
|
43
43
|
|
@@ -56,7 +56,7 @@ module ActionCable
|
|
56
56
|
return if @driver.nil? || @driver_started
|
57
57
|
@stream.hijack_rack_socket
|
58
58
|
|
59
|
-
if callback = @env[
|
59
|
+
if callback = @env["async.callback"]
|
60
60
|
callback.call([101, {}, @stream])
|
61
61
|
end
|
62
62
|
|
@@ -78,20 +78,20 @@ module ActionCable
|
|
78
78
|
def transmit(message)
|
79
79
|
return false if @ready_state > OPEN
|
80
80
|
case message
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
when Numeric then @driver.text(message.to_s)
|
82
|
+
when String then @driver.text(message)
|
83
|
+
when Array then @driver.binary(message)
|
84
84
|
else false
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
88
|
def close(code = nil, reason = nil)
|
89
89
|
code ||= 1000
|
90
|
-
reason ||=
|
90
|
+
reason ||= ""
|
91
91
|
|
92
|
-
unless code == 1000
|
93
|
-
raise ArgumentError, "Failed to execute 'close' on WebSocket: "
|
94
|
-
"The code must be either 1000, or between 3000 and 4999. "
|
92
|
+
unless code == 1000 || (code >= 3000 && code <= 4999)
|
93
|
+
raise ArgumentError, "Failed to execute 'close' on WebSocket: " \
|
94
|
+
"The code must be either 1000, or between 3000 and 4999. " \
|
95
95
|
"#{code} is neither."
|
96
96
|
end
|
97
97
|
|
@@ -28,6 +28,8 @@ module ActionCable
|
|
28
28
|
receive_buffered_messages
|
29
29
|
end
|
30
30
|
|
31
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
32
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
31
33
|
protected
|
32
34
|
attr_reader :connection
|
33
35
|
attr_reader :buffered_messages
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "thread"
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Connection
|
@@ -10,7 +10,7 @@ module ActionCable
|
|
10
10
|
def initialize(event_loop, socket)
|
11
11
|
@event_loop = event_loop
|
12
12
|
@socket_object = socket
|
13
|
-
@stream_send = socket.env[
|
13
|
+
@stream_send = socket.env["stream.send"]
|
14
14
|
|
15
15
|
@rack_hijack_io = nil
|
16
16
|
@write_lock = Mutex.new
|
@@ -94,10 +94,10 @@ module ActionCable
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def hijack_rack_socket
|
97
|
-
return unless @socket_object.env[
|
97
|
+
return unless @socket_object.env["rack.hijack"]
|
98
98
|
|
99
|
-
@socket_object.env[
|
100
|
-
@rack_hijack_io = @socket_object.env[
|
99
|
+
@socket_object.env["rack.hijack"].call
|
100
|
+
@rack_hijack_io = @socket_object.env["rack.hijack_io"]
|
101
101
|
|
102
102
|
@event_loop.attach(@rack_hijack_io, self)
|
103
103
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "active_support/core_ext/hash/indifferent_access"
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Connection
|
@@ -11,19 +11,19 @@ module ActionCable
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def execute_command(data)
|
14
|
-
case data[
|
15
|
-
when
|
16
|
-
when
|
17
|
-
when
|
14
|
+
case data["command"]
|
15
|
+
when "subscribe" then add data
|
16
|
+
when "unsubscribe" then remove data
|
17
|
+
when "message" then perform_action data
|
18
18
|
else
|
19
19
|
logger.error "Received unrecognized command in #{data.inspect}"
|
20
20
|
end
|
21
21
|
rescue Exception => e
|
22
|
-
logger.error "Could not execute command from #{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
|
22
|
+
logger.error "Could not execute command from (#{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
|
23
23
|
end
|
24
24
|
|
25
25
|
def add(data)
|
26
|
-
id_key = data[
|
26
|
+
id_key = data["identifier"]
|
27
27
|
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
|
28
28
|
|
29
29
|
return if subscriptions.key?(id_key)
|
@@ -41,7 +41,7 @@ module ActionCable
|
|
41
41
|
|
42
42
|
def remove(data)
|
43
43
|
logger.info "Unsubscribing from channel: #{data['identifier']}"
|
44
|
-
remove_subscription subscriptions[data[
|
44
|
+
remove_subscription subscriptions[data["identifier"]]
|
45
45
|
end
|
46
46
|
|
47
47
|
def remove_subscription(subscription)
|
@@ -50,7 +50,7 @@ module ActionCable
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def perform_action(data)
|
53
|
-
find(data).perform_action ActiveSupport::JSON.decode(data[
|
53
|
+
find(data).perform_action ActiveSupport::JSON.decode(data["data"])
|
54
54
|
end
|
55
55
|
|
56
56
|
def identifiers
|
@@ -61,6 +61,8 @@ module ActionCable
|
|
61
61
|
subscriptions.each { |id, channel| remove_subscription(channel) }
|
62
62
|
end
|
63
63
|
|
64
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
65
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
64
66
|
protected
|
65
67
|
attr_reader :connection, :subscriptions
|
66
68
|
|
@@ -68,7 +70,7 @@ module ActionCable
|
|
68
70
|
delegate :logger, to: :connection
|
69
71
|
|
70
72
|
def find(data)
|
71
|
-
if subscription = subscriptions[data[
|
73
|
+
if subscription = subscriptions[data["identifier"]]
|
72
74
|
subscription
|
73
75
|
else
|
74
76
|
raise "Unable to find subscription with identifier: #{data['identifier']}"
|