aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml13
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock93
-rw-r--r--README.md18
-rw-r--r--Rakefile1
-rw-r--r--actioncable.gemspec8
-rw-r--r--gemfiles/rails_42.gemfile5
-rw-r--r--lib/action_cable.rb33
-rw-r--r--lib/action_cable/channel.rb16
-rw-r--r--lib/action_cable/channel/base.rb8
-rw-r--r--lib/action_cable/connection.rb20
-rw-r--r--lib/action_cable/connection/base.rb34
-rw-r--r--lib/action_cable/connection/identification.rb12
-rw-r--r--lib/action_cable/connection/internal_channel.rb4
-rw-r--r--lib/action_cable/connection/message_buffer.rb4
-rw-r--r--lib/action_cable/connection/subscriptions.rb2
-rw-r--r--lib/action_cable/connection/web_socket.rb2
-rw-r--r--lib/action_cable/engine.rb18
-rw-r--r--lib/action_cable/server.rb20
-rw-r--r--lib/action_cable/server/base.rb4
-rw-r--r--lib/action_cable/server/broadcasting.rb6
-rw-r--r--lib/action_cable/server/configuration.rb2
-rw-r--r--lib/action_cable/server/connections.rb6
-rw-r--r--lib/action_cable/server/worker.rb5
-rw-r--r--test/channel/base_test.rb5
-rw-r--r--test/channel/stream_test.rb23
-rw-r--r--test/connection/authorization_test.rb20
-rw-r--r--test/connection/base_test.rb110
-rw-r--r--test/connection/cross_site_forgery_test.rb17
-rw-r--r--test/connection/identifier_test.rb70
-rw-r--r--test/connection/string_identifier_test.rb23
-rw-r--r--test/connection/subscriptions_test.rb85
-rw-r--r--test/stubs/test_server.rb3
-rw-r--r--test/test_helper.rb25
-rw-r--r--test/worker_test.rb3
36 files changed, 472 insertions, 255 deletions
diff --git a/.gitignore b/.gitignore
index 1918a1b0ee..cb2bc5e743 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-test/tests.log \ No newline at end of file
+/gemfiles/*.lock
+/test/tests.log
diff --git a/.travis.yml b/.travis.yml
index 99a95ae240..5e156e2b77 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,19 @@
-sudo: false
+language: ruby
cache: bundler
+sudo: false
+
rvm:
- 2.2
- ruby-head
+
+gemfile:
+ - Gemfile
+ - gemfiles/rails_4-2-stable.gemfile
+
matrix:
fast_finish: true
+ allow_failures: ruby-head
+
notifications:
email: false
irc:
@@ -16,4 +25,4 @@ notifications:
on_success: change
on_failure: always
rooms:
- - secure: "EZmqsgjEQbWouCx6xL/30jslug7xcq+Dl09twDGjBs369GB5LiUm17/I7d6H1YQFY0Vu2LpiQ/zs+6ihlBjslRV/2RYM3AgAA9OOC3pn7uENFVTXaECi/io1wjvlbMNrf1YJSc3aUyiWKykRsdZnZSFszkDs4DMnZG1s/Oxf1JTYEGNWW3WcOFfYkzcS7NWlOW9OBf4RuzjtLYF05IO4t4FZI1aTWrNV3NNMZ+tqmiQHHNrQE/CzQE3ujqFiea2vVZ7PwvmjVWJgC29UZqS7HcNuq6cCMtMZZuubCZmyT85GjJ/SKTShxFqfV1oCpY3y6kyWcTAQsUoLtPEX0OxLeX+CgWNIJK0rY5+5/v5pZP1uwRsMfLerfp2a9g4fAnlcAKaZjalOc39rOkJl8FdvLQtqFIGWxpjWdJbMrCt3SrnnOccpDqDWpAL798LVBONcOuor71rEeNj1dZ6fCoHTKhLVy6UVm9eUI8zt1APM0xzHgTBI1KBVZi0ikqPcaW604rrNUSk8g/AFQk0pIKyDzV9qYMJD2wnr42cyPKg0gfk1tc9KRCNeH+My1HdZS6Zogpjkc3plAzJQ1DAPY0EBWUlEKghpkyCunjpxN3cw390iKgZUN52phtmGMRkyNnwI8+ELnT4I+Jata1mFyWiETM85q8Rqx+FeA0W/BBsEAp8=" \ No newline at end of file
+ - secure: "EZmqsgjEQbWouCx6xL/30jslug7xcq+Dl09twDGjBs369GB5LiUm17/I7d6H1YQFY0Vu2LpiQ/zs+6ihlBjslRV/2RYM3AgAA9OOC3pn7uENFVTXaECi/io1wjvlbMNrf1YJSc3aUyiWKykRsdZnZSFszkDs4DMnZG1s/Oxf1JTYEGNWW3WcOFfYkzcS7NWlOW9OBf4RuzjtLYF05IO4t4FZI1aTWrNV3NNMZ+tqmiQHHNrQE/CzQE3ujqFiea2vVZ7PwvmjVWJgC29UZqS7HcNuq6cCMtMZZuubCZmyT85GjJ/SKTShxFqfV1oCpY3y6kyWcTAQsUoLtPEX0OxLeX+CgWNIJK0rY5+5/v5pZP1uwRsMfLerfp2a9g4fAnlcAKaZjalOc39rOkJl8FdvLQtqFIGWxpjWdJbMrCt3SrnnOccpDqDWpAL798LVBONcOuor71rEeNj1dZ6fCoHTKhLVy6UVm9eUI8zt1APM0xzHgTBI1KBVZi0ikqPcaW604rrNUSk8g/AFQk0pIKyDzV9qYMJD2wnr42cyPKg0gfk1tc9KRCNeH+My1HdZS6Zogpjkc3plAzJQ1DAPY0EBWUlEKghpkyCunjpxN3cw390iKgZUN52phtmGMRkyNnwI8+ELnT4I+Jata1mFyWiETM85q8Rqx+FeA0W/BBsEAp8="
diff --git a/Gemfile b/Gemfile
index 851fabc21d..d2eaf07c80 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,2 +1,8 @@
source 'https://rubygems.org'
+
+gem 'activesupport', github: 'rails/rails'
+gem 'actionpack', github: 'rails/rails'
+gem 'arel', github: 'rails/arel'
+gem 'rack', github: 'rack/rack'
+
gemspec
diff --git a/Gemfile.lock b/Gemfile.lock
index 5548531abe..7f128bbdd1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,3 +1,47 @@
+GIT
+ remote: git://github.com/rack/rack.git
+ revision: 6216a3f8a3560639ee1ddadc1e0d6bf9e5f31830
+ specs:
+ rack (2.0.0.alpha)
+ json
+
+GIT
+ remote: git://github.com/rails/arel.git
+ revision: 3c429c5d86e9e2201c2a35d934ca6a8911c18e69
+ specs:
+ arel (7.0.0.alpha)
+
+GIT
+ remote: git://github.com/rails/rails.git
+ revision: 960de47f0eef79d234eb3cfc47fabb470fef1529
+ specs:
+ actionpack (5.0.0.alpha)
+ actionview (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ rack (~> 2.x)
+ rack-test (~> 0.6.3)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ builder (~> 3.1)
+ erubis (~> 2.7.0)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ activesupport (5.0.0.alpha)
+ concurrent-ruby (~> 1.0.0.pre3, < 2.0.0)
+ i18n (~> 0.7)
+ json (~> 1.7, >= 1.7.7)
+ method_source
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
+ railties (5.0.0.alpha)
+ actionpack (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ method_source
+ rake (>= 0.8.7)
+ thor (>= 0.18.1, < 2.0)
+
PATH
remote: .
specs:
@@ -14,27 +58,8 @@ PATH
GEM
remote: https://rubygems.org/
specs:
- actionpack (4.2.3)
- actionview (= 4.2.3)
- activesupport (= 4.2.3)
- rack (~> 1.6)
- rack-test (~> 0.6.2)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.3)
- activesupport (= 4.2.3)
- builder (~> 3.1)
- erubis (~> 2.7.0)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activesupport (4.2.3)
- i18n (~> 0.7)
- json (~> 1.7, >= 1.7.7)
- minitest (~> 5.1)
- thread_safe (~> 0.3, >= 0.3.4)
- tzinfo (~> 1.1)
builder (3.2.2)
- celluloid (0.16.0)
+ celluloid (0.16.1)
timers (~> 4.0.0)
coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
@@ -43,50 +68,46 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.9.1.1)
+ concurrent-ruby (1.0.0.pre4)
em-hiredis (0.3.0)
eventmachine (~> 1.0)
hiredis (~> 0.5.0)
erubis (2.7.0)
- eventmachine (1.0.7)
- execjs (2.5.2)
+ eventmachine (1.0.8)
+ execjs (2.6.0)
faye-websocket (0.10.0)
eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1)
hiredis (0.5.2)
- hitimes (1.2.2)
+ hitimes (1.2.3)
i18n (0.7.0)
json (1.8.3)
- loofah (2.0.2)
+ loofah (2.0.3)
nokogiri (>= 1.5.9)
metaclass (0.0.4)
+ method_source (0.8.2)
mini_portile (0.6.2)
- minitest (5.7.0)
+ minitest (5.8.1)
mocha (1.1.0)
metaclass (~> 0.0.1)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
puma (2.12.2)
- rack (1.6.4)
rack-test (0.6.3)
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.6)
+ rails-dom-testing (1.0.7)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.2)
loofah (~> 2.0)
- railties (4.2.3)
- actionpack (= 4.2.3)
- activesupport (= 4.2.3)
- rake (>= 0.8.7)
- thor (>= 0.18.1, < 2.0)
rake (10.4.2)
redis (3.2.1)
thor (0.19.1)
thread_safe (0.3.5)
- timers (4.0.1)
+ timers (4.0.4)
hitimes
tzinfo (1.2.2)
thread_safe (~> 0.1)
@@ -99,9 +120,13 @@ PLATFORMS
DEPENDENCIES
actioncable!
+ actionpack!
+ activesupport!
+ arel!
mocha
puma
+ rack!
rake
BUNDLED WITH
- 1.10.5
+ 1.10.6
diff --git a/README.md b/README.md
index aa3d6101df..798ca5292f 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-# Action Cable – Integrated websockets for Rails
+# Action Cable – Integrated WebSockets for Rails
[![Build Status](https://travis-ci.org/rails/actioncable.svg)](https://travis-ci.org/rails/actioncable)
-Action Cable seamlessly integrates websockets with the rest of your Rails application.
+Action Cable seamlessly integrates WebSockets with the rest of your Rails application.
It allows for real-time features to be written in Ruby in the same style
and form as the rest of your Rails application, while still being performant
and scalable. It's a full-stack offering that provides both a client-side
@@ -12,9 +12,9 @@ domain model written with ActiveRecord or your ORM of choice.
## Terminology
A single Action Cable server can handle multiple connection instances. It has one
-connection instance per websocket connection. A single user may have multiple
-websockets open to your application if they use multiple browser tabs or devices.
-The client of a websocket connection is called the consumer.
+connection instance per WebSocket connection. A single user may have multiple
+WebSockets open to your application if they use multiple browser tabs or devices.
+The client of a WebSocket connection is called the consumer.
Each consumer can in turn subscribe to multiple cable channels. Each channel encapsulates
a logical unit of work, similar to what a controller does in a regular MVC setup. For example, you could have a `ChatChannel` and a `AppearancesChannel`, and a consumer could be subscribed to either
@@ -169,8 +169,8 @@ Turbolinks `page:change` callback and allowing the user to click a data-behavior
### Channel example 2: Receiving new web notifications
-The appearance example was all about exposing server functionality to client-side invocation over the websocket connection.
-But the great thing about websockets is that it's a two-way street. So now let's show an example where the server invokes
+The appearance example was all about exposing server functionality to client-side invocation over the WebSocket connection.
+But the great thing about WebSockets is that it's a two-way street. So now let's show an example where the server invokes
action on the client.
This is a web notification channel that allows you to trigger client-side web notifications when you broadcast to the right
@@ -336,7 +336,7 @@ The above will start a cable server on port 28080. Remember to point your client
### In app
-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:
+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:
```ruby
# config/routes.rb
@@ -359,7 +359,7 @@ We'll get all this abstracted properly when the framework is integrated into Rai
## Dependencies
Action Cable is currently tied to Redis through its use of the pubsub feature to route
-messages back and forth over the websocket cable connection. This dependency may well
+messages back and forth over the WebSocket cable connection. This dependency may well
be alleviated in the future, but for the moment that's what it is. So be sure to have
Redis installed and running.
diff --git a/Rakefile b/Rakefile
index c2ae16b7d9..69c95468e9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -7,5 +7,6 @@ Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.pattern = 'test/**/*_test.rb'
t.verbose = true
+ t.warning = false
end
Rake::Task['test'].comment = "Run tests"
diff --git a/actioncable.gemspec b/actioncable.gemspec
index 02350186db..2f4ae41dc1 100644
--- a/actioncable.gemspec
+++ b/actioncable.gemspec
@@ -1,11 +1,11 @@
-$:.push File.expand_path("../lib", __FILE__)
+$:.unshift File.expand_path("../lib", __FILE__)
require 'action_cable/version'
Gem::Specification.new do |s|
s.name = 'actioncable'
s.version = ActionCable::VERSION
- s.summary = 'Websockets framework for Rails.'
- s.description = 'Structure many real-time application concerns into channels over a single websockets connection.'
+ s.summary = 'WebSocket framework for Rails.'
+ s.description = 'Structure many real-time application concerns into channels over a single WebSocket connection.'
s.license = 'MIT'
s.author = ['Pratik Naik', 'David Heinemeier Hansson']
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
s.add_development_dependency 'puma'
s.add_development_dependency 'mocha'
- s.files = Dir['README', 'lib/**/*']
+ s.files = Dir['README.md', 'lib/**/*']
s.has_rdoc = false
s.require_path = 'lib'
diff --git a/gemfiles/rails_42.gemfile b/gemfiles/rails_42.gemfile
new file mode 100644
index 0000000000..8ca60d69db
--- /dev/null
+++ b/gemfiles/rails_42.gemfile
@@ -0,0 +1,5 @@
+source 'https://rubygems.org'
+
+gem 'rails', '~> 4.2.4'
+
+gemspec path: '..'
diff --git a/lib/action_cable.rb b/lib/action_cable.rb
index d2c5251634..89ffa1fda7 100644
--- a/lib/action_cable.rb
+++ b/lib/action_cable.rb
@@ -1,32 +1,21 @@
-require 'eventmachine'
-EventMachine.epoll if EventMachine.epoll?
-EventMachine.kqueue if EventMachine.kqueue?
-
-require 'set'
-
require 'active_support'
-require 'active_support/json'
-require 'active_support/concern'
-require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/callbacks'
-
-require 'faye/websocket'
-require 'celluloid'
-require 'em-hiredis'
-require 'redis'
-
-require 'action_cable/engine' if defined?(Rails)
+require 'active_support/rails'
require 'action_cable/version'
module ActionCable
- autoload :Server, 'action_cable/server'
- autoload :Connection, 'action_cable/connection'
- autoload :Channel, 'action_cable/channel'
- autoload :RemoteConnections, 'action_cable/remote_connections'
+ extend ActiveSupport::Autoload
# Singleton instance of the server
module_function def server
@server ||= ActionCable::Server::Base.new
end
+
+ eager_autoload do
+ autoload :Server
+ autoload :Connection
+ autoload :Channel
+ autoload :RemoteConnections
+ end
end
+
+require 'action_cable/engine' if defined?(Rails)
diff --git a/lib/action_cable/channel.rb b/lib/action_cable/channel.rb
index 3b973ba0a7..7ae262ce5f 100644
--- a/lib/action_cable/channel.rb
+++ b/lib/action_cable/channel.rb
@@ -1,10 +1,14 @@
module ActionCable
module Channel
- autoload :Base, 'action_cable/channel/base'
- autoload :Broadcasting, 'action_cable/channel/broadcasting'
- autoload :Callbacks, 'action_cable/channel/callbacks'
- autoload :Naming, 'action_cable/channel/naming'
- autoload :PeriodicTimers, 'action_cable/channel/periodic_timers'
- autoload :Streams, 'action_cable/channel/streams'
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Base
+ autoload :Broadcasting
+ autoload :Callbacks
+ autoload :Naming
+ autoload :PeriodicTimers
+ autoload :Streams
+ end
end
end
diff --git a/lib/action_cable/channel/base.rb b/lib/action_cable/channel/base.rb
index 2f1b4a187d..df87064195 100644
--- a/lib/action_cable/channel/base.rb
+++ b/lib/action_cable/channel/base.rb
@@ -1,6 +1,8 @@
+require 'set'
+
module ActionCable
module Channel
- # The channel provides the basic structure of grouping behavior into logical units when communicating over the websocket connection.
+ # The channel provides the basic structure of grouping behavior into logical units when communicating over the WebSocket connection.
# You can think of a channel like a form of controller, but one that's capable of pushing content to the subscriber in addition to simply
# responding to the subscriber's direct requests.
#
@@ -139,7 +141,6 @@ module ActionCable
# This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
def unsubscribe_from_channel
run_unsubscribe_callbacks
- logger.info "#{self.class.name} unsubscribed"
end
@@ -160,7 +161,7 @@ module ActionCable
# the proper channel identifier marked as the recipient.
def transmit(data, via: nil)
logger.info "#{self.class.name} transmitting #{data.inspect}".tap { |m| m << " (via #{via})" if via }
- connection.transmit({ identifier: @identifier, message: data }.to_json)
+ connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data)
end
@@ -175,7 +176,6 @@ module ActionCable
def subscribe_to_channel
- logger.info "#{self.class.name} subscribing"
run_subscribe_callbacks
end
diff --git a/lib/action_cable/connection.rb b/lib/action_cable/connection.rb
index 3d6ed6a6e8..b672e00682 100644
--- a/lib/action_cable/connection.rb
+++ b/lib/action_cable/connection.rb
@@ -1,12 +1,16 @@
module ActionCable
module Connection
- autoload :Authorization, 'action_cable/connection/authorization'
- autoload :Base, 'action_cable/connection/base'
- autoload :Identification, 'action_cable/connection/identification'
- autoload :InternalChannel, 'action_cable/connection/internal_channel'
- autoload :MessageBuffer, 'action_cable/connection/message_buffer'
- autoload :WebSocket, 'action_cable/connection/web_socket'
- autoload :Subscriptions, 'action_cable/connection/subscriptions'
- autoload :TaggedLoggerProxy, 'action_cable/connection/tagged_logger_proxy'
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Authorization
+ autoload :Base
+ autoload :Identification
+ autoload :InternalChannel
+ autoload :MessageBuffer
+ autoload :WebSocket
+ autoload :Subscriptions
+ autoload :TaggedLoggerProxy
+ end
end
end
diff --git a/lib/action_cable/connection/base.rb b/lib/action_cable/connection/base.rb
index 2f2fa1fdec..9f74226f98 100644
--- a/lib/action_cable/connection/base.rb
+++ b/lib/action_cable/connection/base.rb
@@ -1,8 +1,8 @@
-require 'action_dispatch/http/request'
+require 'action_dispatch'
module ActionCable
module Connection
- # For every websocket the cable server is accepting, a Connection object will be instantiated. This instance becomes the parent
+ # For every WebSocket the cable server is accepting, a Connection object will be instantiated. This instance becomes the parent
# of all the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
# based on an identifier sent by the cable consumer. The Connection itself does not deal with any specific application logic beyond
# authentication and authorization.
@@ -37,8 +37,8 @@ module ActionCable
# established for that current_user (and potentially disconnect them if the user was removed from an account). You can declare as many
# identification indexes as you like. Declaring an identification means that a attr_accessor is automatically set for that key.
#
- # Second, we rely on the fact that the websocket connection is established with the cookies from the domain being sent along. This makes
- # it easy to use signed cookies that were set when logging in via a web interface to authorize the websocket connection.
+ # Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes
+ # it easy to use signed cookies that were set when logging in via a web interface to authorize the WebSocket connection.
#
# Finally, we add a tag to the connection-specific logger with name of the current user to easily distinguish their messages in the log.
#
@@ -65,7 +65,7 @@ module ActionCable
@started_at = Time.now
end
- # Called by the server when a new websocket connection is established. This configures the callbacks intended for overwriting by the user.
+ # Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user.
# This method should not be called directly. Rely on the #connect (and #disconnect) callback instead.
def process
logger.info started_request_message
@@ -87,19 +87,18 @@ module ActionCable
if websocket.alive?
subscriptions.execute_command ActiveSupport::JSON.decode(data_in_json)
else
- logger.error "Received data without a live websocket (#{data.inspect})"
+ logger.error "Received data without a live WebSocket (#{data_in_json.inspect})"
end
end
- # Send raw data straight back down the websocket. This is not intended to be called directly. Use the #transmit available on the
+ # Send raw data straight back down the WebSocket. This is not intended to be called directly. Use the #transmit available on the
# Channel instead, as that'll automatically address the correct subscriber and wrap the message in JSON.
def transmit(data)
websocket.transmit data
end
- # Close the websocket connection.
+ # Close the WebSocket connection.
def close
- logger.error "Closing connection"
websocket.close
end
@@ -120,12 +119,12 @@ module ActionCable
end
def beat
- transmit({ identifier: '_ping', message: Time.now.to_i }.to_json)
+ transmit ActiveSupport::JSON.encode(identifier: '_ping', message: Time.now.to_i)
end
protected
- # The request that initiated the websocket connection is available here. This gives access to the environment, cookies, etc.
+ # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
def request
@request ||= begin
environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
@@ -133,7 +132,7 @@ module ActionCable
end
end
- # The cookies of the request that initiated the websocket connection. Useful for performing authorization checks.
+ # The cookies of the request that initiated the WebSocket connection. Useful for performing authorization checks.
def cookies
request.cookie_jar
end
@@ -144,13 +143,12 @@ module ActionCable
attr_reader :subscriptions, :message_buffer
def on_open
- server.add_connection(self)
-
connect if respond_to?(:connect)
subscribe_to_internal_channel
beat
message_buffer.process!
+ server.add_connection(self)
rescue ActionCable::Connection::Authorization::UnauthorizedError
respond_to_invalid_request
close
@@ -203,17 +201,17 @@ module ActionCable
'Started %s "%s"%s for %s at %s' % [
request.request_method,
request.filtered_path,
- websocket.possible? ? ' [Websocket]' : '',
+ websocket.possible? ? ' [WebSocket]' : '',
request.ip,
- Time.now.to_default_s ]
+ Time.now.to_s ]
end
def finished_request_message
'Finished "%s"%s for %s at %s' % [
request.filtered_path,
- websocket.possible? ? ' [Websocket]' : '',
+ websocket.possible? ? ' [WebSocket]' : '',
request.ip,
- Time.now.to_default_s ]
+ Time.now.to_s ]
end
end
end
diff --git a/lib/action_cable/connection/identification.rb b/lib/action_cable/connection/identification.rb
index 4e9beac058..431493aa70 100644
--- a/lib/action_cable/connection/identification.rb
+++ b/lib/action_cable/connection/identification.rb
@@ -1,3 +1,5 @@
+require 'set'
+
module ActionCable
module Connection
module Identification
@@ -22,7 +24,7 @@ module ActionCable
# Return a single connection identifier that combines the value of all the registered identifiers into a single gid.
def connection_identifier
- if @connection_identifier.blank?
+ unless defined? @connection_identifier
@connection_identifier = connection_gid identifiers.map { |id| instance_variable_get("@#{id}") }.compact
end
@@ -31,7 +33,13 @@ module ActionCable
private
def connection_gid(ids)
- ids.map { |o| (o.try(:to_global_id) || o).to_s }.sort.join(":")
+ ids.map do |o|
+ if o.respond_to? :to_global_id
+ o.to_global_id
+ else
+ o.to_s
+ end
+ end.sort.join(":")
end
end
end
diff --git a/lib/action_cable/connection/internal_channel.rb b/lib/action_cable/connection/internal_channel.rb
index b00e21824c..c065a24ab7 100644
--- a/lib/action_cable/connection/internal_channel.rb
+++ b/lib/action_cable/connection/internal_channel.rb
@@ -15,14 +15,14 @@ module ActionCable
@_internal_redis_subscriptions ||= []
@_internal_redis_subscriptions << [ internal_redis_channel, callback ]
- pubsub.subscribe(internal_redis_channel, &callback)
+ EM.next_tick { pubsub.subscribe(internal_redis_channel, &callback) }
logger.info "Registered connection (#{connection_identifier})"
end
end
def unsubscribe_from_internal_channel
if @_internal_redis_subscriptions.present?
- @_internal_redis_subscriptions.each { |channel, callback| pubsub.unsubscribe_proc(channel, callback) }
+ @_internal_redis_subscriptions.each { |channel, callback| EM.next_tick { pubsub.unsubscribe_proc(channel, callback) } }
end
end
diff --git a/lib/action_cable/connection/message_buffer.rb b/lib/action_cable/connection/message_buffer.rb
index d5a8e9eba9..25cff75b41 100644
--- a/lib/action_cable/connection/message_buffer.rb
+++ b/lib/action_cable/connection/message_buffer.rb
@@ -1,6 +1,6 @@
module ActionCable
module Connection
- # Allows us to buffer messages received from the websocket before the Connection has been fully initialized and is ready to receive them.
+ # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized and is ready to receive them.
# Entirely internal operation and should not be used directly by the user.
class MessageBuffer
def initialize(connection)
@@ -50,4 +50,4 @@ module ActionCable
end
end
end
-end \ No newline at end of file
+end
diff --git a/lib/action_cable/connection/subscriptions.rb b/lib/action_cable/connection/subscriptions.rb
index 69e3f60706..229be2a316 100644
--- a/lib/action_cable/connection/subscriptions.rb
+++ b/lib/action_cable/connection/subscriptions.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/indifferent_access'
+
module ActionCable
module Connection
# Collection class for all the channel subscriptions established on a given connection. Responsible for routing incoming commands that arrive on
diff --git a/lib/action_cable/connection/web_socket.rb b/lib/action_cable/connection/web_socket.rb
index 135a28cfe4..169b683b8c 100644
--- a/lib/action_cable/connection/web_socket.rb
+++ b/lib/action_cable/connection/web_socket.rb
@@ -1,3 +1,5 @@
+require 'faye/websocket'
+
module ActionCable
module Connection
# Decorate the Faye::WebSocket with helpers we need.
diff --git a/lib/action_cable/engine.rb b/lib/action_cable/engine.rb
index 6c943c7971..613a9b99f2 100644
--- a/lib/action_cable/engine.rb
+++ b/lib/action_cable/engine.rb
@@ -1,4 +1,22 @@
+require 'rails/engine'
+require 'active_support/ordered_options'
+
module ActionCable
class Engine < ::Rails::Engine
+ config.action_cable = ActiveSupport::OrderedOptions.new
+
+ initializer "action_cable.logger" do
+ ActiveSupport.on_load(:action_cable) { self.logger ||= ::Rails.logger }
+ end
+
+ initializer "action_cable.set_configs" do |app|
+ options = app.config.action_cable
+
+ options.allowed_request_origins ||= "http://localhost:3000" if ::Rails.env.development?
+
+ ActiveSupport.on_load(:action_cable) do
+ options.each { |k,v| send("#{k}=", v) }
+ end
+ end
end
end
diff --git a/lib/action_cable/server.rb b/lib/action_cable/server.rb
index 2278509341..a2a89d5f1e 100644
--- a/lib/action_cable/server.rb
+++ b/lib/action_cable/server.rb
@@ -1,11 +1,19 @@
+require 'eventmachine'
+EventMachine.epoll if EventMachine.epoll?
+EventMachine.kqueue if EventMachine.kqueue?
+
module ActionCable
module Server
- autoload :Base, 'action_cable/server/base'
- autoload :Broadcasting, 'action_cable/server/broadcasting'
- autoload :Connections, 'action_cable/server/connections'
- autoload :Configuration, 'action_cable/server/configuration'
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Base
+ autoload :Broadcasting
+ autoload :Connections
+ autoload :Configuration
- autoload :Worker, 'action_cable/server/worker'
- autoload :ActiveRecordConnectionManagement, 'action_cable/server/worker/active_record_connection_management'
+ autoload :Worker
+ autoload :ActiveRecordConnectionManagement, 'action_cable/server/worker/active_record_connection_management'
+ end
end
end
diff --git a/lib/action_cable/server/base.rb b/lib/action_cable/server/base.rb
index 9315a48f20..f1585dc776 100644
--- a/lib/action_cable/server/base.rb
+++ b/lib/action_cable/server/base.rb
@@ -1,3 +1,5 @@
+require 'em-hiredis'
+
module ActionCable
module Server
# A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the rack process that starts the cable server, but
@@ -66,5 +68,7 @@ module ActionCable
config.connection_class.identifiers
end
end
+
+ ActiveSupport.run_load_hooks(:action_cable, Base.config)
end
end
diff --git a/lib/action_cable/server/broadcasting.rb b/lib/action_cable/server/broadcasting.rb
index 037b98951e..6e0fbae387 100644
--- a/lib/action_cable/server/broadcasting.rb
+++ b/lib/action_cable/server/broadcasting.rb
@@ -1,3 +1,5 @@
+require 'redis'
+
module ActionCable
module Server
# Broadcasting is how other parts of your application can send messages to the channel subscribers. As explained in Channel, most of the time, these
@@ -44,9 +46,9 @@ module ActionCable
def broadcast(message)
server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message}"
- server.broadcasting_redis.publish broadcasting, message.to_json
+ server.broadcasting_redis.publish broadcasting, ActiveSupport::JSON.encode(message)
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/lib/action_cable/server/configuration.rb b/lib/action_cable/server/configuration.rb
index 315782ec3e..b22de273b8 100644
--- a/lib/action_cable/server/configuration.rb
+++ b/lib/action_cable/server/configuration.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/indifferent_access'
+
module ActionCable
module Server
# An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak the configuration points
diff --git a/lib/action_cable/server/connections.rb b/lib/action_cable/server/connections.rb
index b3d1632cf7..47dcea8c20 100644
--- a/lib/action_cable/server/connections.rb
+++ b/lib/action_cable/server/connections.rb
@@ -18,13 +18,13 @@ module ActionCable
connections.delete connection
end
- # Websocket connection implementations differ on when they'll mark a connection as stale. We basically never want a connection to go stale, as you
+ # WebSocket connection implementations differ on when they'll mark a connection as stale. We basically never want a connection to go stale, as you
# then can't rely on being able to receive and send to it. So there's a 3 second heartbeat running on all connections. If the beat fails, we automatically
# disconnect.
def setup_heartbeat_timer
EM.next_tick do
@heartbeat_timer ||= EventMachine.add_periodic_timer(BEAT_INTERVAL) do
- EM.next_tick { connections.map &:beat }
+ EM.next_tick { connections.map(&:beat) }
end
end
end
@@ -34,4 +34,4 @@ module ActionCable
end
end
end
-end \ No newline at end of file
+end
diff --git a/lib/action_cable/server/worker.rb b/lib/action_cable/server/worker.rb
index 91496775b8..e063b2a2e1 100644
--- a/lib/action_cable/server/worker.rb
+++ b/lib/action_cable/server/worker.rb
@@ -1,3 +1,6 @@
+require 'celluloid'
+require 'active_support/callbacks'
+
module ActionCable
module Server
# Worker used by Server.send_async to do connection work in threads. Only for internal use.
@@ -36,4 +39,4 @@ module ActionCable
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/channel/base_test.rb b/test/channel/base_test.rb
index e7944ff06b..bac8569780 100644
--- a/test/channel/base_test.rb
+++ b/test/channel/base_test.rb
@@ -23,6 +23,11 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
on_subscribe :toggle_subscribed
on_unsubscribe :toggle_subscribed
+ def initialize(*)
+ @subscribed = false
+ super
+ end
+
def subscribed
@room = Room.new params[:id]
@actions = []
diff --git a/test/channel/stream_test.rb b/test/channel/stream_test.rb
index b0a6f49072..5914b39be0 100644
--- a/test/channel/stream_test.rb
+++ b/test/channel/stream_test.rb
@@ -2,7 +2,7 @@ require 'test_helper'
require 'stubs/test_connection'
require 'stubs/room'
-class ActionCable::Channel::StreamTest < ActiveSupport::TestCase
+class ActionCable::Channel::StreamTest < ActionCable::TestCase
class ChatChannel < ActionCable::Channel::Base
def subscribed
if params[:id]
@@ -17,16 +17,23 @@ class ActionCable::Channel::StreamTest < ActiveSupport::TestCase
end
test "streaming start and stop" do
- @connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1") }
- channel = ChatChannel.new @connection, "{id: 1}", { id: 1 }
+ run_in_eventmachine do
+ @connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1") }
+ channel = ChatChannel.new @connection, "{id: 1}", { id: 1 }
- @connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe_proc) }
- channel.unsubscribe_from_channel
+ @connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe_proc) }
+ channel.unsubscribe_from_channel
+ end
end
test "stream_for" do
- @connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire") }
- channel = ChatChannel.new @connection, ""
- channel.stream_for Room.new(1)
+ run_in_eventmachine do
+ EM.next_tick do
+ @connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire") }
+ end
+
+ channel = ChatChannel.new @connection, ""
+ channel.stream_for Room.new(1)
+ end
end
end
diff --git a/test/connection/authorization_test.rb b/test/connection/authorization_test.rb
index 09dfead8c8..762c90fbbc 100644
--- a/test/connection/authorization_test.rb
+++ b/test/connection/authorization_test.rb
@@ -1,7 +1,7 @@
require 'test_helper'
require 'stubs/test_server'
-class ActionCable::Connection::AuthorizationTest < ActiveSupport::TestCase
+class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase
class Connection < ActionCable::Connection::Base
attr_reader :websocket
@@ -10,17 +10,15 @@ class ActionCable::Connection::AuthorizationTest < ActiveSupport::TestCase
end
end
- setup do
- @server = TestServer.new
-
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
- @connection = Connection.new(@server, env)
- end
-
test "unauthorized connection" do
- @connection.websocket.expects(:close)
+ run_in_eventmachine do
+ server = TestServer.new
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
- @connection.process
- @connection.send :on_open
+ connection = Connection.new(server, env)
+ connection.websocket.expects(:close)
+ connection.process
+ connection.send :on_open
+ end
end
end
diff --git a/test/connection/base_test.rb b/test/connection/base_test.rb
index 7118c34d9e..da6041db4a 100644
--- a/test/connection/base_test.rb
+++ b/test/connection/base_test.rb
@@ -1,7 +1,7 @@
require 'test_helper'
require 'stubs/test_server'
-class ActionCable::Connection::BaseTest < ActiveSupport::TestCase
+class ActionCable::Connection::BaseTest < ActionCable::TestCase
class Connection < ActionCable::Connection::Base
attr_reader :websocket, :subscriptions, :message_buffer, :connected
@@ -12,69 +12,107 @@ class ActionCable::Connection::BaseTest < ActiveSupport::TestCase
def disconnect
@connected = false
end
+
+ def send_async(method, *args)
+ # Bypass Celluloid
+ send method, *args
+ end
end
setup do
@server = TestServer.new
@server.config.allowed_request_origins = %w( http://rubyonrails.com )
-
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
- 'HTTP_ORIGIN' => 'http://rubyonrails.com'
-
- @connection = Connection.new(@server, env)
- @response = @connection.process
end
test "making a connection with invalid headers" do
- connection = ActionCable::Connection::Base.new(@server, Rack::MockRequest.env_for("/test"))
- response = connection.process
- assert_equal 404, response[0]
+ run_in_eventmachine do
+ connection = ActionCable::Connection::Base.new(@server, Rack::MockRequest.env_for("/test"))
+ response = connection.process
+ assert_equal 404, response[0]
+ end
end
test "websocket connection" do
- assert @connection.websocket.possible?
- assert @connection.websocket.alive?
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ assert connection.websocket.possible?
+ assert connection.websocket.alive?
+ end
end
test "rack response" do
- assert_equal [ -1, {}, [] ], @response
+ run_in_eventmachine do
+ connection = open_connection
+ response = connection.process
+
+ assert_equal [ -1, {}, [] ], response
+ end
end
test "on connection open" do
- assert ! @connection.connected
-
- @connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/))
- @connection.message_buffer.expects(:process!)
-
- @connection.send :on_open
-
- assert_equal [ @connection ], @server.connections
- assert @connection.connected
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/))
+ connection.message_buffer.expects(:process!)
+
+ # Allow EM to run on_open callback
+ EM.next_tick do
+ assert_equal [ connection ], @server.connections
+ assert connection.connected
+ end
+ end
end
test "on connection close" do
- # Setup the connection
- EventMachine.stubs(:add_periodic_timer).returns(true)
- @connection.send :on_open
- assert @connection.connected
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ # Setup the connection
+ EventMachine.stubs(:add_periodic_timer).returns(true)
+ connection.send :on_open
+ assert connection.connected
- @connection.subscriptions.expects(:unsubscribe_from_all)
- @connection.send :on_close
+ connection.subscriptions.expects(:unsubscribe_from_all)
+ connection.send :on_close
- assert ! @connection.connected
- assert_equal [], @server.connections
+ assert ! connection.connected
+ assert_equal [], @server.connections
+ end
end
test "connection statistics" do
- statistics = @connection.statistics
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ statistics = connection.statistics
- assert statistics[:identifier].blank?
- assert_kind_of Time, statistics[:started_at]
- assert_equal [], statistics[:subscriptions]
+ assert statistics[:identifier].blank?
+ assert_kind_of Time, statistics[:started_at]
+ assert_equal [], statistics[:subscriptions]
+ end
end
test "explicitly closing a connection" do
- @connection.websocket.expects(:close)
- @connection.close
+ run_in_eventmachine do
+ connection = open_connection
+ connection.process
+
+ connection.websocket.expects(:close)
+ connection.close
+ end
end
+
+ private
+ def open_connection
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket',
+ 'HTTP_ORIGIN' => 'http://rubyonrails.com'
+
+ Connection.new(@server, env)
+ end
end
diff --git a/test/connection/cross_site_forgery_test.rb b/test/connection/cross_site_forgery_test.rb
index 6073f89287..166abb7b38 100644
--- a/test/connection/cross_site_forgery_test.rb
+++ b/test/connection/cross_site_forgery_test.rb
@@ -1,9 +1,16 @@
require 'test_helper'
require 'stubs/test_server'
-class ActionCable::Connection::CrossSiteForgeryTest < ActiveSupport::TestCase
+class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase
HOST = 'rubyonrails.com'
+ class Connection < ActionCable::Connection::Base
+ def send_async(method, *args)
+ # Bypass Celluloid
+ send method, *args
+ end
+ end
+
setup do
@server = TestServer.new
@server.config.allowed_request_origins = %w( http://rubyonrails.com )
@@ -45,7 +52,13 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActiveSupport::TestCase
end
def connect_with_origin(origin)
- ActionCable::Connection::Base.new(@server, env_for_origin(origin)).process
+ response = nil
+
+ run_in_eventmachine do
+ response = Connection.new(@server, env_for_origin(origin)).process
+ end
+
+ response
end
def env_for_origin(origin)
diff --git a/test/connection/identifier_test.rb b/test/connection/identifier_test.rb
index 745cf308d0..02e6b21845 100644
--- a/test/connection/identifier_test.rb
+++ b/test/connection/identifier_test.rb
@@ -2,7 +2,7 @@ require 'test_helper'
require 'stubs/test_server'
require 'stubs/user'
-class ActionCable::Connection::IdentifierTest < ActiveSupport::TestCase
+class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
class Connection < ActionCable::Connection::Base
identified_by :current_user
attr_reader :websocket
@@ -14,59 +14,59 @@ class ActionCable::Connection::IdentifierTest < ActiveSupport::TestCase
end
end
- setup do
- @server = TestServer.new
-
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
- @connection = Connection.new(@server, env)
- end
-
test "connection identifier" do
- open_connection_with_stubbed_pubsub
- assert_equal "User#lifo", @connection.connection_identifier
- end
-
- test "should subscribe to internal channel on open" do
- pubsub = mock('pubsub')
- pubsub.expects(:subscribe).with('action_cable/User#lifo')
- @server.expects(:pubsub).returns(pubsub)
-
- open_connection
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+ assert_equal "User#lifo", @connection.connection_identifier
+ end
end
- test "should unsubscribe from internal channel on close" do
- open_connection_with_stubbed_pubsub
+ test "should subscribe to internal channel on open and unsubscribe on close" do
+ run_in_eventmachine do
+ pubsub = mock('pubsub')
+ pubsub.expects(:subscribe).with('action_cable/User#lifo')
+ pubsub.expects(:unsubscribe_proc).with('action_cable/User#lifo', kind_of(Proc))
- pubsub = mock('pubsub')
- pubsub.expects(:unsubscribe_proc).with('action_cable/User#lifo', kind_of(Proc))
- @server.expects(:pubsub).returns(pubsub)
+ server = TestServer.new
+ server.stubs(:pubsub).returns(pubsub)
- close_connection
+ open_connection server: server
+ close_connection
+ end
end
test "processing disconnect message" do
- open_connection_with_stubbed_pubsub
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
- @connection.websocket.expects(:close)
- message = { 'type' => 'disconnect' }.to_json
- @connection.process_internal_message message
+ @connection.websocket.expects(:close)
+ message = ActiveSupport::JSON.encode('type' => 'disconnect')
+ @connection.process_internal_message message
+ end
end
test "processing invalid message" do
- open_connection_with_stubbed_pubsub
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
- @connection.websocket.expects(:close).never
- message = { 'type' => 'unknown' }.to_json
- @connection.process_internal_message message
+ @connection.websocket.expects(:close).never
+ message = ActiveSupport::JSON.encode('type' => 'unknown')
+ @connection.process_internal_message message
+ end
end
protected
def open_connection_with_stubbed_pubsub
- @server.stubs(:pubsub).returns(stub_everything('pubsub'))
- open_connection
+ server = TestServer.new
+ server.stubs(:pubsub).returns(stub_everything('pubsub'))
+
+ open_connection server: server
end
- def open_connection
+ def open_connection(server:)
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ @connection = Connection.new(server, env)
+
@connection.process
@connection.send :on_open
end
diff --git a/test/connection/string_identifier_test.rb b/test/connection/string_identifier_test.rb
index 87a9025008..ab69df57b3 100644
--- a/test/connection/string_identifier_test.rb
+++ b/test/connection/string_identifier_test.rb
@@ -1,34 +1,39 @@
require 'test_helper'
require 'stubs/test_server'
-class ActionCable::Connection::StringIdentifierTest < ActiveSupport::TestCase
+class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase
class Connection < ActionCable::Connection::Base
identified_by :current_token
def connect
self.current_token = "random-string"
end
- end
-
- setup do
- @server = TestServer.new
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
- @connection = Connection.new(@server, env)
+ def send_async(method, *args)
+ # Bypass Celluloid
+ send method, *args
+ end
end
test "connection identifier" do
- open_connection_with_stubbed_pubsub
- assert_equal "random-string", @connection.connection_identifier
+ run_in_eventmachine do
+ open_connection_with_stubbed_pubsub
+ assert_equal "random-string", @connection.connection_identifier
+ end
end
protected
def open_connection_with_stubbed_pubsub
+ @server = TestServer.new
@server.stubs(:pubsub).returns(stub_everything('pubsub'))
+
open_connection
end
def open_connection
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ @connection = Connection.new(@server, env)
+
@connection.process
@connection.send :on_open
end
diff --git a/test/connection/subscriptions_test.rb b/test/connection/subscriptions_test.rb
index 24fe8f9300..4f6760827e 100644
--- a/test/connection/subscriptions_test.rb
+++ b/test/connection/subscriptions_test.rb
@@ -1,8 +1,13 @@
require 'test_helper'
-class ActionCable::Connection::SubscriptionsTest < ActiveSupport::TestCase
+class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
class Connection < ActionCable::Connection::Base
attr_reader :websocket
+
+ def send_async(method, *args)
+ # Bypass Celluloid
+ send method, *args
+ end
end
class ChatChannel < ActionCable::Channel::Base
@@ -22,59 +27,76 @@ class ActionCable::Connection::SubscriptionsTest < ActiveSupport::TestCase
@server = TestServer.new
@server.stubs(:channel_classes).returns(ChatChannel.name => ChatChannel)
- env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
- @connection = Connection.new(@server, env)
-
- @subscriptions = ActionCable::Connection::Subscriptions.new(@connection)
- @chat_identifier = { id: 1, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel' }.to_json
+ @chat_identifier = ActiveSupport::JSON.encode(id: 1, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel')
end
test "subscribe command" do
- channel = subscribe_to_chat_channel
+ run_in_eventmachine do
+ setup_connection
+ channel = subscribe_to_chat_channel
- assert_kind_of ChatChannel, channel
- assert_equal 1, channel.room.id
+ assert_kind_of ChatChannel, channel
+ assert_equal 1, channel.room.id
+ end
end
test "subscribe command without an identifier" do
- @subscriptions.execute_command 'command' => 'subscribe'
- assert @subscriptions.identifiers.empty?
+ run_in_eventmachine do
+ setup_connection
+
+ @subscriptions.execute_command 'command' => 'subscribe'
+ assert @subscriptions.identifiers.empty?
+ end
end
test "unsubscribe command" do
- subscribe_to_chat_channel
+ run_in_eventmachine do
+ setup_connection
+ subscribe_to_chat_channel
- channel = subscribe_to_chat_channel
- channel.expects(:unsubscribe_from_channel)
+ channel = subscribe_to_chat_channel
+ channel.expects(:unsubscribe_from_channel)
- @subscriptions.execute_command 'command' => 'unsubscribe', 'identifier' => @chat_identifier
- assert @subscriptions.identifiers.empty?
+ @subscriptions.execute_command 'command' => 'unsubscribe', 'identifier' => @chat_identifier
+ assert @subscriptions.identifiers.empty?
+ end
end
test "unsubscribe command without an identifier" do
- @subscriptions.execute_command 'command' => 'unsubscribe'
- assert @subscriptions.identifiers.empty?
+ run_in_eventmachine do
+ setup_connection
+
+ @subscriptions.execute_command 'command' => 'unsubscribe'
+ assert @subscriptions.identifiers.empty?
+ end
end
test "message command" do
- channel = subscribe_to_chat_channel
+ run_in_eventmachine do
+ setup_connection
+ channel = subscribe_to_chat_channel
- data = { 'content' => 'Hello World!', 'action' => 'speak' }
- @subscriptions.execute_command 'command' => 'message', 'identifier' => @chat_identifier, 'data' => data.to_json
+ data = { 'content' => 'Hello World!', 'action' => 'speak' }
+ @subscriptions.execute_command 'command' => 'message', 'identifier' => @chat_identifier, 'data' => ActiveSupport::JSON.encode(data)
- assert_equal [ data ], channel.lines
+ assert_equal [ data ], channel.lines
+ end
end
test "unsubscrib from all" do
- channel1 = subscribe_to_chat_channel
+ run_in_eventmachine do
+ setup_connection
- channel2_id = { id: 2, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel' }.to_json
- channel2 = subscribe_to_chat_channel(channel2_id)
+ channel1 = subscribe_to_chat_channel
- channel1.expects(:unsubscribe_from_channel)
- channel2.expects(:unsubscribe_from_channel)
+ channel2_id = ActiveSupport::JSON.encode(id: 2, channel: 'ActionCable::Connection::SubscriptionsTest::ChatChannel')
+ channel2 = subscribe_to_chat_channel(channel2_id)
- @subscriptions.unsubscribe_from_all
+ channel1.expects(:unsubscribe_from_channel)
+ channel2.expects(:unsubscribe_from_channel)
+
+ @subscriptions.unsubscribe_from_all
+ end
end
private
@@ -84,4 +106,11 @@ class ActionCable::Connection::SubscriptionsTest < ActiveSupport::TestCase
@subscriptions.send :find, 'identifier' => identifier
end
+
+ def setup_connection
+ env = Rack::MockRequest.env_for "/test", 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket'
+ @connection = Connection.new(@server, env)
+
+ @subscriptions = ActionCable::Connection::Subscriptions.new(@connection)
+ end
end
diff --git a/test/stubs/test_server.rb b/test/stubs/test_server.rb
index 2a7ac3e927..f9168f9b78 100644
--- a/test/stubs/test_server.rb
+++ b/test/stubs/test_server.rb
@@ -9,4 +9,7 @@ class TestServer
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
@config = OpenStruct.new(log_tags: [])
end
+
+ def send_async
+ end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 5640178f34..f8a9971077 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -18,4 +18,29 @@ ActiveSupport.test_order = :sorted
# Require all the stubs and models
Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file }
+require 'celluloid'
+$CELLULOID_DEBUG = false
+$CELLULOID_TEST = false
Celluloid.logger = Logger.new(StringIO.new)
+
+require 'faye/websocket'
+class << Faye::WebSocket
+ remove_method :ensure_reactor_running
+
+ # We don't want Faye to start the EM reactor in tests because it makes testing much harder.
+ # We want to be able to start and stop EM loop in tests to make things simpler.
+ def ensure_reactor_running
+ # no-op
+ end
+end
+
+class ActionCable::TestCase < ActiveSupport::TestCase
+ def run_in_eventmachine
+ EM.run do
+ yield
+
+ EM.run_deferred_callbacks
+ EM.stop
+ end
+ end
+end
diff --git a/test/worker_test.rb b/test/worker_test.rb
index e1fa6f561b..69c4b6529d 100644
--- a/test/worker_test.rb
+++ b/test/worker_test.rb
@@ -11,6 +11,9 @@ class WorkerTest < ActiveSupport::TestCase
def process(message)
@last_action = [ :process, message ]
end
+
+ def connection
+ end
end
setup do