websocket-rails 0.1.8 → 0.1.9
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 +15 -2
- data/Gemfile +3 -1
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +5 -0
- data/lib/websocket-rails.rb +31 -5
- data/lib/websocket_rails/channel.rb +17 -7
- data/lib/websocket_rails/channel_manager.rb +1 -1
- data/lib/websocket_rails/connection_adapters.rb +10 -9
- data/lib/websocket_rails/connection_adapters/http.rb +8 -8
- data/lib/websocket_rails/connection_adapters/web_socket.rb +4 -4
- data/lib/websocket_rails/connection_manager.rb +25 -12
- data/lib/websocket_rails/data_store.rb +10 -10
- data/lib/websocket_rails/dispatcher.rb +12 -9
- data/lib/websocket_rails/event.rb +23 -11
- data/lib/websocket_rails/event_map.rb +8 -8
- data/lib/websocket_rails/logging.rb +18 -2
- data/lib/websocket_rails/synchronization.rb +92 -0
- data/lib/websocket_rails/version.rb +1 -1
- data/spec/dummy/log/test.log +300 -0
- data/spec/integration/connection_manager_spec.rb +15 -15
- data/spec/javascripts/generated/assets/http_connection.js +1 -1
- data/spec/javascripts/generated/assets/websocket_connection.js +9 -0
- data/spec/unit/channel_spec.rb +3 -3
- data/spec/unit/connection_manager_spec.rb +25 -15
- data/spec/unit/dispatcher_spec.rb +21 -9
- data/spec/unit/event_spec.rb +11 -1
- data/spec/unit/logging_spec.rb +38 -0
- data/spec/unit/synchronization_spec.rb +68 -0
- metadata +56 -5
data/CHANGELOG.md
CHANGED
@@ -1,15 +1,28 @@
|
|
1
1
|
# WebsocketRails Change Log
|
2
2
|
|
3
|
+
## Version 0.1.9
|
4
|
+
|
5
|
+
November 19 2012
|
6
|
+
|
7
|
+
* Fix bug that crashed the server when receiving badly formed messages
|
8
|
+
through an open websocket. Fixes issue #27.
|
9
|
+
|
10
|
+
* Add support for communication between multiple server instances and
|
11
|
+
background jobs. Solves scaling problems discussed in issue #21.
|
12
|
+
|
13
|
+
* Fixed client_disconnected event firing twice - Thanks to
|
14
|
+
@nickdesaulniers
|
15
|
+
|
3
16
|
## Version 0.1.8
|
4
17
|
|
5
|
-
July 18
|
18
|
+
July 18 2012
|
6
19
|
|
7
20
|
* Fix bug in Channel#trigger preventing the data from coming through
|
8
21
|
properly.
|
9
22
|
|
10
23
|
## Version 0.1.7
|
11
24
|
|
12
|
-
July 17
|
25
|
+
July 17 2012
|
13
26
|
|
14
27
|
* Fixed botched release of 0.1.6
|
15
28
|
* Reorganized directory structure
|
data/Gemfile
CHANGED
@@ -3,11 +3,13 @@ source "http://rubygems.org"
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
gem "rspec-rails"
|
6
|
+
gem "therubyrhino"
|
7
|
+
gem "therubyracer"
|
6
8
|
gem "jasmine"
|
7
9
|
gem "headless"
|
8
10
|
gem "coffee-script"
|
9
11
|
gem "thin"
|
10
|
-
gem "eventmachine"
|
12
|
+
gem "eventmachine"
|
11
13
|
gem "faye-websocket"
|
12
14
|
gem "simplecov"
|
13
15
|
gem "ruby_gntp"
|
@@ -9,6 +9,7 @@ class WebSocketRails.WebSocketConnection
|
|
9
9
|
@_conn = new WebSocket(@url)
|
10
10
|
@_conn.onmessage = @on_message
|
11
11
|
@_conn.onclose = @on_close
|
12
|
+
@_conn.onerror = @on_error
|
12
13
|
|
13
14
|
trigger: (event) =>
|
14
15
|
if @dispatcher.state != 'connected'
|
@@ -24,6 +25,10 @@ class WebSocketRails.WebSocketConnection
|
|
24
25
|
close_event = new WebSocketRails.Event(['connection_closed',{}])
|
25
26
|
@dispatcher.dispatch close_event
|
26
27
|
|
28
|
+
on_error: (event) =>
|
29
|
+
error_event = new WebSocketRails.Event(['connection_error',event?.data])
|
30
|
+
@dispatcher.dispatch error_event
|
31
|
+
|
27
32
|
flush_queue: =>
|
28
33
|
for event in @message_queue
|
29
34
|
@_conn.send event.serialize()
|
data/lib/websocket-rails.rb
CHANGED
@@ -3,24 +3,50 @@ require 'thin'
|
|
3
3
|
|
4
4
|
module WebsocketRails
|
5
5
|
mattr_accessor :app_root
|
6
|
-
|
6
|
+
|
7
7
|
def self.setup
|
8
8
|
yield self
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def self.route_block=(routes)
|
12
12
|
@event_routes = routes
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def self.route_block
|
16
16
|
@event_routes
|
17
17
|
end
|
18
|
-
end
|
19
18
|
|
20
|
-
|
19
|
+
def self.log_level
|
20
|
+
@log_level ||= :warn
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.log_level=(level)
|
24
|
+
@log_level = level
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_accessor :synchronize
|
28
|
+
module_function :synchronize, :synchronize=
|
29
|
+
|
30
|
+
def self.synchronize?
|
31
|
+
@synchronize == true
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.redis_options
|
35
|
+
@redis_options ||= redis_defaults
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.redis_options=(options = {})
|
39
|
+
@redis_options = redis_defaults.merge(options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.redis_defaults
|
43
|
+
{:host => '127.0.0.1', :port => 6379}
|
44
|
+
end
|
45
|
+
end
|
21
46
|
|
22
47
|
require 'websocket_rails/engine'
|
23
48
|
require 'websocket_rails/logging'
|
49
|
+
require 'websocket_rails/synchronization'
|
24
50
|
require 'websocket_rails/connection_manager'
|
25
51
|
require 'websocket_rails/dispatcher'
|
26
52
|
require 'websocket_rails/event'
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module WebsocketRails
|
2
2
|
class Channel
|
3
3
|
|
4
|
+
include Logging
|
5
|
+
|
4
6
|
attr_reader :name, :subscribers
|
5
7
|
|
6
8
|
def initialize(channel_name)
|
@@ -10,30 +12,38 @@ module WebsocketRails
|
|
10
12
|
end
|
11
13
|
|
12
14
|
def subscribe(connection)
|
15
|
+
log "#{connection} subscribed to channel #{name}"
|
13
16
|
@subscribers << connection
|
14
17
|
end
|
15
18
|
|
16
|
-
def trigger(event_name,data={})
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
def trigger(event_name,data={},options={})
|
20
|
+
options.merge! :channel => name
|
21
|
+
options[:data] = data
|
22
|
+
|
23
|
+
event = Event.new event_name, options
|
24
|
+
|
25
|
+
send_data event
|
20
26
|
end
|
21
27
|
|
22
28
|
def trigger_event(event)
|
23
29
|
send_data event
|
24
30
|
end
|
25
|
-
|
31
|
+
|
26
32
|
def make_private
|
27
33
|
@private = true
|
28
34
|
end
|
29
|
-
|
35
|
+
|
30
36
|
def is_private?
|
31
37
|
@private
|
32
38
|
end
|
33
|
-
|
39
|
+
|
34
40
|
private
|
35
41
|
|
36
42
|
def send_data(event)
|
43
|
+
if WebsocketRails.synchronize? && event.server_token.nil?
|
44
|
+
Synchronization.publish event
|
45
|
+
end
|
46
|
+
|
37
47
|
subscribers.each do |subscriber|
|
38
48
|
subscriber.trigger event
|
39
49
|
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
module WebsocketRails
|
2
2
|
module ConnectionAdapters
|
3
|
-
|
3
|
+
|
4
4
|
attr_reader :adapters
|
5
5
|
module_function :adapters
|
6
|
-
|
6
|
+
|
7
7
|
def self.register(adapter)
|
8
8
|
@adapters ||= []
|
9
9
|
@adapters.unshift adapter
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def self.establish_connection(request,dispatcher)
|
13
13
|
adapter = adapters.detect { |a| a.accepts?( request.env ) } || (raise InvalidConnectionError)
|
14
14
|
adapter.new request, dispatcher
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
class Base
|
18
18
|
|
19
19
|
include Logging
|
@@ -21,11 +21,11 @@ module WebsocketRails
|
|
21
21
|
def self.accepts?(env)
|
22
22
|
false
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def self.inherited(adapter)
|
26
26
|
ConnectionAdapters.register adapter
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
attr_reader :dispatcher, :queue, :env, :request
|
30
30
|
|
31
31
|
def initialize(request,dispatcher)
|
@@ -52,6 +52,7 @@ module WebsocketRails
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def on_close(data=nil)
|
55
|
+
@ping_timer.cancel
|
55
56
|
dispatch Event.new_on_close( self, data )
|
56
57
|
close_connection
|
57
58
|
end
|
@@ -93,11 +94,11 @@ module WebsocketRails
|
|
93
94
|
def send(message)
|
94
95
|
raise NotImplementedError, "Override this method in the connection specific adapter class"
|
95
96
|
end
|
96
|
-
|
97
|
+
|
97
98
|
def rack_response
|
98
99
|
[ -1, {}, [] ]
|
99
100
|
end
|
100
|
-
|
101
|
+
|
101
102
|
def id
|
102
103
|
object_id.to_i
|
103
104
|
end
|
@@ -135,6 +136,6 @@ module WebsocketRails
|
|
135
136
|
end
|
136
137
|
|
137
138
|
end
|
138
|
-
|
139
|
+
|
139
140
|
end
|
140
141
|
end
|
@@ -7,18 +7,18 @@ module WebsocketRails
|
|
7
7
|
'Content-Type' => 'text/json',
|
8
8
|
'Transfer-Encoding' => 'chunked'
|
9
9
|
}
|
10
|
-
|
10
|
+
|
11
11
|
def self.accepts?(env)
|
12
12
|
true
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
attr_accessor :headers
|
16
16
|
|
17
17
|
def initialize(env,dispatcher)
|
18
18
|
super
|
19
19
|
@body = DeferrableBody.new
|
20
20
|
@headers = HttpHeaders
|
21
|
-
|
21
|
+
|
22
22
|
define_deferrable_callbacks
|
23
23
|
|
24
24
|
EM.next_tick do
|
@@ -30,9 +30,9 @@ module WebsocketRails
|
|
30
30
|
def send(message)
|
31
31
|
@body.chunk encode_chunk( message )
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
private
|
35
|
-
|
35
|
+
|
36
36
|
def define_deferrable_callbacks
|
37
37
|
@body.callback do |event|
|
38
38
|
on_close(event)
|
@@ -41,7 +41,7 @@ module WebsocketRails
|
|
41
41
|
on_close(event)
|
42
42
|
end
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
# From [Rack::Stream](https://github.com/intridea/rack-stream)
|
46
46
|
def encode_chunk(c)
|
47
47
|
return nil if c.nil?
|
@@ -53,7 +53,7 @@ module WebsocketRails
|
|
53
53
|
c.dup.force_encoding(Encoding::BINARY) if c.respond_to?(:force_encoding)
|
54
54
|
[size.to_s(16), TERM, c, TERM].join
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
# From [thin_async](https://github.com/macournoyer/thin_async)
|
58
58
|
class DeferrableBody
|
59
59
|
include EM::Deferrable
|
@@ -103,7 +103,7 @@ module WebsocketRails
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
end
|
108
108
|
end
|
109
109
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module WebsocketRails
|
2
2
|
module ConnectionAdapters
|
3
3
|
class WebSocket < Base
|
4
|
-
|
4
|
+
|
5
5
|
def self.accepts?(env)
|
6
6
|
Faye::WebSocket.websocket?( env )
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def initialize(request,dispatcher)
|
10
10
|
super
|
11
11
|
@connection = Faye::WebSocket.new( request.env )
|
@@ -14,7 +14,7 @@ module WebsocketRails
|
|
14
14
|
@connection.onclose = method(:on_close)
|
15
15
|
on_open
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def send(message)
|
19
19
|
@connection.send message
|
20
20
|
end
|
@@ -23,7 +23,7 @@ module WebsocketRails
|
|
23
23
|
data = event.respond_to?(:data) ? event.data : event
|
24
24
|
super data
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -12,35 +12,48 @@ module WebsocketRails
|
|
12
12
|
SuccessfulResponse = [200,{'Content-Type' => 'text/plain'},['success']].freeze
|
13
13
|
BadRequestResponse = [400,{'Content-Type' => 'text/plain'},['invalid']].freeze
|
14
14
|
ExceptionResponse = [500,{'Content-Type' => 'text/plain'},['exception']].freeze
|
15
|
-
|
15
|
+
|
16
16
|
# Contains an Array of currently open connections.
|
17
17
|
# @return [Array]
|
18
18
|
attr_reader :connections
|
19
|
-
|
19
|
+
|
20
20
|
# Contains the {Dispatcher} instance for the active server.
|
21
21
|
# @return [Dispatcher]
|
22
22
|
attr_reader :dispatcher
|
23
|
-
|
23
|
+
|
24
|
+
# Contains the {Synchronization} instance for the active server.
|
25
|
+
# @return [Synchronization]
|
26
|
+
attr_reader :synchronization
|
27
|
+
|
24
28
|
def initialize
|
25
29
|
@connections = []
|
26
|
-
@dispatcher = Dispatcher.new(
|
30
|
+
@dispatcher = Dispatcher.new(self)
|
31
|
+
|
32
|
+
if WebsocketRails.synchronize?
|
33
|
+
EM.next_tick do
|
34
|
+
Fiber.new {
|
35
|
+
Synchronization.synchronize!
|
36
|
+
EM.add_shutdown_hook { Synchronization.shutdown! }
|
37
|
+
}.resume
|
38
|
+
end
|
39
|
+
end
|
27
40
|
end
|
28
|
-
|
41
|
+
|
29
42
|
# Primary entry point for the Rack application
|
30
43
|
def call(env)
|
31
|
-
request = ActionDispatch::Request.new
|
44
|
+
request = ActionDispatch::Request.new(env)
|
32
45
|
|
33
46
|
if request.post?
|
34
|
-
response = parse_incoming_event
|
47
|
+
response = parse_incoming_event(request.params)
|
35
48
|
else
|
36
|
-
response = open_connection
|
49
|
+
response = open_connection(request)
|
37
50
|
end
|
38
|
-
|
51
|
+
|
39
52
|
response
|
40
53
|
rescue InvalidConnectionError
|
41
54
|
BadRequestResponse
|
42
55
|
end
|
43
|
-
|
56
|
+
|
44
57
|
private
|
45
58
|
|
46
59
|
def parse_incoming_event(params)
|
@@ -52,7 +65,7 @@ module WebsocketRails
|
|
52
65
|
def find_connection_by_id(id)
|
53
66
|
connections.detect { |connection| connection.id == id.to_i } || (raise InvalidConnectionError)
|
54
67
|
end
|
55
|
-
|
68
|
+
|
56
69
|
# Opens a persistent connection using the appropriate {ConnectionAdapter}. Stores
|
57
70
|
# active connections in the {connections} array.
|
58
71
|
def open_connection(request)
|
@@ -68,6 +81,6 @@ module WebsocketRails
|
|
68
81
|
connection = nil
|
69
82
|
end
|
70
83
|
public :close_connection
|
71
|
-
|
84
|
+
|
72
85
|
end
|
73
86
|
end
|
@@ -4,16 +4,16 @@ module WebsocketRails
|
|
4
4
|
# variables defined in actions will be shared between clients. The {DataStore} provides a Hash
|
5
5
|
# that is private for each connected client. It is accessed through a WebsocketRails controller
|
6
6
|
# using the {BaseController.data_store} instance method.
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# = Example Usage
|
9
9
|
# == Creating a user
|
10
10
|
# # action on ChatController called by :client_connected event
|
11
11
|
# def new_user
|
12
12
|
# # This would be overwritten when the next user joins
|
13
13
|
# @user = User.new( message[:user_name] )
|
14
|
-
#
|
14
|
+
#
|
15
15
|
# # This will remain private for each user
|
16
|
-
# data_store[:user] = User.new( message[:user_name] )
|
16
|
+
# data_store[:user] = User.new( message[:user_name] )
|
17
17
|
# end
|
18
18
|
#
|
19
19
|
# == Collecting all Users from the DataStore
|
@@ -29,11 +29,11 @@ module WebsocketRails
|
|
29
29
|
# data_store.each_user
|
30
30
|
# => [UserOne,UserTwo,UserThree]
|
31
31
|
class DataStore
|
32
|
-
|
32
|
+
|
33
33
|
extend Forwardable
|
34
|
-
|
34
|
+
|
35
35
|
def_delegator :@base, :client_id, :cid
|
36
|
-
|
36
|
+
|
37
37
|
def initialize(base_controller)
|
38
38
|
@base = base_controller
|
39
39
|
@data = Hash.new {|h,k| h[k] = Hash.new}
|
@@ -55,15 +55,15 @@ module WebsocketRails
|
|
55
55
|
block.call(hash) if block
|
56
56
|
end
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
def remove_client
|
60
60
|
@data.delete(cid)
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
def delete(key)
|
64
64
|
@data[cid].delete(key)
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def method_missing(method, *args, &block)
|
68
68
|
if /each_(?<hash_key>\w*)/ =~ method
|
69
69
|
results = []
|
@@ -76,4 +76,4 @@ module WebsocketRails
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
79
|
-
end
|
79
|
+
end
|