actioncable 5.0.0.beta1.1 → 5.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -3
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +60 -44
  5. data/lib/action_cable.rb +2 -1
  6. data/lib/action_cable/channel/base.rb +2 -2
  7. data/lib/action_cable/channel/periodic_timers.rb +3 -3
  8. data/lib/action_cable/channel/streams.rb +4 -4
  9. data/lib/action_cable/connection.rb +4 -1
  10. data/lib/action_cable/connection/base.rb +22 -21
  11. data/lib/action_cable/connection/client_socket.rb +150 -0
  12. data/lib/action_cable/connection/identification.rb +1 -1
  13. data/lib/action_cable/connection/internal_channel.rb +6 -6
  14. data/lib/action_cable/connection/stream.rb +59 -0
  15. data/lib/action_cable/connection/stream_event_loop.rb +94 -0
  16. data/lib/action_cable/connection/subscriptions.rb +0 -1
  17. data/lib/action_cable/connection/web_socket.rb +14 -8
  18. data/lib/action_cable/engine.rb +3 -3
  19. data/lib/action_cable/gem_version.rb +1 -1
  20. data/lib/action_cable/remote_connections.rb +1 -1
  21. data/lib/action_cable/server.rb +0 -4
  22. data/lib/action_cable/server/base.rb +19 -22
  23. data/lib/action_cable/server/broadcasting.rb +1 -8
  24. data/lib/action_cable/server/configuration.rb +25 -5
  25. data/lib/action_cable/server/connections.rb +3 -5
  26. data/lib/action_cable/server/worker.rb +42 -13
  27. data/lib/action_cable/subscription_adapter.rb +8 -0
  28. data/lib/action_cable/subscription_adapter/async.rb +22 -0
  29. data/lib/action_cable/subscription_adapter/base.rb +28 -0
  30. data/lib/action_cable/subscription_adapter/evented_redis.rb +67 -0
  31. data/lib/action_cable/subscription_adapter/inline.rb +35 -0
  32. data/lib/action_cable/subscription_adapter/postgresql.rb +106 -0
  33. data/lib/action_cable/subscription_adapter/redis.rb +163 -0
  34. data/lib/action_cable/subscription_adapter/subscriber_map.rb +53 -0
  35. data/lib/assets/compiled/action_cable.js +473 -0
  36. data/lib/rails/generators/channel/channel_generator.rb +6 -1
  37. metadata +21 -99
  38. data/lib/action_cable/process/logging.rb +0 -10
  39. data/lib/assets/javascripts/action_cable.coffee.erb +0 -23
  40. data/lib/assets/javascripts/action_cable/connection.coffee +0 -84
  41. data/lib/assets/javascripts/action_cable/connection_monitor.coffee +0 -84
  42. data/lib/assets/javascripts/action_cable/consumer.coffee +0 -31
  43. data/lib/assets/javascripts/action_cable/subscription.coffee +0 -68
  44. data/lib/assets/javascripts/action_cable/subscriptions.coffee +0 -78
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1aad5170db2b6b99a079c3e32f144cfdd03f6f76
4
- data.tar.gz: bf24e304789dc7b982eb8c5dcc126dc0c90db47f
3
+ metadata.gz: 666a8feb5df8f5f6a523f81a826c1a2fbd23a1c3
4
+ data.tar.gz: 85b3d45b716f55eed7b2b8aad82e69dd7f00c1e7
5
5
  SHA512:
6
- metadata.gz: 096e4a23394e14f19d9a874f2cc34e8dc675b333fa38db1ab1db019c912532218ec051bac8e30e5d255496c14f1fb4dffe26a949c7ce066f90eb84b9103f15eb
7
- data.tar.gz: b8961ebcd43134b02d0d4e0f5a1a5df8d4f8b7f27abc153360255a5cada9ecbc601c57c714d84fd9e29d85bb2f17d44f8f75ddfc389ace460ce16e8babcfac25
6
+ metadata.gz: be0531d119c3792067b0cc54609830700ea1eacb28dd6199775e13b3914a83a90cdbc6e44d10d434a14dabc159e7dc2d7df0b984e3a3e748b4e8ec42ea554def
7
+ data.tar.gz: 75e01690a3a351949df277f14492b79f050518a9c8f5e5a72a68df0f83595df635b9bfedea7f53e9882e0d0bac302fed69c5f79ce535ae765d22da607dc65fce
data/CHANGELOG.md CHANGED
@@ -1,8 +1,28 @@
1
- ## Rails 5.0.0.beta1 (December 18, 2015) ##
1
+ ## Rails 5.0.0.beta2 (February 01, 2016) ##
2
+
3
+ * Support PostgreSQL pubsub adapter.
4
+
5
+ *Jon Moss*
6
+
7
+ * Remove EventMachine dependency.
8
+
9
+ *Matthew Draper*
2
10
 
