websocket-rails 0.1.2 → 0.1.3
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.
- data/CHANGELOG.md +34 -0
- data/Gemfile +1 -0
- data/README.md +34 -34
- data/assets/javascripts/http_dispatcher.js +6 -5
- data/assets/javascripts/websocket_dispatcher.js +7 -6
- data/lib/websocket-rails.rb +22 -2
- data/lib/websocket_rails/base_controller.rb +24 -8
- data/lib/websocket_rails/connection_adapters.rb +43 -16
- data/lib/websocket_rails/connection_adapters/http.rb +14 -10
- data/lib/websocket_rails/connection_adapters/web_socket.rb +13 -12
- data/lib/websocket_rails/connection_manager.rb +19 -47
- data/lib/websocket_rails/dispatcher.rb +39 -33
- data/lib/websocket_rails/event.rb +71 -0
- data/lib/websocket_rails/event_map.rb +120 -0
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/dummy/config/initializers/events.rb +2 -2
- data/spec/dummy/{public/stylesheets/.gitkeep → log/development.log} +0 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/log/test.log +454 -0
- data/spec/integration/connection_manager_spec.rb +29 -13
- data/spec/spec_helper.rb +3 -3
- data/spec/support/helper_methods.rb +29 -0
- data/spec/support/mock_web_socket.rb +6 -2
- data/spec/unit/connection_adapters/http_spec.rb +1 -3
- data/spec/unit/connection_adapters/web_socket_spec.rb +1 -11
- data/spec/unit/connection_adapters_spec.rb +51 -19
- data/spec/unit/connection_manager_spec.rb +22 -46
- data/spec/unit/dispatcher_spec.rb +56 -25
- data/spec/unit/event_map_spec.rb +96 -0
- data/spec/unit/event_spec.rb +99 -0
- metadata +23 -25
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/.travis.yml +0 -3
- data/Gemfile.lock +0 -144
- data/Guardfile +0 -9
- data/lib/websocket_rails/events.rb +0 -53
- data/spec/unit/events_spec.rb +0 -70
- data/websocket-rails.gemspec +0 -26
data/CHANGELOG.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# WebsocketRails Change Log
|
2
|
+
|
3
|
+
## Version 0.1.3
|
4
|
+
|
5
|
+
June 22 2012
|
6
|
+
|
7
|
+
* Added support for namespaced events.
|
8
|
+
* Improved event machine scheduling for action processing.
|
9
|
+
* Made a client's connection ID private.
|
10
|
+
* Bugfixes in the JavaScript event dispatchers.
|
11
|
+
|
12
|
+
## Version 0.1.2
|
13
|
+
|
14
|
+
June 10 2012
|
15
|
+
|
16
|
+
* Added streaming HTTP support as a fallback from WebSockets.
|
17
|
+
* Added example JavaScript event dispatchers.
|
18
|
+
|
19
|
+
## Version 0.1.1
|
20
|
+
|
21
|
+
June 2 2012
|
22
|
+
|
23
|
+
* Created project home page.
|
24
|
+
* Improved test coverage and cleaned up the internals.
|
25
|
+
|
26
|
+
## Version 0.1.0
|
27
|
+
|
28
|
+
April 14 2012
|
29
|
+
|
30
|
+
* Complete project rewrite.
|
31
|
+
* Removed websocket-rack dependency.
|
32
|
+
* Enhanced documentation.
|
33
|
+
* Added event observers in WebsocketRail Controllers.
|
34
|
+
* First stable release!
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -41,10 +41,16 @@ There are two built in events that are fired automatically by the dispatcher. Th
|
|
41
41
|
|
42
42
|
You can subscribe multiple controllers and actions to the same event to provide very clean event handling logic. The new message will be available in each controller using the `message` method discussed in the *Controllers* section below. The example event router below demonstrates subscribing to the `:new_message` event with one controller action to rebroadcast the message out to all connected clients and another controller action to log the message to a database.
|
43
43
|
|
44
|
+
The Event Map now supports namespacing events. Events triggered on the
|
45
|
+
client as `namespace.event_name` will now be dispatched the the action
|
46
|
+
subscribed to the `event_name` event under the `namespace` namespace.
|
47
|
+
See the [EventMap
|
48
|
+
Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/frames/WebsocketRails/EventMap) for more details.
|
49
|
+
|
44
50
|
````ruby
|
45
51
|
# app/config/initializers
|
46
52
|
|
47
|
-
WebsocketRails::
|
53
|
+
WebsocketRails::EventMap.describe do
|
48
54
|
# The :client_connected method is fired automatically when a new client connects
|
49
55
|
subscribe :client_connected, to: ChatController, with_method: :client_connected
|
50
56
|
|
@@ -55,6 +61,10 @@ WebsocketRails::Events.describe_events do
|
|
55
61
|
subscribe :new_user, to: ChatController, with_method: :new_user
|
56
62
|
subscribe :change_username, to: ChatController, with_method: :change_username
|
57
63
|
|
64
|
+
namespace :product do
|
65
|
+
subscribe :new, to: ProductController, with_method: :new_product
|
66
|
+
end
|
67
|
+
|
58
68
|
# The :client_disconnected method is fired automatically when a client disconnects
|
59
69
|
subscribe :client_disconnected, to: ChatController, with_method: :delete_user
|
60
70
|
end
|
@@ -62,42 +72,32 @@ end
|
|
62
72
|
|
63
73
|
The `subscribe` method takes the event name as the first argument, then a hash where `:to` is the Controller class and `:with_method` is the action to execute.
|
64
74
|
|
65
|
-
##
|
75
|
+
## JavaScript Dispatcher
|
76
|
+
|
77
|
+
There are two example dispatchers located in the [assets/javascripts](https://github.com/DanKnox/websocket-rails/tree/master/assets/javascripts) directory. One connects to the server using
|
78
|
+
WebSockets and the other connects using streaming HTTP. These will eventually be merged into one dispatcher that selects the best transport at runtime based on what's available in the
|
79
|
+
browser. A pull request providing this functionality will definitely be accepted.
|
66
80
|
|
67
|
-
The
|
81
|
+
The current dispatchers are limited in functionality and meant mostly as a reference implementation. The two dispatchers are functionally equivalent and can be swapped out at will.
|
68
82
|
|
69
83
|
````javascript
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
}
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
84
|
+
//Setting up the dispatcher and connecting to the server:
|
85
|
+
var dispatcher = new ServerEventsDispatcher()
|
86
|
+
dispatcher.onopen(function() {
|
87
|
+
// trigger a server event immediately after opening connection
|
88
|
+
dispatcher.trigger('new_user',{user_name: 'guest'})
|
89
|
+
})
|
90
|
+
|
91
|
+
//Triggering a new event on the server
|
92
|
+
dispatcher.trigger('event_name',object_to_be_serialized_to_json)
|
93
|
+
|
94
|
+
//Listening for new events from the server
|
95
|
+
dispatcher.bind('event_name', function(data) {
|
96
|
+
alert(data.user_name)
|
97
|
+
})
|
82
98
|
````
|
83
99
|
|
84
|
-
|
85
|
-
[assets/javascripts](https://github.com/DanKnox/websocket-rails/tree/master/assets/javascripts) directory.
|
86
|
-
One for connecting to the server using WebSockets and the other for
|
87
|
-
using streaming HTTP. The HTTP dispatcher was built to mimick the
|
88
|
-
WebSocket interface so they are completely interchangable. These will
|
89
|
-
eventually be merged into one dispatcher which detects which protocol to
|
90
|
-
use based on what's available in the browser. Please feel free to submit
|
91
|
-
a pull request that accomplishes this.
|
92
|
-
|
93
|
-
View the source for the dispatchers for example usage or check out the
|
94
|
-
[example application](https://github.com/DanKnox/websocket-rails-Example-Project) for a working implementation.
|
95
|
-
|
96
|
-
*Note on the dispatchers*
|
97
|
-
|
98
|
-
The example dispatchers are currently meant to be used for reference and
|
99
|
-
are not yet included into the Rails asset pipleline. If you want to use
|
100
|
-
one, copy it into your local project.
|
100
|
+
Check out the [example application](https://github.com/DanKnox/websocket-rails-Example-Project) for a working implementation.
|
101
101
|
|
102
102
|
## Controllers
|
103
103
|
|
@@ -184,7 +184,7 @@ end
|
|
184
184
|
|
185
185
|
## Message Format
|
186
186
|
|
187
|
-
The message can be a string, hash, or array. The message is serialized as JSON before being sent to the client. The message arrives at the client as a
|
187
|
+
The message can be a string, hash, or array. The message is serialized as JSON before being sent to the client. The message arrives at the client as a two element serialized array with the `event_name` string as the first element, and the message object you passed to the `message` parameter of the `send_message` method as the second element. The example JavaScript dispatchers decode the JSON for you as well as provide a few conveniences for event subscribing and dispatching.
|
188
188
|
|
189
189
|
If you executed this code in your controller:
|
190
190
|
|
@@ -196,7 +196,7 @@ send_message :new_message, new_message
|
|
196
196
|
The message that arrives on the client would look like:
|
197
197
|
|
198
198
|
````javascript
|
199
|
-
['
|
199
|
+
['new_message',{message: 'this is a message'}]
|
200
200
|
````
|
201
201
|
|
202
202
|
## Development
|
@@ -21,18 +21,19 @@ var ServerEventsDispatcher = function(){
|
|
21
21
|
open_handler = function(){},
|
22
22
|
loaded = false,
|
23
23
|
lastPos = 0,
|
24
|
-
client_id =
|
24
|
+
client_id = 0;
|
25
25
|
|
26
26
|
conn.onreadystatechange = function() {
|
27
27
|
if (conn.readyState == 3) {
|
28
28
|
var data = conn.responseText.substring(lastPos);
|
29
29
|
lastPos = conn.responseText.length;
|
30
30
|
var json_data = JSON.parse(data),
|
31
|
-
|
32
|
-
|
33
|
-
message = json_data[2];
|
31
|
+
event_name = json_data[0],
|
32
|
+
message = json_data[1];
|
34
33
|
|
35
|
-
client_id
|
34
|
+
if (client_id == 0 && event_name == 'client_connected') {
|
35
|
+
client_id = message.connection_id
|
36
|
+
}
|
36
37
|
|
37
38
|
if (loaded == false) {
|
38
39
|
open_handler();
|
@@ -42,11 +42,12 @@ var ServerEventsDispatcher = function(){
|
|
42
42
|
|
43
43
|
conn.onmessage = function(evt) {
|
44
44
|
var data = JSON.parse(evt.data),
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
event_name = data[0],
|
46
|
+
message = data[1];
|
47
|
+
|
48
|
+
if (client_id === '' && event_name === 'client_connected') {
|
49
|
+
client_id = message.connection_id
|
50
|
+
}
|
50
51
|
console.log(data)
|
51
52
|
dispatch(event_name, message)
|
52
53
|
}
|
@@ -62,4 +63,4 @@ var ServerEventsDispatcher = function(){
|
|
62
63
|
chain[i]( message )
|
63
64
|
}
|
64
65
|
}
|
65
|
-
}
|
66
|
+
}
|
data/lib/websocket-rails.rb
CHANGED
@@ -20,7 +20,8 @@ end
|
|
20
20
|
require "websocket_rails/engine"
|
21
21
|
require 'websocket_rails/connection_manager'
|
22
22
|
require 'websocket_rails/dispatcher'
|
23
|
-
require 'websocket_rails/
|
23
|
+
require 'websocket_rails/event'
|
24
|
+
require 'websocket_rails/event_map'
|
24
25
|
require 'websocket_rails/base_controller'
|
25
26
|
|
26
27
|
require 'websocket_rails/connection_adapters'
|
@@ -28,4 +29,23 @@ require 'websocket_rails/connection_adapters/http'
|
|
28
29
|
require 'websocket_rails/connection_adapters/web_socket'
|
29
30
|
|
30
31
|
::Thin::Server.send( :remove_const, 'DEFAULT_TIMEOUT' )
|
31
|
-
::Thin::Server.const_set( 'DEFAULT_TIMEOUT', 0 )
|
32
|
+
::Thin::Server.const_set( 'DEFAULT_TIMEOUT', 0 )
|
33
|
+
|
34
|
+
# Exceptions
|
35
|
+
class InvalidConnectionError < StandardError
|
36
|
+
def rack_response
|
37
|
+
[400,{'Content-Type' => 'text/plain'},['invalid connection']]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Deprecation Notices
|
42
|
+
class WebsocketRails::Dispatcher
|
43
|
+
def self.describe_events(&block)
|
44
|
+
raise "This method has been deprecated. Please use WebsocketRails::EventMap.describe instead."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
class WebsocketRails::Events
|
48
|
+
def self.describe_events(&block)
|
49
|
+
raise "This method has been deprecated. Please use WebsocketRails::EventMap.describe instead."
|
50
|
+
end
|
51
|
+
end
|
@@ -53,7 +53,7 @@ module WebsocketRails
|
|
53
53
|
# Provides direct access to the Faye::WebSocket connection object for the client that
|
54
54
|
# initiated the event that is currently being executed.
|
55
55
|
def connection
|
56
|
-
@
|
56
|
+
@_event.connection
|
57
57
|
end
|
58
58
|
|
59
59
|
# The numerical ID for the client connection that initiated the event. The ID is unique
|
@@ -66,23 +66,39 @@ module WebsocketRails
|
|
66
66
|
# The current message that was passed from the client when the event was initiated. The
|
67
67
|
# message is typically a standard ruby Hash object. See the README for more information.
|
68
68
|
def message
|
69
|
-
@
|
69
|
+
@_event.data
|
70
70
|
end
|
71
|
+
alias_method :data, :message
|
71
72
|
|
72
73
|
# Sends a message to the client that initiated the current event being executed. Messages
|
73
74
|
# are serialized as JSON into a two element Array where the first element is the event
|
74
75
|
# and the second element is the message that was passed, typically a Hash.
|
76
|
+
#
|
77
|
+
# # Will arrive on the client as JSON string like the following:
|
78
|
+
# # ['new_message',{'message': 'new message for the client'}]
|
75
79
|
# message_hash = {:message => 'new message for the client'}
|
76
80
|
# send_message :new_message, message_hash
|
77
|
-
#
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
+
#
|
82
|
+
# To send an event under a namespace, add the `:namespace => :target_namespace` option.
|
83
|
+
#
|
84
|
+
# # Will arrive as: ['product.new_message',{'message': 'new message'}]
|
85
|
+
# send_message :new_message, message_hash, :namespace => :product
|
86
|
+
#
|
87
|
+
# Nested namespaces can be passed as an array like the following:
|
88
|
+
#
|
89
|
+
# # Will arrive as: ['products.glasses.new',{'message': 'new message'}]
|
90
|
+
# send_message :new, message_hash, :namespace => [:products,:glasses]
|
91
|
+
#
|
92
|
+
# See the {EventMap} documentation for more on mapping namespaced actions.
|
93
|
+
def send_message(event_name, message, options={})
|
94
|
+
event = Event.new( event_name, message, connection, options )
|
95
|
+
@_dispatcher.send_message event if @_dispatcher.respond_to?(:send_message)
|
81
96
|
end
|
82
97
|
|
83
98
|
# Broadcasts a message to all connected clients. See {#send_message} for message passing details.
|
84
|
-
def broadcast_message(
|
85
|
-
|
99
|
+
def broadcast_message(event_name, message, options={})
|
100
|
+
event = Event.new( event_name, message, connection, options )
|
101
|
+
@_dispatcher.broadcast_message event if @_dispatcher.respond_to?(:broadcast_message)
|
86
102
|
end
|
87
103
|
|
88
104
|
# Provides access to the {DataStore} for the current controller. The {DataStore} provides convenience
|
@@ -4,35 +4,52 @@ module WebsocketRails
|
|
4
4
|
attr_reader :adapters
|
5
5
|
module_function :adapters
|
6
6
|
|
7
|
-
def self.
|
7
|
+
def self.register(adapter)
|
8
8
|
@adapters ||= []
|
9
9
|
@adapters.unshift adapter
|
10
10
|
end
|
11
11
|
|
12
|
-
def self.establish_connection(env)
|
13
|
-
adapter = adapters.detect { |a| a.accepts?( env ) } ||
|
14
|
-
adapter.new
|
12
|
+
def self.establish_connection(env,dispatcher)
|
13
|
+
adapter = adapters.detect { |a| a.accepts?( env ) } || (raise InvalidConnectionError)
|
14
|
+
adapter.new env, dispatcher
|
15
15
|
end
|
16
16
|
|
17
17
|
class Base
|
18
|
-
|
19
|
-
|
18
|
+
|
19
|
+
def self.accepts?(env)
|
20
|
+
false
|
21
|
+
end
|
20
22
|
|
21
23
|
def self.inherited(adapter)
|
22
|
-
ConnectionAdapters.
|
24
|
+
ConnectionAdapters.register adapter
|
23
25
|
end
|
24
26
|
|
25
|
-
|
27
|
+
attr_accessor :dispatcher
|
28
|
+
|
29
|
+
def initialize(env,dispatcher)
|
26
30
|
@env = env
|
31
|
+
@dispatcher = dispatcher
|
27
32
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
|
34
|
+
def on_open(data=nil)
|
35
|
+
event = Event.new_on_open( self, data )
|
36
|
+
dispatch event
|
37
|
+
send event.serialize
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_message(encoded_data)
|
41
|
+
dispatch Event.new_from_json( encoded_data, self )
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_close(data=nil)
|
45
|
+
dispatch Event.new_on_close( self, data )
|
46
|
+
close_connection
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_error(data=nil)
|
50
|
+
event = Event.new_on_error( self, data )
|
51
|
+
dispatch event
|
52
|
+
on_close event.data
|
36
53
|
end
|
37
54
|
|
38
55
|
def send(message)
|
@@ -46,6 +63,16 @@ module WebsocketRails
|
|
46
63
|
def id
|
47
64
|
object_id.to_i
|
48
65
|
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def dispatch(event)
|
70
|
+
dispatcher.dispatch( event )
|
71
|
+
end
|
72
|
+
|
73
|
+
def close_connection
|
74
|
+
dispatcher.connection_manager.close_connection self
|
75
|
+
end
|
49
76
|
end
|
50
77
|
|
51
78
|
end
|
@@ -3,6 +3,10 @@ module WebsocketRails
|
|
3
3
|
class Http < Base
|
4
4
|
TERM = "\r\n".freeze
|
5
5
|
TAIL = "0#{TERM}#{TERM}".freeze
|
6
|
+
HttpHeaders = {
|
7
|
+
'Content-Type' => 'text/json',
|
8
|
+
'Transfer-Encoding' => 'chunked'
|
9
|
+
}
|
6
10
|
|
7
11
|
def self.accepts?(env)
|
8
12
|
true
|
@@ -10,15 +14,17 @@ module WebsocketRails
|
|
10
14
|
|
11
15
|
attr_accessor :headers
|
12
16
|
|
13
|
-
def initialize(env)
|
17
|
+
def initialize(env,dispatcher)
|
14
18
|
super
|
15
19
|
@body = DeferrableBody.new
|
16
|
-
@headers =
|
17
|
-
|
18
|
-
@headers['Transfer-Encoding'] = 'chunked'
|
19
|
-
|
20
|
+
@headers = HttpHeaders
|
21
|
+
|
20
22
|
define_deferrable_callbacks
|
21
|
-
|
23
|
+
|
24
|
+
EM.next_tick do
|
25
|
+
@env['async.callback'].call [200, @headers, @body]
|
26
|
+
on_open
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
30
|
def send(message)
|
@@ -29,10 +35,10 @@ module WebsocketRails
|
|
29
35
|
|
30
36
|
def define_deferrable_callbacks
|
31
37
|
@body.callback do |event|
|
32
|
-
|
38
|
+
on_close(event)
|
33
39
|
end
|
34
40
|
@body.errback do |event|
|
35
|
-
|
41
|
+
on_close(event)
|
36
42
|
end
|
37
43
|
end
|
38
44
|
|
@@ -45,8 +51,6 @@ module WebsocketRails
|
|
45
51
|
size = Rack::Utils.bytesize(c)
|
46
52
|
return nil if size == 0
|
47
53
|
c.dup.force_encoding(Encoding::BINARY) if c.respond_to?(:force_encoding)
|
48
|
-
puts "Chunking:: #{c}"
|
49
|
-
puts "Chunking:: #{size.to_s(16)}#{TERM}#{c}#{TERM}"
|
50
54
|
[size.to_s(16), TERM, c, TERM].join
|
51
55
|
end
|
52
56
|
|
@@ -2,27 +2,28 @@ module WebsocketRails
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
class WebSocket < Base
|
4
4
|
|
5
|
-
extend Forwardable
|
6
|
-
|
7
5
|
def self.accepts?(env)
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.delegated_methods
|
12
|
-
setter_methods = ADAPTER_EVENTS.map {|e| "#{e}=".to_sym }
|
13
|
-
setter_methods + ADAPTER_EVENTS
|
6
|
+
Faye::WebSocket.websocket?( env )
|
14
7
|
end
|
15
|
-
def_delegators :@connection, *delegated_methods
|
16
8
|
|
17
|
-
def initialize(env)
|
9
|
+
def initialize(env,dispatcher)
|
18
10
|
super
|
19
|
-
@connection =
|
11
|
+
@connection = Faye::WebSocket.new( env )
|
12
|
+
@connection.onmessage = method(:on_message)
|
13
|
+
@connection.onerror = method(:on_error)
|
14
|
+
@connection.onclose = method(:on_close)
|
15
|
+
on_open
|
20
16
|
end
|
21
17
|
|
22
18
|
def send(message)
|
23
19
|
@connection.send message
|
24
20
|
end
|
21
|
+
|
22
|
+
def on_message(event)
|
23
|
+
data = event.respond_to?(:data) ? event.data : event
|
24
|
+
super data
|
25
|
+
end
|
25
26
|
|
26
27
|
end
|
27
28
|
end
|
28
|
-
end
|
29
|
+
end
|