actioncable 5.0.0.beta3 → 5.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
- 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?
|