3
- * No changes.
11
+ * Remove Celluloid dependency.
4
12
 
13
+ *Mike Perham*
14
+
15
+ * Create notion of an `ActionCable::SubscriptionAdapter`.
16
+ Separate out Redis functionality into
17
+ `ActionCable::SubscriptionAdapter::Redis`, and add a
18
+ PostgreSQL adapter as well. Configuration file for
19
+ ActionCable was changed from`config/redis/cable.yml` to
20
+ `config/cable.yml`.
21
+
22
+ *Jon Moss*
23
+
24
+ ## Rails 5.0.0.beta1 (December 18, 2015) ##
5
25
 
6
26
  * Added to Rails!
7
27
 
8
- *DHH*
28
+ *DHH*
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015 Basecamp, LLC
1
+ Copyright (c) 2015-2016 Basecamp, LLC
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Action Cable –- Integrated WebSockets for Rails
1
+ # Action Cable Integrated WebSockets for Rails
2
2
 
3
3
  Action Cable seamlessly integrates WebSockets with the rest of your Rails application.
4
4
  It allows for real-time features to be written in Ruby in the same style
@@ -16,7 +16,7 @@ WebSockets open to your application if they use multiple browser tabs or devices
16
16
  The client of a WebSocket connection is called the consumer.
17
17
 
18
18
  Each consumer can in turn subscribe to multiple cable channels. Each channel encapsulates
19
- a logical unit of work, similar to what a controller does in a regular MVC setup. For example,
19
+ a logical unit of work, similar to what a controller does in a regular MVC setup. For example,
20
20
  you could have a `ChatChannel` and a `AppearancesChannel`, and a consumer could be subscribed to either
21
21
  or to both of these channels. At the very least, a consumer should be subscribed to one channel.
22
22
 
@@ -66,6 +66,13 @@ end
66
66
  Here `identified_by` is a connection identifier that can be used to find the specific connection again or later.
67
67
  Note that anything marked as an identifier will automatically create a delegate by the same name on any channel instances created off the connection.
68
68
 
69
+ This relies on the fact that you will already have handled authentication of the user, and
70
+ that a successful authentication sets a signed cookie with the `user_id`. This cookie is then
71
+ automatically sent to the connection instance when a new connection is attempted, and you
72
+ use that to set the `current_user`. By identifying the connection by this same current_user,
73
+ you're also ensuring that you can later retrieve all open connections by a given user (and
74
+ potentially disconnect them all if the user is deleted or deauthorized).
75
+
69
76
  Then you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put
70
77
  shared logic between your channels.
71
78
 
@@ -77,13 +84,6 @@ module ApplicationCable
77
84
  end
78
85
  ```
79
86
 
80
- This relies on the fact that you will already have handled authentication of the user, and
81
- that a successful authentication sets a signed cookie with the `user_id`. This cookie is then
82
- automatically sent to the connection instance when a new connection is attempted, and you
83
- use that to set the `current_user`. By identifying the connection by this same current_user,
84
- you're also ensuring that you can later retrieve all open connections by a given user (and
85
- potentially disconnect them all if the user is deleted or deauthorized).
86
-
87
87
  The client-side needs to setup a consumer instance of this connection. That's done like so:
88
88
 
89
89
  ```coffeescript
@@ -91,7 +91,7 @@ The client-side needs to setup a consumer instance of this connection. That's do
91
91
  #= require action_cable
92
92
 
93
93
  @App = {}
94
- App.cable = Cable.createConsumer("ws://cable.example.com")
94
+ App.cable = ActionCable.createConsumer("ws://cable.example.com")
95
95
  ```
96
96
 
97
97
  The ws://cable.example.com address must point to your set of Action Cable servers, and it
@@ -298,11 +298,12 @@ See the [rails/actioncable-examples](http://github.com/rails/actioncable-example
298
298
 
299
299
  ## Configuration
300
300
 
301
- Action Cable has two required configurations: the Redis connection and specifying allowed request origins.
301
+ Action Cable has three required configurations: the Redis connection, allowed request origins, and the cable server url (which can optionally be set on the client side).
302
302
 
303
303
  ### Redis
304
304
 
305
- By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/redis/cable.yml')`. The file must follow the following format:
305
+ By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/cable.yml')`.
306
+ This file must specify a Redis url for each Rails environment. It may use the following format:
306
307
 
307
308
  ```yaml
308
309
  production: &production
@@ -312,11 +313,10 @@ development: &development
312
313
  test: *development
313
314
  ```
