actioncable 5.0.0.beta3 → 5.0.0.beta4
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 +52 -6
- data/README.md +11 -15
- data/lib/action_cable.rb +5 -4
- data/lib/action_cable/channel/base.rb +19 -6
- data/lib/action_cable/channel/periodic_timers.rb +45 -7
- data/lib/action_cable/channel/streams.rb +70 -14
- data/lib/action_cable/connection.rb +2 -0
- data/lib/action_cable/connection/base.rb +33 -21
- data/lib/action_cable/connection/client_socket.rb +17 -9
- data/lib/action_cable/connection/faye_client_socket.rb +48 -0
- data/lib/action_cable/connection/faye_event_loop.rb +44 -0
- data/lib/action_cable/connection/internal_channel.rb +3 -5
- data/lib/action_cable/connection/message_buffer.rb +2 -2
- data/lib/action_cable/connection/stream.rb +9 -11
- data/lib/action_cable/connection/stream_event_loop.rb +10 -1
- data/lib/action_cable/connection/web_socket.rb +6 -2
- data/lib/action_cable/engine.rb +37 -1
- data/lib/action_cable/gem_version.rb +1 -1
- data/lib/action_cable/helpers/action_cable_helper.rb +19 -8
- data/lib/action_cable/remote_connections.rb +1 -1
- data/lib/action_cable/server/base.rb +26 -6
- data/lib/action_cable/server/broadcasting.rb +10 -9
- data/lib/action_cable/server/configuration.rb +19 -3
- data/lib/action_cable/server/connections.rb +3 -3
- data/lib/action_cable/server/worker.rb +27 -27
- data/lib/action_cable/server/worker/active_record_connection_management.rb +0 -3
- data/lib/action_cable/subscription_adapter/async.rb +8 -3
- data/lib/action_cable/subscription_adapter/evented_redis.rb +5 -1
- data/lib/action_cable/subscription_adapter/postgresql.rb +5 -4
- data/lib/action_cable/subscription_adapter/redis.rb +11 -6
- data/lib/assets/compiled/action_cable.js +248 -188
- data/lib/rails/generators/channel/USAGE +1 -1
- data/lib/rails/generators/channel/channel_generator.rb +4 -1
- data/lib/rails/generators/channel/templates/assets/cable.js +13 -0
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5d4383eac68ebad915b6ba22ef556a6dbcdabf5
|
4
|
+
data.tar.gz: 04c95deb8c0ae1e283b0fd630172646f86d4e541
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b37dfd802d1c0f95b97e5afe0832a0cf9143b77cc17615ed6a402dead98856e4c759a503c301a1a5359a95116fcbfe812dfb18655b6476b6548b7b182d988f4
|
7
|
+
data.tar.gz: 2657c42b971d5dc208906c98a3545aae2111c6775e6478561bb91071f854e83bd6c8b9eb7a8080d0c5d17bd67ae95e2385ac5b97d5ef063d917825bb0898c4a6
|
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,57 @@
|
|
1
|
+
## Rails 5.0.0.beta4 (April 27, 2016) ##
|
2
|
+
|
3
|
+
* WebSocket protocol negotiation.
|
4
|
+
|
5
|
+
Introduces an Action Cable protocol version that moves independently
|
6
|
+
of and, hopefully, more slowly than Action Cable itself. Client sockets
|
7
|
+
negotiate a protocol with the Cable server using WebSockets' native
|
8
|
+
subprotocol support:
|
9
|
+
* https://tools.ietf.org/html/rfc6455#section-1.9
|
10
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Subprotocols
|
11
|
+
|
12
|
+
If they can't negotiate a compatible protocol (usually due to upgrading
|
13
|
+
the Cable server with a browser still running old JavaScript) then the
|
14
|
+
client knows to disconnect, cease retrying, and tell the app that it hit
|
15
|
+
a protocol mismatch.
|
16
|
+
|
17
|
+
This allows us to evolve the Action Cable message format, handshaking,
|
18
|
+
pings, acknowledgements, and more without breaking older clients'
|
19
|
+
expectations of server behavior.
|
20
|
+
|
21
|
+
*Daniel Rhodes*
|
22
|
+
|
23
|
+
* Pubsub: automatic stream decoding.
|
24
|
+
|
25
|
+
stream_for @room, coder: ActiveSupport::JSON do |message|
|
26
|
+
# `message` is a Ruby hash here instead of a JSON string
|
27
|
+
|
28
|
+
The `coder` must respond to `#decode`. Defaults to `coder: nil`
|
29
|
+
which skips decoding entirely.
|
30
|
+
|
31
|
+
*Jeremy Daer*
|
32
|
+
|
33
|
+
* Add ActiveSupport::Notifications to ActionCable::Channel.
|
34
|
+
|
35
|
+
*Matthew Wear*
|
36
|
+
|
37
|
+
* Safely support autoloading and class unloading, by preventing concurrent
|
38
|
+
loads, and disconnecting all cables during reload.
|
39
|
+
|
40
|
+
*Matthew Draper*
|
41
|
+
|
42
|
+
* Ensure ActionCable behaves correctly for non-string queue names.
|
43
|
+
|
44
|
+
*Jay Hayes*
|
45
|
+
|
1
46
|
## Rails 5.0.0.beta3 (February 24, 2016) ##
|
2
47
|
|
3
|
-
*
|
4
|
-
ActionCable::SubscriptionAdapter::
|
5
|
-
|
6
|
-
|
48
|
+
* Added `em_redis_connector` and `redis_connector` to
|
49
|
+
`ActionCable::SubscriptionAdapter::EventedRedis` and added `redis_connector`
|
50
|
+
to `ActionCable::SubscriptionAdapter::Redis`, so you can overwrite with your
|
51
|
+
own initializers. This is used when you want to use different-than-standard
|
52
|
+
Redis adapters, like for Makara distributed Redis.
|
7
53
|
|
8
|
-
|
54
|
+
*DHH*
|
9
55
|
|
10
56
|
## Rails 5.0.0.beta2 (February 01, 2016) ##
|
11
57
|
|
@@ -28,7 +74,7 @@
|
|
28
74
|
ActionCable was changed from`config/redis/cable.yml` to
|
29
75
|
`config/cable.yml`.
|
30
76
|
|
31
|
-
|
77
|
+
*Jon Moss*
|
32
78
|
|
33
79
|
## Rails 5.0.0.beta1 (December 18, 2015) ##
|
34
80
|
|
data/README.md
CHANGED
@@ -178,7 +178,7 @@ App.cable.subscriptions.create "AppearanceChannel",
|
|
178
178
|
```
|
179
179
|
|
180
180
|
Simply calling `App.cable.subscriptions.create` will setup the subscription, which will call `AppearanceChannel#subscribed`,
|
181
|
-
which in turn is linked to original `App.cable` -> `ApplicationCable::Connection` instances.
|
181
|
+
which in turn is linked to the original `App.cable` -> `ApplicationCable::Connection` instances.
|
182
182
|
|
183
183
|
Next, we link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side
|
184
184
|
channel instance will automatically expose the public methods declared on the class (minus the callbacks), so that these
|
@@ -339,21 +339,21 @@ Rails.application.config.action_cable.disable_request_forgery_protection = true
|
|
339
339
|
|
340
340
|
### Consumer Configuration
|
341
341
|
|
342
|
-
Once you have decided how to run your cable server (see below), you must provide the server
|
342
|
+
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.
|
343
343
|
There are two ways you can do this.
|
344
344
|
|
345
345
|
The first is to simply pass it in when creating your consumer. For a standalone server,
|
346
346
|
this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server,
|
347
347
|
something like: `App.cable = ActionCable.createConsumer("/cable")`.
|
348
348
|
|
349
|
-
The second option is to pass the server
|
350
|
-
This uses a
|
349
|
+
The second option is to pass the server URL through the `action_cable_meta_tag` in your layout.
|
350
|
+
This uses a URL or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
|
351
351
|
|
352
|
-
This method is especially useful if your WebSocket
|
352
|
+
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
|
353
353
|
for your Action Cable server, but development might remain http and use the ws scheme. You might use localhost in development and your
|
354
354
|
domain in production.
|
355
355
|
|
356
|
-
In any case, to vary the WebSocket
|
356
|
+
In any case, to vary the WebSocket URL between environments, add the following configuration to each environment:
|
357
357
|
|
358
358
|
```ruby
|
359
359
|
config.action_cable.url = "ws://example.com:28080"
|
@@ -412,12 +412,12 @@ The above will start a cable server on port 28080.
|
|
412
412
|
|
413
413
|
### In app
|
414
414
|
|
415
|
-
If you are using a
|
415
|
+
If you are using a server that supports the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/websocket`, specify that path to `config.action_cable.mount_path`:
|
416
416
|
|
417
417
|
```ruby
|
418
|
-
# config/
|
419
|
-
|
420
|
-
|
418
|
+
# config/application.rb
|
419
|
+
class Application < Rails::Application
|
420
|
+
config.action_cable.mount_path = '/websocket'
|
421
421
|
end
|
422
422
|
```
|
423
423
|
|
@@ -445,12 +445,8 @@ connection management is handled internally by utilizing Ruby’s native thread
|
|
445
445
|
support, which means you can use all your regular Rails models with no problems
|
446
446
|
as long as you haven’t committed any thread-safety sins.
|
447
447
|
|
448
|
-
But this also means that Action Cable needs to run in its own server process.
|
449
|
-
So you'll have one set of server processes for your normal web work, and another
|
450
|
-
set of server processes for the Action Cable.
|
451
|
-
|
452
448
|
The Action Cable server does _not_ need to be a multi-threaded application server.
|
453
|
-
This is because Action Cable uses the [Rack socket hijacking API](http://
|
449
|
+
This is because Action Cable uses the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking)
|
454
450
|
to take over control of connections from the application server. Action Cable
|
455
451
|
then manages connections internally, in a multithreaded manner, regardless of
|
456
452
|
whether the application server is multi-threaded or not. So Action Cable works
|
data/lib/action_cable.rb
CHANGED
@@ -29,13 +29,14 @@ module ActionCable
|
|
29
29
|
extend ActiveSupport::Autoload
|
30
30
|
|
31
31
|
INTERNAL = {
|
32
|
-
identifiers: {
|
33
|
-
ping: '_ping'.freeze
|
34
|
-
},
|
35
32
|
message_types: {
|
33
|
+
welcome: 'welcome'.freeze,
|
34
|
+
ping: 'ping'.freeze,
|
36
35
|
confirmation: 'confirm_subscription'.freeze,
|
37
36
|
rejection: 'reject_subscription'.freeze
|
38
|
-
}
|
37
|
+
},
|
38
|
+
default_mount_path: '/cable'.freeze,
|
39
|
+
protocols: ["actioncable-v1-json".freeze, "actioncable-unsupported".freeze].freeze
|
39
40
|
}
|
40
41
|
|
41
42
|
# Singleton instance of the server
|
@@ -160,13 +160,16 @@ module ActionCable
|
|
160
160
|
action = extract_action(data)
|
161
161
|
|
162
162
|
if processable_action?(action)
|
163
|
-
|
163
|
+
payload = { channel_class: self.class.name, action: action, data: data }
|
164
|
+
ActiveSupport::Notifications.instrument("perform_action.action_cable", payload) do
|
165
|
+
dispatch_action(action, data)
|
166
|
+
end
|
164
167
|
else
|
165
168
|
logger.error "Unable to process #{action_signature(action, data)}"
|
166
169
|
end
|
167
170
|
end
|
168
171
|
|
169
|
-
# Called by the cable connection when
|
172
|
+
# Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks.
|
170
173
|
# This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
|
171
174
|
def unsubscribe_from_channel # :nodoc:
|
172
175
|
run_callbacks :unsubscribe do
|
@@ -192,7 +195,11 @@ module ActionCable
|
|
192
195
|
# the proper channel identifier marked as the recipient.
|
193
196
|
def transmit(data, via: nil)
|
194
197
|
logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via }
|
195
|
-
|
198
|
+
|
199
|
+
payload = { channel_class: self.class.name, data: data, via: via }
|
200
|
+
ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do
|
201
|
+
connection.transmit identifier: @identifier, message: data
|
202
|
+
end
|
196
203
|
end
|
197
204
|
|
198
205
|
def defer_subscription_confirmation!
|
@@ -265,8 +272,11 @@ module ActionCable
|
|
265
272
|
def transmit_subscription_confirmation
|
266
273
|
unless subscription_confirmation_sent?
|
267
274
|
logger.info "#{self.class.name} is transmitting the subscription confirmation"
|
268
|
-
|
269
|
-
|
275
|
+
|
276
|
+
ActiveSupport::Notifications.instrument("transmit_subscription_confirmation.action_cable", channel_class: self.class.name) do
|
277
|
+
connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation]
|
278
|
+
@subscription_confirmation_sent = true
|
279
|
+
end
|
270
280
|
end
|
271
281
|
end
|
272
282
|
|
@@ -277,7 +287,10 @@ module ActionCable
|
|
277
287
|
|
278
288
|
def transmit_subscription_rejection
|
279
289
|
logger.info "#{self.class.name} is transmitting the subscription rejection"
|
280
|
-
|
290
|
+
|
291
|
+
ActiveSupport::Notifications.instrument("transmit_subscription_rejection.action_cable", channel_class: self.class.name) do
|
292
|
+
connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection]
|
293
|
+
end
|
281
294
|
end
|
282
295
|
end
|
283
296
|
end
|
@@ -12,11 +12,42 @@ module ActionCable
|
|
12
12
|
end
|
13
13
|
|
14
14
|
module ClassMethods
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
15
|
+
# Periodically performs a task on the channel, like updating an online
|
16
|
+
# user counter, polling a backend for new status messages, sending
|
17
|
+
# regular "heartbeat" messages, or doing some internal work and giving
|
18
|
+
# progress updates.
|
19
|
+
#
|
20
|
+
# Pass a method name or lambda argument or provide a block to call.
|
21
|
+
# Specify the calling period in seconds using the <tt>every:</tt>
|
22
|
+
# keyword argument.
|
23
|
+
#
|
24
|
+
# periodically :transmit_progress, every: 5.seconds
|
25
|
+
#
|
26
|
+
# periodically every: 3.minutes do
|
27
|
+
# transmit action: :update_count, count: current_count
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
def periodically(callback_or_method_name = nil, every:, &block)
|
31
|
+
callback =
|
32
|
+
if block_given?
|
33
|
+
raise ArgumentError, 'Pass a block or provide a callback arg, not both' if callback_or_method_name
|
34
|
+
block
|
35
|
+
else
|
36
|
+
case callback_or_method_name
|
37
|
+
when Proc
|
38
|
+
callback_or_method_name
|
39
|
+
when Symbol
|
40
|
+
-> { __send__ callback_or_method_name }
|
41
|
+
else
|
42
|
+
raise ArgumentError, "Expected a Symbol method name or a Proc, got #{callback_or_method_name.inspect}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
unless every.kind_of?(Numeric) && every > 0
|
47
|
+
raise ArgumentError, "Expected every: to be a positive number of seconds, got #{every.inspect}"
|
48
|
+
end
|
49
|
+
|
50
|
+
self.periodic_timers += [[ callback, every: every ]]
|
20
51
|
end
|
21
52
|
end
|
22
53
|
|
@@ -27,14 +58,21 @@ module ActionCable
|
|
27
58
|
|
28
59
|
def start_periodic_timers
|
29
60
|
self.class.periodic_timers.each do |callback, options|
|
30
|
-
active_periodic_timers <<
|
31
|
-
|
61
|
+
active_periodic_timers << start_periodic_timer(callback, every: options.fetch(:every))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def start_periodic_timer(callback, every:)
|
66
|
+
connection.server.event_loop.timer every do
|
67
|
+
connection.worker_pool.async_invoke connection do
|
68
|
+
instance_exec(&callback)
|
32
69
|
end
|
33
70
|
end
|
34
71
|
end
|
35
72
|
|
36
73
|
def stop_periodic_timers
|
37
74
|
active_periodic_timers.each { |timer| timer.shutdown }
|
75
|
+
active_periodic_timers.clear
|
38
76
|
end
|
39
77
|
end
|
40
78
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module ActionCable
|
2
2
|
module Channel
|
3
|
-
# Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a
|
3
|
+
# Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data
|
4
4
|
# placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
|
5
|
-
# streaming a broadcasting at the very moment it sends out an update, you will not get that update, if you connect after it has been sent.
|
5
|
+
# streaming a broadcasting at the very moment it sends out an update, you will not get that update, even if you connect after it has been sent.
|
6
6
|
#
|
7
7
|
# Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between
|
8
8
|
# the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new
|
@@ -46,9 +46,7 @@ module ActionCable
|
|
46
46
|
# def subscribed
|
47
47
|
# @room = Chat::Room[params[:room_number]]
|
48
48
|
#
|
49
|
-
# stream_for @room,
|
50
|
-
# message = ActiveSupport::JSON.decode(encoded_message)
|
51
|
-
#
|
49
|
+
# stream_for @room, coder: ActiveSupport::JSON do |message|
|
52
50
|
# if message['originated_at'].present?
|
53
51
|
# elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
|
54
52
|
#
|
@@ -71,15 +69,21 @@ module ActionCable
|
|
71
69
|
|
72
70
|
# Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
|
73
71
|
# instead of the default of just transmitting the updates straight to the subscriber.
|
74
|
-
|
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.
|
74
|
+
def stream_from(broadcasting, callback = nil, coder: nil, &block)
|
75
|
+
broadcasting = String(broadcasting)
|
76
|
+
|
75
77
|
# Don't send the confirmation until pubsub#subscribe is successful
|
76
78
|
defer_subscription_confirmation!
|
77
79
|
|
78
|
-
callback
|
79
|
-
|
80
|
+
# Build a stream handler by wrapping the user-provided callback with
|
81
|
+
# a decoder or defaulting to a JSON-decoding retransmitter.
|
82
|
+
handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder)
|
83
|
+
streams << [ broadcasting, handler ]
|
80
84
|
|
81
|
-
|
82
|
-
pubsub.subscribe(broadcasting,
|
85
|
+
connection.server.event_loop.post do
|
86
|
+
pubsub.subscribe(broadcasting, handler, lambda do
|
83
87
|
transmit_subscription_confirmation
|
84
88
|
logger.info "#{self.class.name} is streaming from #{broadcasting}"
|
85
89
|
end)
|
@@ -89,8 +93,11 @@ module ActionCable
|
|
89
93
|
# Start streaming the pubsub queue for the <tt>model</tt> in this channel. Optionally, you can pass a
|
90
94
|
# <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
|
91
95
|
# to the subscriber.
|
92
|
-
|
93
|
-
|
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.
|
99
|
+
def stream_for(model, callback = nil, coder: nil, &block)
|
100
|
+
stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder)
|
94
101
|
end
|
95
102
|
|
96
103
|
# Unsubscribes all streams associated with this channel from the pubsub queue.
|
@@ -108,11 +115,60 @@ module ActionCable
|
|
108
115
|
@_streams ||= []
|
109
116
|
end
|
110
117
|
|
111
|
-
|
118
|
+
# Always wrap the outermost handler to invoke the user handler on the
|
119
|
+
# worker pool rather than blocking the event loop.
|
120
|
+
def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
|
121
|
+
handler = stream_handler(broadcasting, user_handler, coder: coder)
|
122
|
+
|
123
|
+
-> message do
|
124
|
+
connection.worker_pool.async_invoke handler, :call, message, connection: connection
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# May be overridden to add instrumentation, logging, specialized error
|
129
|
+
# handling, or other forms of handler decoration.
|
130
|
+
#
|
131
|
+
# TODO: Tests demonstrating this.
|
132
|
+
def stream_handler(broadcasting, user_handler, coder: nil)
|
133
|
+
if user_handler
|
134
|
+
stream_decoder user_handler, coder: coder
|
135
|
+
else
|
136
|
+
default_stream_handler broadcasting, coder: coder
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# May be overridden to change the default stream handling behavior
|
141
|
+
# which decodes JSON and transmits to client.
|
142
|
+
#
|
143
|
+
# TODO: Tests demonstrating this.
|
144
|
+
#
|
145
|
+
# TODO: Room for optimization. Update transmit API to be coder-aware
|
146
|
+
# so we can no-op when pubsub and connection are both JSON-encoded.
|
147
|
+
# Then we can skip decode+encode if we're just proxying messages.
|
148
|
+
def default_stream_handler(broadcasting, coder:)
|
149
|
+
coder ||= ActiveSupport::JSON
|
150
|
+
stream_transmitter stream_decoder(coder: coder), broadcasting: broadcasting
|
151
|
+
end
|
152
|
+
|
153
|
+
def stream_decoder(handler = identity_handler, coder:)
|
154
|
+
if coder
|
155
|
+
-> message { handler.(coder.decode(message)) }
|
156
|
+
else
|
157
|
+
handler
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def stream_transmitter(handler = identity_handler, broadcasting:)
|
162
|
+
via = "streamed from #{broadcasting}"
|
163
|
+
|
112
164
|
-> (message) do
|
113
|
-
transmit
|
165
|
+
transmit handler.(message), via: via
|
114
166
|
end
|
115
167
|
end
|
168
|
+
|
169
|
+
def identity_handler
|
170
|
+
-> message { message }
|
171
|
+
end
|
116
172
|
end
|
117
173
|
end
|
118
174
|
end
|
@@ -40,7 +40,7 @@ module ActionCable
|
|
40
40
|
# Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes
|
41
41
|
# it easy to use signed cookies that were set when logging in via a web interface to authorize the WebSocket connection.
|
42
42
|
#
|
43
|
-
# Finally, we add a tag to the connection-specific logger with name of the current user to easily distinguish their messages in the log.
|
43
|
+
# Finally, we add a tag to the connection-specific logger with the name of the current user to easily distinguish their messages in the log.
|
44
44
|
#
|
45
45
|
# Pretty simple, eh?
|
46
46
|
class Base
|
@@ -48,15 +48,16 @@ module ActionCable
|
|
48
48
|
include InternalChannel
|
49
49
|
include Authorization
|
50
50
|
|
51
|
-
attr_reader :server, :env, :subscriptions, :logger
|
52
|
-
delegate :
|
51
|
+
attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol
|
52
|
+
delegate :event_loop, :pubsub, to: :server
|
53
53
|
|
54
|
-
def initialize(server, env)
|
55
|
-
@server, @env = server, env
|
54
|
+
def initialize(server, env, coder: ActiveSupport::JSON)
|
55
|
+
@server, @env, @coder = server, env, coder
|
56
56
|
|
57
|
+
@worker_pool = server.worker_pool
|
57
58
|
@logger = new_tagged_logger
|
58
59
|
|
59
|
-
@websocket = ActionCable::Connection::WebSocket.new(env, self,
|
60
|
+
@websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop, server.config.client_socket_class)
|
60
61
|
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
|
61
62
|
@message_buffer = ActionCable::Connection::MessageBuffer.new(self)
|
62
63
|
|
@@ -66,7 +67,7 @@ module ActionCable
|
|
66
67
|
|
67
68
|
# Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user.
|
68
69
|
# This method should not be called directly -- instead rely upon on the #connect (and #disconnect) callbacks.
|
69
|
-
def process
|
70
|
+
def process #:nodoc:
|
70
71
|
logger.info started_request_message
|
71
72
|
|
72
73
|
if websocket.possible? && allow_request_origin?
|
@@ -76,20 +77,22 @@ module ActionCable
|
|
76
77
|
end
|
77
78
|
end
|
78
79
|
|
79
|
-
#
|
80
|
-
#
|
81
|
-
def receive(
|
80
|
+
# Decodes WebSocket messages and dispatches them to subscribed channels.
|
81
|
+
# WebSocket message transfer encoding is always JSON.
|
82
|
+
def receive(websocket_message) #:nodoc:
|
83
|
+
send_async :dispatch_websocket_message, websocket_message
|
84
|
+
end
|
85
|
+
|
86
|
+
def dispatch_websocket_message(websocket_message) #:nodoc:
|
82
87
|
if websocket.alive?
|
83
|
-
subscriptions.execute_command
|
88
|
+
subscriptions.execute_command decode(websocket_message)
|
84
89
|
else
|
85
|
-
logger.error "
|
90
|
+
logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})"
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
89
|
-
|
90
|
-
|
91
|
-
def transmit(data) # :nodoc:
|
92
|
-
websocket.transmit data
|
94
|
+
def transmit(cable_message) # :nodoc:
|
95
|
+
websocket.transmit encode(cable_message)
|
93
96
|
end
|
94
97
|
|
95
98
|
# Close the WebSocket connection.
|
@@ -114,7 +117,7 @@ module ActionCable
|
|
114
117
|
end
|
115
118
|
|
116
119
|
def beat
|
117
|
-
transmit
|
120
|
+
transmit type: ActionCable::INTERNAL[:message_types][:ping], message: Time.now.to_i
|
118
121
|
end
|
119
122
|
|
120
123
|
def on_open # :nodoc:
|
@@ -151,10 +154,19 @@ module ActionCable
|
|
151
154
|
attr_reader :message_buffer
|
152
155
|
|
153
156
|
private
|
157
|
+
def encode(cable_message)
|
158
|
+
@coder.encode cable_message
|
159
|
+
end
|
160
|
+
|
161
|
+
def decode(websocket_message)
|
162
|
+
@coder.decode websocket_message
|
163
|
+
end
|
164
|
+
|
154
165
|
def handle_open
|
166
|
+
@protocol = websocket.protocol
|
155
167
|
connect if respond_to?(:connect)
|
156
168
|
subscribe_to_internal_channel
|
157
|
-
|
169
|
+
send_welcome_message
|
158
170
|
|
159
171
|
message_buffer.process!
|
160
172
|
server.add_connection(self)
|
@@ -173,11 +185,11 @@ module ActionCable
|
|
173
185
|
disconnect if respond_to?(:disconnect)
|
174
186
|
end
|
175
187
|
|
176
|
-
def
|
177
|
-
# Send
|
188
|
+
def send_welcome_message
|
189
|
+
# Send welcome message to the internal connection monitor channel.
|
178
190
|
# This ensures the connection monitor state is reset after a successful
|
179
191
|
# websocket connection.
|
180
|
-
transmit
|
192
|
+
transmit type: ActionCable::INTERNAL[:message_types][:welcome]
|
181
193
|
end
|
182
194
|
|
183
195
|
def allow_request_origin?
|