actioncable 5.0.0.1 → 5.0.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/README.md +12 -2
- data/lib/action_cable/channel/base.rb +23 -17
- data/lib/action_cable/channel/streams.rb +1 -1
- data/lib/action_cable/connection/base.rb +4 -1
- data/lib/action_cable/connection/stream.rb +58 -2
- data/lib/action_cable/connection/stream_event_loop.rb +39 -8
- data/lib/action_cable/connection/subscriptions.rb +5 -1
- data/lib/action_cable/gem_version.rb +2 -2
- data/lib/action_cable/server/base.rb +6 -2
- data/lib/action_cable/server/configuration.rb +2 -1
- data/lib/action_cable/server/worker.rb +1 -1
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +5 -1
- data/lib/assets/compiled/action_cable.js +566 -559
- data/lib/rails/generators/channel/templates/application_cable/channel.rb +0 -1
- data/lib/rails/generators/channel/templates/application_cable/connection.rb +0 -1
- data/lib/rails/generators/channel/templates/channel.rb +0 -1
- metadata +7 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e71477ad06403305b2d7cb74ae7154a6ca54d8a
|
4
|
+
data.tar.gz: 375e2e323c8c7e987410a5fff1f035d3dba94081
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c6c756bfca7b69592aadc17004a36f993cd86c1de5010d8e98c7a52897823e5a7164b4a9dfdf433127c4b4b41dfb56d5ae6ab9f79ea1831c97d3259aaa12ed7
|
7
|
+
data.tar.gz: 93862ebfe578dc52dc841f47258031ffc58938138631c101de4cb89c0fa09ceaf4eae2c18103d6e694fc86a7e879094f8bfe19598b00c1938ae5c46cf31452ce
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,56 @@
|
|
1
|
+
## Rails 5.0.1.rc1 (December 01, 2016) ##
|
2
|
+
|
3
|
+
* Permit same-origin connections by default.
|
4
|
+
|
5
|
+
New option `config.action_cable.allow_same_origin_as_host = false`
|
6
|
+
to disable.
|
7
|
+
|
8
|
+
*Dávid Halász*, *Matthew Draper*
|
9
|
+
|
10
|
+
* Fixed and added a workaround to avoid race condition, when one
|
11
|
+
thread closed the IO, when an another thread was still trying read
|
12
|
+
from IO on a connection.
|
13
|
+
|
14
|
+
*Matthew Draper*
|
15
|
+
|
16
|
+
* Shutdown pubsub connection before classes are reloaded, to avoid
|
17
|
+
hangups caused by pubsub still holding reference to Active Record
|
18
|
+
connection from the pool, and Active Record trying to cleanup the pool.
|
19
|
+
|
20
|
+
*Jon Moss*
|
21
|
+
|
22
|
+
* Prevent race where the client could receive and act upon a
|
23
|
+
subscription confirmation before the channel's `subscribed` method
|
24
|
+
completed.
|
25
|
+
|
26
|
+
Fixes #25381.
|
27
|
+
|
28
|
+
*Vladimir Dementyev*
|
29
|
+
|
30
|
+
* Buffer writes to websocket connections, to avoid blocking threads
|
31
|
+
that could be doing more useful things.
|
32
|
+
|
33
|
+
*Matthew Draper*, *Tinco Andringa*
|
34
|
+
|
35
|
+
* Invocation of channel action is now prevented, if subscription
|
36
|
+
connection was rejected.
|
37
|
+
|
38
|
+
Fixes #23757.
|
39
|
+
|
40
|
+
*Jon Moss*
|
41
|
+
|
42
|
+
* Protect against concurrent writes to a websocket connection from
|
43
|
+
multiple threads; the underlying OS write is not always threadsafe.
|
44
|
+
|
45
|
+
*Tinco Andringa*
|
46
|
+
|
47
|
+
* Close hijacked socket when connection is shut down.
|
48
|
+
|
49
|
+
Fixes #25613.
|
50
|
+
|
51
|
+
*Tinco Andringa*
|
52
|
+
|
53
|
+
|
1
54
|
## Rails 5.0.0 (June 30, 2016) ##
|
2
55
|
|
3
56
|
* Fix development reloading support: new cable connections are now correctly
|
data/README.md
CHANGED
@@ -323,7 +323,10 @@ Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml"
|
|
323
323
|
|
324
324
|
### Allowed Request Origins
|
325
325
|
|
326
|
-
Action Cable will only accept requests from
|
326
|
+
Action Cable will only accept requests from specific origins.
|
327
|
+
|
328
|
+
By default, only an origin matching the cable server itself will be permitted.
|
329
|
+
Additional origins can be specified using strings or regular expressions, provided in an array.
|
327
330
|
|
328
331
|
```ruby
|
329
332
|
Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
|
@@ -331,12 +334,19 @@ Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonr
|
|
331
334
|
|
332
335
|
When running in the development environment, this defaults to "http://localhost:3000".
|
333
336
|
|
334
|
-
To disable and allow requests from any origin:
|
337
|
+
To disable protection and allow requests from any origin:
|
335
338
|
|
336
339
|
```ruby
|
337
340
|
Rails.application.config.action_cable.disable_request_forgery_protection = true
|
338
341
|
```
|
339
342
|
|
343
|
+
To disable automatic access for same-origin requests, and strictly allow
|
344
|
+
only the configured origins:
|
345
|
+
|
346
|
+
```ruby
|
347
|
+
Rails.application.config.action_cable.allow_same_origin_as_host = false
|
348
|
+
```
|
349
|
+
|
340
350
|
### Consumer Configuration
|
341
351
|
|
342
352
|
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.
|
@@ -144,13 +144,14 @@ module ActionCable
|
|
144
144
|
|
145
145
|
# When a channel is streaming via pubsub, we want to delay the confirmation
|
146
146
|
# transmission until pubsub subscription is confirmed.
|
147
|
-
|
147
|
+
#
|
148
|
+
# The counter starts at 1 because it's awaiting a call to #subscribe_to_channel
|
149
|
+
@defer_subscription_confirmation_counter = Concurrent::AtomicFixnum.new(1)
|
148
150
|
|
149
151
|
@reject_subscription = nil
|
150
152
|
@subscription_confirmation_sent = nil
|
151
153
|
|
152
154
|
delegate_connection_identifiers
|
153
|
-
subscribe_to_channel
|
154
155
|
end
|
155
156
|
|
156
157
|
# Extract the action name from the passed data and process it via the channel. The process will ensure
|
@@ -169,6 +170,17 @@ module ActionCable
|
|
169
170
|
end
|
170
171
|
end
|
171
172
|
|
173
|
+
# This method is called after subscription has been added to the connection
|
174
|
+
# and confirms or rejects the subscription.
|
175
|
+
def subscribe_to_channel
|
176
|
+
run_callbacks :subscribe do
|
177
|
+
subscribed
|
178
|
+
end
|
179
|
+
|
180
|
+
reject_subscription if subscription_rejected?
|
181
|
+
ensure_confirmation_sent
|
182
|
+
end
|
183
|
+
|
172
184
|
# Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks.
|
173
185
|
# This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
|
174
186
|
def unsubscribe_from_channel # :nodoc:
|
@@ -202,12 +214,18 @@ module ActionCable
|
|
202
214
|
end
|
203
215
|
end
|
204
216
|
|
217
|
+
def ensure_confirmation_sent
|
218
|
+
return if subscription_rejected?
|
219
|
+
@defer_subscription_confirmation_counter.decrement
|
220
|
+
transmit_subscription_confirmation unless defer_subscription_confirmation?
|
221
|
+
end
|
222
|
+
|
205
223
|
def defer_subscription_confirmation!
|
206
|
-
@
|
224
|
+
@defer_subscription_confirmation_counter.increment
|
207
225
|
end
|
208
226
|
|
209
227
|
def defer_subscription_confirmation?
|
210
|
-
@
|
228
|
+
@defer_subscription_confirmation_counter.value > 0
|
211
229
|
end
|
212
230
|
|
213
231
|
def subscription_confirmation_sent?
|
@@ -231,24 +249,12 @@ module ActionCable
|
|
231
249
|
end
|
232
250
|
end
|
233
251
|
|
234
|
-
def subscribe_to_channel
|
235
|
-
run_callbacks :subscribe do
|
236
|
-
subscribed
|
237
|
-
end
|
238
|
-
|
239
|
-
if subscription_rejected?
|
240
|
-
reject_subscription
|
241
|
-
else
|
242
|
-
transmit_subscription_confirmation unless defer_subscription_confirmation?
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
252
|
def extract_action(data)
|
247
253
|
(data['action'].presence || :receive).to_sym
|
248
254
|
end
|
249
255
|
|
250
256
|
def processable_action?(action)
|
251
|
-
self.class.action_methods.include?(action.to_s)
|
257
|
+
self.class.action_methods.include?(action.to_s) unless subscription_rejected?
|
252
258
|
end
|
253
259
|
|
254
260
|
def dispatch_action(action, data)
|
@@ -84,7 +84,7 @@ module ActionCable
|
|
84
84
|
|
85
85
|
connection.server.event_loop.post do
|
86
86
|
pubsub.subscribe(broadcasting, handler, lambda do
|
87
|
-
|
87
|
+
ensure_confirmation_sent
|
88
88
|
logger.info "#{self.class.name} is streaming from #{broadcasting}"
|
89
89
|
end)
|
90
90
|
end
|
@@ -195,7 +195,10 @@ module ActionCable
|
|
195
195
|
def allow_request_origin?
|
196
196
|
return true if server.config.disable_request_forgery_protection
|
197
197
|
|
198
|
-
|
198
|
+
proto = Rack::Request.new(env).ssl? ? "https" : "http"
|
199
|
+
if server.config.allow_same_origin_as_host && env["HTTP_ORIGIN"] == "#{proto}://#{env['HTTP_HOST']}"
|
200
|
+
true
|
201
|
+
elsif Array(server.config.allowed_request_origins).any? { |allowed_origin| allowed_origin === env["HTTP_ORIGIN"] }
|
199
202
|
true
|
200
203
|
else
|
201
204
|
logger.error("Request origin not allowed: #{env['HTTP_ORIGIN']}")
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
module ActionCable
|
2
4
|
module Connection
|
3
5
|
#--
|
@@ -11,6 +13,10 @@ module ActionCable
|
|
11
13
|
@stream_send = socket.env['stream.send']
|
12
14
|
|
13
15
|
@rack_hijack_io = nil
|
16
|
+
@write_lock = Mutex.new
|
17
|
+
|
18
|
+
@write_head = nil
|
19
|
+
@write_buffer = Queue.new
|
14
20
|
end
|
15
21
|
|
16
22
|
def each(&callback)
|
@@ -27,12 +33,62 @@ module ActionCable
|
|
27
33
|
end
|
28
34
|
|
29
35
|
def write(data)
|
30
|
-
|
31
|
-
|
36
|
+
if @stream_send
|
37
|
+
return @stream_send.call(data)
|
38
|
+
end
|
39
|
+
|
40
|
+
if @write_lock.try_lock
|
41
|
+
begin
|
42
|
+
if @write_head.nil? && @write_buffer.empty?
|
43
|
+
written = @rack_hijack_io.write_nonblock(data, exception: false)
|
44
|
+
|
45
|
+
case written
|
46
|
+
when :wait_writable
|
47
|
+
# proceed below
|
48
|
+
when data.bytesize
|
49
|
+
return data.bytesize
|
50
|
+
else
|
51
|
+
@write_head = data.byteslice(written, data.bytesize)
|
52
|
+
@event_loop.writes_pending @rack_hijack_io
|
53
|
+
|
54
|
+
return data.bytesize
|
55
|
+
end
|
56
|
+
end
|
57
|
+
ensure
|
58
|
+
@write_lock.unlock
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@write_buffer << data
|
63
|
+
@event_loop.writes_pending @rack_hijack_io
|
64
|
+
|
65
|
+
data.bytesize
|
32
66
|
rescue EOFError, Errno::ECONNRESET
|
33
67
|
@socket_object.client_gone
|
34
68
|
end
|
35
69
|
|
70
|
+
def flush_write_buffer
|
71
|
+
@write_lock.synchronize do
|
72
|
+
loop do
|
73
|
+
if @write_head.nil?
|
74
|
+
return true if @write_buffer.empty?
|
75
|
+
@write_head = @write_buffer.pop
|
76
|
+
end
|
77
|
+
|
78
|
+
written = @rack_hijack_io.write_nonblock(@write_head, exception: false)
|
79
|
+
case written
|
80
|
+
when :wait_writable
|
81
|
+
return false
|
82
|
+
when @write_head.bytesize
|
83
|
+
@write_head = nil
|
84
|
+
else
|
85
|
+
@write_head = @write_head.byteslice(written, @write_head.bytesize)
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
36
92
|
def receive(data)
|
37
93
|
@socket_object.parse(data)
|
38
94
|
end
|
@@ -5,7 +5,7 @@ module ActionCable
|
|
5
5
|
module Connection
|
6
6
|
class StreamEventLoop
|
7
7
|
def initialize
|
8
|
-
@nio = @thread = nil
|
8
|
+
@nio = @executor = @thread = nil
|
9
9
|
@map = {}
|
10
10
|
@stopping = false
|
11
11
|
@todo = Queue.new
|
@@ -20,13 +20,14 @@ module ActionCable
|
|
20
20
|
def post(task = nil, &block)
|
21
21
|
task ||= block
|
22
22
|
|
23
|
-
|
23
|
+
spawn
|
24
|
+
@executor << task
|
24
25
|
end
|
25
26
|
|
26
27
|
def attach(io, stream)
|
27
28
|
@todo << lambda do
|
28
|
-
@map[io] =
|
29
|
-
@
|
29
|
+
@map[io] = @nio.register(io, :r)
|
30
|
+
@map[io].value = stream
|
30
31
|
end
|
31
32
|
wakeup
|
32
33
|
end
|
@@ -35,6 +36,16 @@ module ActionCable
|
|
35
36
|
@todo << lambda do
|
36
37
|
@nio.deregister io
|
37
38
|
@map.delete io
|
39
|
+
io.close
|
40
|
+
end
|
41
|
+
wakeup
|
42
|
+
end
|
43
|
+
|
44
|
+
def writes_pending(io)
|
45
|
+
@todo << lambda do
|
46
|
+
if monitor = @map[io]
|
47
|
+
monitor.interests = :rw
|
48
|
+
end
|
38
49
|
end
|
39
50
|
wakeup
|
40
51
|
end
|
@@ -52,6 +63,13 @@ module ActionCable
|
|
52
63
|
return if @thread && @thread.status
|
53
64
|
|
54
65
|
@nio ||= NIO::Selector.new
|
66
|
+
|
67
|
+
@executor ||= Concurrent::ThreadPoolExecutor.new(
|
68
|
+
min_threads: 1,
|
69
|
+
max_threads: 10,
|
70
|
+
max_queue: 0,
|
71
|
+
)
|
72
|
+
|
55
73
|
@thread = Thread.new { run }
|
56
74
|
|
57
75
|
return true
|
@@ -77,12 +95,25 @@ module ActionCable
|
|
77
95
|
|
78
96
|
monitors.each do |monitor|
|
79
97
|
io = monitor.io
|
80
|
-
stream =
|
98
|
+
stream = monitor.value
|
81
99
|
|
82
100
|
begin
|
83
|
-
|
84
|
-
|
85
|
-
|
101
|
+
if monitor.writable?
|
102
|
+
if stream.flush_write_buffer
|
103
|
+
monitor.interests = :r
|
104
|
+
end
|
105
|
+
next unless monitor.readable?
|
106
|
+
end
|
107
|
+
|
108
|
+
incoming = io.read_nonblock(4096, exception: false)
|
109
|
+
case incoming
|
110
|
+
when :wait_readable
|
111
|
+
next
|
112
|
+
when nil
|
113
|
+
stream.close
|
114
|
+
else
|
115
|
+
stream.receive incoming
|
116
|
+
end
|
86
117
|
rescue
|
87
118
|
# We expect one of EOFError or Errno::ECONNRESET in
|
88
119
|
# normal operation (when the client goes away). But if
|
@@ -26,10 +26,14 @@ module ActionCable
|
|
26
26
|
id_key = data['identifier']
|
27
27
|
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
|
28
28
|
|
29
|
+
return if subscriptions.key?(id_key)
|
30
|
+
|
29
31
|
subscription_klass = id_options[:channel].safe_constantize
|
30
32
|
|
31
33
|
if subscription_klass && ActionCable::Channel::Base >= subscription_klass
|
32
|
-
|
34
|
+
subscription = subscription_klass.new(connection, id_key, id_options)
|
35
|
+
subscriptions[id_key] = subscription
|
36
|
+
subscription.subscribe_to_channel
|
33
37
|
else
|
34
38
|
logger.error "Subscription class not found: #{id_options[:channel].inspect}"
|
35
39
|
end
|
@@ -37,9 +37,13 @@ module ActionCable
|
|
37
37
|
connections.each(&:close)
|
38
38
|
|
39
39
|
@mutex.synchronize do
|
40
|
-
|
41
|
-
|
40
|
+
# Shutdown the worker pool
|
41
|
+
@worker_pool.halt if @worker_pool
|
42
42
|
@worker_pool = nil
|
43
|
+
|
44
|
+
# Shutdown the pub/sub adapter
|
45
|
+
@pubsub.shutdown if @pubsub
|
46
|
+
@pubsub = nil
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
@@ -5,7 +5,7 @@ module ActionCable
|
|
5
5
|
class Configuration
|
6
6
|
attr_accessor :logger, :log_tags
|
7
7
|
attr_accessor :use_faye, :connection_class, :worker_pool_size
|
8
|
-
attr_accessor :disable_request_forgery_protection, :allowed_request_origins
|
8
|
+
attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
|
9
9
|
attr_accessor :cable, :url, :mount_path
|
10
10
|
|
11
11
|
def initialize
|
@@ -15,6 +15,7 @@ module ActionCable
|
|
15
15
|
@worker_pool_size = 4
|
16
16
|
|
17
17
|
@disable_request_forgery_protection = false
|
18
|
+
@allow_same_origin_as_host = true
|
18
19
|
end
|
19
20
|
|
20
21
|
# Returns constant of subscription adapter specified in config/cable.yml.
|