314
315
 
315
- This format allows you to specify one configuration per Rails environment. You can also change the location of the Redis config file in
316
- a Rails initializer with something like:
316
+ You can also change the location of the Redis config file in a Rails initializer with something like:
317
317
 
318
318
  ```ruby
319
- Rails.application.paths.add "config/redis/cable", with: "somewhere/else/cable.yml"
319
+ Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml"
320
320
  ```
321
321
 
322
322
  ### Allowed Request Origins
@@ -327,29 +327,31 @@ Action Cable will only accept requests from specified origins, which are passed
327
327
  ActionCable.server.config.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
328
328
  ```
329
329
 
330
+ When running in the development environment, this defaults to "http://localhost:3000".
331
+
330
332
  To disable and allow requests from any origin:
331
333
 
332
334
  ```ruby
333
335
  ActionCable.server.config.disable_request_forgery_protection = true
334
336
  ```
335
337
 
336
- By default, Action Cable allows all requests from localhost:3000 when running in the development environment.
338
+ ### Consumer Configuration
337
339
 
338
- ### Other Configurations
340
+ Once you have decided how to run your cable server (see below), you must provide the server url (or path) to your client-side setup.
341
+ There are two ways you can do this.
339
342
 
340
- The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
343
+ The first is to simply pass it in when creating your consumer. For a standalone server,
344
+ this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server,
345
+ something like: `App.cable = ActionCable.createConsumer("/cable")`.
341
346
 
342
- ```ruby
343
- ActionCable.server.config.log_tags = [
344
- -> request { request.env['bc.account_id'] || "no-account" },
345
- :action_cable,
346
- -> request { request.uuid }
347
- ]
348
- ```
347
+ The second option is to pass the server url through the `action_cable_meta_tag` in your layout.
348
+ This uses a url or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
349
349
 
350
- Your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
350
+ This method is especially useful if your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
351
351
  for your ActionCable server, but development might remain http and use the ws scheme. You might use localhost in development and your
352
- domain in production. In any case, to vary the websocket url between environments, add the following configuration to each environment:
352
+ domain in production.
353
+
354
+ In any case, to vary the websocket url between environments, add the following configuration to each environment:
353
355
 
354
356
  ```ruby
355
357
  config.action_cable.url = "ws://example.com:28080"
@@ -364,7 +366,19 @@ Then add the following line to your layout before your JavaScript tag:
364
366
  And finally, create your consumer like so:
365
367
 
366
368
  ```coffeescript
367
- App.cable = Cable.createConsumer()
369
+ App.cable = ActionCable.createConsumer()
370
+ ```
371
+
372
+ ### Other Configurations
373
+
374
+ The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
375
+
376
+ ```ruby
377
+ ActionCable.server.config.log_tags = [
378
+ -> request { request.env['bc.account_id'] || "no-account" },
379
+ :action_cable,
380
+ -> request { request.uuid }
381
+ ]
368
382
  ```
369
383
 
370
384
  For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.
@@ -383,8 +397,6 @@ application. The recommended basic setup is as follows:
383
397
  require ::File.expand_path('../../config/environment', __FILE__)
384
398
  Rails.application.eager_load!
385
399
 
386
- require 'action_cable/process/logging'
387
-
388
400
  run ActionCable.server
389
401
  ```
390
402
 
@@ -394,22 +406,19 @@ Then you start the server using a binstub in bin/cable ala:
394
406
  bundle exec puma -p 28080 cable/config.ru
395
407
  ```
396
408
 
397
- The above will start a cable server on port 28080. Remember to point your client-side setup against that using something like:
398
- `App.cable = Cable.createConsumer("ws://basecamp.dev:28080")`.
409
+ The above will start a cable server on port 28080.
399
410
 
400
411
  ### In app
401
412
 
402
- If you are using a threaded server like Puma or Thin, the current implementation of ActionCable can run side-along with your Rails application. For example, to listen for WebSocket requests on `/websocket`, match requests on that path:
413
+ If you are using a threaded server like Puma or Thin, the current implementation of ActionCable can run side-along with your Rails application. For example, to listen for WebSocket requests on `/cable`, mount the server at that path:
403
414
 
404
415
  ```ruby
405
416
  # config/routes.rb
406
417
  Example::Application.routes.draw do
407
- match "/websocket", :to => ActionCable.server, via: [:get, :post]
418
+ mount ActionCable.server => '/cable'
408
419
  end
409
420
  ```
410
421
 
