actioncable 5.0.7.2 → 5.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +13 -169
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +18 -15
  5. data/lib/action_cable.rb +9 -9
  6. data/lib/action_cable/channel/base.rb +17 -19
  7. data/lib/action_cable/channel/broadcasting.rb +2 -2
  8. data/lib/action_cable/channel/callbacks.rb +1 -1
  9. data/lib/action_cable/channel/naming.rb +2 -1
  10. data/lib/action_cable/channel/periodic_timers.rb +1 -1
  11. data/lib/action_cable/channel/streams.rb +7 -7
  12. data/lib/action_cable/connection.rb +0 -2
  13. data/lib/action_cable/connection/authorization.rb +6 -6
  14. data/lib/action_cable/connection/base.rb +20 -21
  15. data/lib/action_cable/connection/client_socket.rb +16 -16
  16. data/lib/action_cable/connection/identification.rb +1 -1
  17. data/lib/action_cable/connection/internal_channel.rb +2 -2
  18. data/lib/action_cable/connection/message_buffer.rb +2 -0
  19. data/lib/action_cable/connection/stream.rb +5 -5
  20. data/lib/action_cable/connection/stream_event_loop.rb +2 -2
  21. data/lib/action_cable/connection/subscriptions.rb +12 -10
  22. data/lib/action_cable/connection/tagged_logger_proxy.rb +2 -2
  23. data/lib/action_cable/connection/web_socket.rb +5 -3
  24. data/lib/action_cable/engine.rb +4 -4
  25. data/lib/action_cable/gem_version.rb +3 -3
  26. data/lib/action_cable/helpers/action_cable_helper.rb +1 -1
  27. data/lib/action_cable/remote_connections.rb +2 -2
  28. data/lib/action_cable/server.rb +1 -1
  29. data/lib/action_cable/server/base.rb +5 -5
  30. data/lib/action_cable/server/broadcasting.rb +7 -3
  31. data/lib/action_cable/server/configuration.rb +3 -19
  32. data/lib/action_cable/server/worker.rb +3 -3
  33. data/lib/action_cable/subscription_adapter.rb +1 -0
  34. data/lib/action_cable/subscription_adapter/async.rb +1 -1
  35. data/lib/action_cable/subscription_adapter/channel_prefix.rb +26 -0
  36. data/lib/action_cable/subscription_adapter/evented_redis.rb +13 -5
  37. data/lib/action_cable/subscription_adapter/postgresql.rb +4 -4
  38. data/lib/action_cable/subscription_adapter/redis.rb +9 -7
  39. data/lib/action_cable/subscription_adapter/subscriber_map.rb +1 -1
  40. data/lib/action_cable/version.rb +1 -1
  41. data/lib/assets/compiled/action_cable.js +554 -567
  42. data/lib/rails/generators/channel/USAGE +2 -2
  43. data/lib/rails/generators/channel/channel_generator.rb +9 -9
  44. data/lib/rails/generators/channel/templates/assets/cable.js +1 -1
  45. metadata +13 -33
  46. data/lib/action_cable/connection/faye_client_socket.rb +0 -48
  47. data/lib/action_cable/connection/faye_event_loop.rb +0 -44
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/to_param'
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
@@ -1,4 +1,4 @@
1
- require 'active_support/callbacks'
1
+ require "active_support/callbacks"
2
2
 
3
3
  module ActionCable
4
4
  module Channel
@@ -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$/, '').gsub('::',':').underscore
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, 'Pass a block or provide a callback arg, not both' if callback_or_method_name
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, `comments_for_45` broadcasting as soon as it's put there.
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 `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`
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 `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
73
- # Defaults to `coder: nil` which does no decoding, passes raw messages.
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 `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
98
- # Defaults to `coder: nil` which does no decoding, passes raw messages.
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
  #
@@ -8,8 +8,6 @@ module ActionCable
8
8
  autoload :ClientSocket
9
9
  autoload :Identification
10
10
  autoload :InternalChannel
11
- autoload :FayeClientSocket
12
- autoload :FayeEventLoop
13
11
  autoload :MessageBuffer
14
12
  autoload :Stream
15
13
  autoload :StreamEventLoop
@@ -3,11 +3,11 @@ module ActionCable
3
3
  module Authorization
4
4
  class UnauthorizedError < StandardError; end
5
5
 
6
- # Closes the \WebSocket connection if it is open and returns a 404 "File not Found" response.
7
- def reject_unauthorized_connection
8
- logger.error "An unauthorized connection attempt was rejected"
9
- raise UnauthorizedError
10
- end
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 'action_dispatch'
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
- # protected
25
+ # private
26
26
  # def find_verified_user
27
- # if current_user = User.find_by_identity cookies.signed[:identity_id]
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, server.config.client_socket_class)
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 `identifier`, `started_at`, and `subscriptions`.
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['action_dispatch.request_id']
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, { 'Content-Type' => 'text/plain' }, [ 'Page not found' ] ]
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? ? ' [WebSocket]' : '[non-WebSocket]',
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? ? ' [WebSocket]' : '[non-WebSocket]',
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
- 'Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [
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
- 'Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [
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 'websocket/driver'
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) ? 'wss:' : 'ws:'
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['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'
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 = ['', 1006]
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['async.callback']
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
- when Numeric then @driver.text(message.to_s)
82
- when String then @driver.text(message)
83
- when Array then @driver.binary(message)
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 or (code >= 3000 and code <= 4999)
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
 
@@ -1,4 +1,4 @@
1
- require 'set'
1
+ require "set"
2
2
 
3
3
  module ActionCable
4
4
  module Connection
@@ -27,8 +27,8 @@ module ActionCable
27
27
  end
28
28
 
29
29
  def process_internal_message(message)
30
- case message['type']
31
- when 'disconnect'
30
+ case message["type"]
31
+ when "disconnect"
32
32
  logger.info "Removing connection (#{connection_identifier})"
33
33
  websocket.close
34
34
  end
@@ -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 'thread'
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['stream.send']
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['rack.hijack']
97
+ return unless @socket_object.env["rack.hijack"]
98
98
 
99
- @socket_object.env['rack.hijack'].call
100
- @rack_hijack_io = @socket_object.env['rack.hijack_io']
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,5 +1,5 @@
1
- require 'nio'
2
- require 'thread'
1
+ require "nio"
2
+ require "thread"
3
3
 
4
4
  module ActionCable
5
5
  module Connection
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/hash/indifferent_access'
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['command']
15
- when 'subscribe' then add data
16
- when 'unsubscribe' then remove data
17
- when 'message' then perform_action data
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['identifier']
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['identifier']]
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['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['identifier']]
73
+ if subscription = subscriptions[data["identifier"]]
72
74
  subscription
73
75
  else
74
76
  raise "Unable to find subscription with identifier: #{data['identifier']}"