411
- You can use `App.cable = Cable.createConsumer("/websocket")` to connect to the cable server.
412
-
413
422
  For every instance of your server you create and for every worker your server spawns, you will also have a new instance of ActionCable, but the use of Redis keeps messages synced across connections.
414
423
 
415
424
  ### Notes
@@ -427,16 +436,15 @@ messages back and forth over the WebSocket cable connection. This dependency may
427
436
  be alleviated in the future, but for the moment that's what it is. So be sure to have
428
437
  Redis installed and running.
429
438
 
430
- The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [celluloid](https://github.com/celluloid/celluloid).
439
+ The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
431
440
 
432
441
 
433
442
  ## Deployment
434
443
 
435
- Action Cable is powered by a combination of EventMachine and threads. The
436
- framework plumbing needed for connection handling is handled in the
437
- EventMachine loop, but the actual channel, user-specified, work is handled
438
- in a normal Ruby thread. This means you can use all your regular Rails models
439
- with no problem, as long as you haven't committed any thread-safety sins.
444
+ Action Cable is powered by a combination of websockets and threads. All of the
445
+ connection management is handled internally by utilizing Ruby’s native thread
446
+ support, which means you can use all your regular Rails models with no problems
447
+ as long as you haven’t committed any thread-safety sins.
440
448
 
441
449
  But this also means that Action Cable needs to run in its own server process.
442
450
  So you'll have one set of server processes for your normal web work, and another
@@ -452,6 +460,14 @@ Action Cable is released under the MIT license:
452
460
 
453
461
  ## Support
454
462
 
455
- Bug reports can be filed for the alpha development project here:
463
+ API documentation is at:
464
+
465
+ * http://api.rubyonrails.org
466
+
467
+ Bug reports can be filed for the Ruby on Rails project here:
468
+
469
+ * https://github.com/rails/rails/issues
470
+
471
+ Feature requests should be discussed on the rails-core mailing list here:
456
472
 
457
- * https://github.com/rails/actioncable/issues
473
+ * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
data/lib/action_cable.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2015 Basecamp, LLC
2
+ # Copyright (c) 2015-2016 Basecamp, LLC
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -47,4 +47,5 @@ module ActionCable
47
47
  autoload :Connection
48
48
  autoload :Channel
49
49
  autoload :RemoteConnections
50
+ autoload :SubscriptionAdapter
50
51
  end
@@ -133,8 +133,8 @@ module ActionCable
133
133
  @identifier = identifier
134
134
  @params = params
135
135
 
136
- # When a channel is streaming via redis pubsub, we want to delay the confirmation
137
- # transmission until redis pubsub subscription is confirmed.
136
+ # When a channel is streaming via pubsub, we want to delay the confirmation
137
+ # transmission until pubsub subscription is confirmed.
138
138
  @defer_subscription_confirmation = false
139
139
 
140
140
  @reject_subscription = nil
@@ -27,14 +27,14 @@ module ActionCable
27
27
 
28
28
  def start_periodic_timers
29
29
  self.class.periodic_timers.each do |callback, options|
30
- active_periodic_timers << EventMachine::PeriodicTimer.new(options[:every]) do
31
- connection.worker_pool.async.run_periodic_timer(self, callback)
30
+ active_periodic_timers << Concurrent::TimerTask.new(execution_interval: options[:every]) do
31
+ connection.worker_pool.async_run_periodic_timer(self, callback)
32
32
  end
33
33
  end
34
34
  end
35
35
 
36
36
  def stop_periodic_timers
37
- active_periodic_timers.each { |timer| timer.cancel }
37
+ active_periodic_timers.each { |timer| timer.shutdown }
38
38
  end
39
39
  end
40
40
  end
@@ -75,11 +75,11 @@ module ActionCable
75
75
  callback ||= default_stream_callback(broadcasting)
76
76
  streams << [ broadcasting, callback ]
77
77
 
78
- EM.next_tick do
79
- pubsub.subscribe(broadcasting, &callback).callback do |reply|
78
+ Concurrent.global_io_executor.post do
79
+ pubsub.subscribe(broadcasting, callback, lambda do
80
80
  transmit_subscription_confirmation
81
81
  logger.info "#{self.class.name} is streaming from #{broadcasting}"
82
- end
82
+ end)
83
83
  end
84
84
  end
85
85
 
@@ -92,7 +92,7 @@ module ActionCable
92
92
 
93
93
  def stop_all_streams
94
94
  streams.each do |broadcasting, callback|
95
- pubsub.unsubscribe_proc broadcasting, callback
95
+ pubsub.unsubscribe broadcasting, callback
96
96
  logger.info "#{self.class.name} stopped streaming from #{broadcasting}"
97
97
  end.clear
98
98
  end
@@ -5,12 +5,15 @@ module ActionCable
5
5
  eager_autoload do
6
6
  autoload :Authorization
7
7
  autoload :Base
8
+ autoload :ClientSocket
8
9
  autoload :Identification
9
10
  autoload :InternalChannel
10
11
  autoload :MessageBuffer
11
- autoload :WebSocket
12
+ autoload :Stream
13
+ autoload :StreamEventLoop
12
14
  autoload :Subscriptions
13
15
  autoload :TaggedLoggerProxy
16
+ autoload :WebSocket
14
17
  end
15
18
  end
16
19
  end
@@ -48,21 +48,19 @@ module ActionCable
48
48
  include InternalChannel
49
49
  include Authorization
50
50
 
51
- attr_reader :server, :env, :subscriptions
52
- delegate :worker_pool, :pubsub, to: :server
53
-
54
- attr_reader :logger
51
+ attr_reader :server, :env, :subscriptions, :logger
52
+ delegate :stream_event_loop, :worker_pool, :pubsub, to: :server
55
53
 
56
54
  def initialize(server, env)
57
55
  @server, @env = server, env
58
56
 
59
57
  @logger = new_tagged_logger
60
58
 
61
- @websocket = ActionCable::Connection::WebSocket.new(env)
59
+ @websocket = ActionCable::Connection::WebSocket.new(env, self, stream_event_loop)
62
60
  @subscriptions = ActionCable::Connection::Subscriptions.new(self)
63
61
  @message_buffer = ActionCable::Connection::MessageBuffer.new(self)
64
62
 
65
- @_internal_redis_subscriptions = nil
63
+ @_internal_subscriptions = nil
66
64
  @started_at = Time.now
67
65
  end
68
66
 
@@ -72,10 +70,6 @@ module ActionCable
72
70
  logger.info started_request_message
73
71
 
74
72
  if websocket.possible? && allow_request_origin?
75
- websocket.on(:open) { |event| send_async :on_open }
76
- websocket.on(:message) { |event| on_message event.data }
77
- websocket.on(:close) { |event| send_async :on_close }
78
-
79
73
  respond_to_successful_request
80
74
  else
81
75
  respond_to_invalid_request
@@ -105,7 +99,7 @@ module ActionCable
105
99
 
106
100
  # Invoke a method on the connection asynchronously through the pool of thread workers.
107
101
  def send_async(method, *arguments)
108
- worker_pool.async.invoke(self, method, *arguments)
102
+ worker_pool.async_invoke(self, method, *arguments)
109
103
  end
110
104
 
111
105
  # Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`.
@@ -123,6 +117,21 @@ module ActionCable
123
117
  transmit ActiveSupport::JSON.encode(identifier: ActionCable::INTERNAL[:identifiers][:ping], message: Time.now.to_i)
124
118
  end
125
119
 
120
+ def on_open # :nodoc:
121
+ send_async :handle_open
122
+ end
123
+
124
+ def on_message(message) # :nodoc:
125
+ message_buffer.append message
126
+ end
127
+
128
+ def on_error(message) # :nodoc:
129
+ # ignore
130
+ end
131
+
132
+ def on_close(reason, code) # :nodoc:
133
+ send_async :handle_close
134
+ end
126
135
 
127
136
  protected
128
137
  # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
@@ -138,13 +147,11 @@ module ActionCable
138
147
  request.cookie_jar
139
148
  end
140
149
 
141
-
142
- protected
143
150
  attr_reader :websocket
144
151
  attr_reader :message_buffer
145
152
 
146
153
  private
147
- def on_open
154
+ def handle_open
148
155
  connect if respond_to?(:connect)
149
156
  subscribe_to_internal_channel
150
157
  beat
@@ -155,11 +162,7 @@ module ActionCable
155
162
  respond_to_invalid_request
156
163
  end
157
164
 
158
- def on_message(message)
159
- message_buffer.append message
160
- end
161
-
162
- def on_close
165
+ def handle_close
163
166
  logger.info finished_request_message
164
167
 
165
168
  server.remove_connection(self)
@@ -170,7 +173,6 @@ module ActionCable
170
173
  disconnect if respond_to?(:disconnect)
171
174
  end
172
175
 
173
-
174
176
  def allow_request_origin?
175
177
  return true if server.config.disable_request_forgery_protection
176
178
 
@@ -193,7 +195,6 @@ module ActionCable
193
195
  [ 404, { 'Content-Type' => 'text/plain' }, [ 'Page not found' ] ]
194
196
  end
195
197
 
196
-
197
198
  # Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags.
198
199
  def new_tagged_logger
199
200
  TaggedLoggerProxy.new server.logger,