hkroger-websocket-rails 0.7.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +328 -0
- data/Gemfile +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +237 -0
- data/Rakefile +72 -0
- data/bin/thin-socketrails +45 -0
- data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
- data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
- data/lib/assets/javascripts/websocket_rails/event.js.coffee +42 -0
- data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
- data/lib/assets/javascripts/websocket_rails/main.js +6 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
- data/lib/config.ru +3 -0
- data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
- data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
- data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +63 -0
- data/lib/hkroger-websocket-rails.rb +1 -0
- data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
- data/lib/rails/config/routes.rb +7 -0
- data/lib/rails/tasks/websocket_rails.tasks +42 -0
- data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
- data/lib/spec_helpers/matchers/trigger_matchers.rb +113 -0
- data/lib/spec_helpers/spec_helper_event.rb +34 -0
- data/lib/websocket-rails.rb +108 -0
- data/lib/websocket_rails/base_controller.rb +197 -0
- data/lib/websocket_rails/channel.rb +97 -0
- data/lib/websocket_rails/channel_manager.rb +55 -0
- data/lib/websocket_rails/configuration.rb +169 -0
- data/lib/websocket_rails/connection_adapters.rb +195 -0
- data/lib/websocket_rails/connection_adapters/http.rb +120 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +36 -0
- data/lib/websocket_rails/connection_manager.rb +119 -0
- data/lib/websocket_rails/controller_factory.rb +80 -0
- data/lib/websocket_rails/data_store.rb +145 -0
- data/lib/websocket_rails/dispatcher.rb +129 -0
- data/lib/websocket_rails/engine.rb +26 -0
- data/lib/websocket_rails/event.rb +189 -0
- data/lib/websocket_rails/event_map.rb +184 -0
- data/lib/websocket_rails/event_queue.rb +33 -0
- data/lib/websocket_rails/internal_events.rb +37 -0
- data/lib/websocket_rails/logging.rb +133 -0
- data/lib/websocket_rails/spec_helpers.rb +3 -0
- data/lib/websocket_rails/synchronization.rb +182 -0
- data/lib/websocket_rails/user_manager.rb +276 -0
- data/lib/websocket_rails/version.rb +3 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/chat_controller.rb +53 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/events.rb +7 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +17 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +202 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/integration/connection_manager_spec.rb +135 -0
- data/spec/javascripts/support/jasmine.yml +52 -0
- data/spec/javascripts/support/jasmine_helper.rb +38 -0
- data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
- data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
- data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
- data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
- data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
- data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +274 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
- data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +247 -0
- data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
- data/spec/support/helper_methods.rb +42 -0
- data/spec/support/mock_web_socket.rb +41 -0
- data/spec/unit/base_controller_spec.rb +74 -0
- data/spec/unit/channel_manager_spec.rb +58 -0
- data/spec/unit/channel_spec.rb +169 -0
- data/spec/unit/connection_adapters/http_spec.rb +88 -0
- data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
- data/spec/unit/connection_adapters_spec.rb +259 -0
- data/spec/unit/connection_manager_spec.rb +148 -0
- data/spec/unit/controller_factory_spec.rb +76 -0
- data/spec/unit/data_store_spec.rb +106 -0
- data/spec/unit/dispatcher_spec.rb +203 -0
- data/spec/unit/event_map_spec.rb +120 -0
- data/spec/unit/event_queue_spec.rb +36 -0
- data/spec/unit/event_spec.rb +181 -0
- data/spec/unit/logging_spec.rb +162 -0
- data/spec/unit/synchronization_spec.rb +150 -0
- data/spec/unit/target_validator_spec.rb +88 -0
- data/spec/unit/user_manager_spec.rb +165 -0
- metadata +320 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
|
|
4
|
+
attr_reader :adapters
|
|
5
|
+
module_function :adapters
|
|
6
|
+
|
|
7
|
+
def self.register(adapter)
|
|
8
|
+
@adapters ||= []
|
|
9
|
+
@adapters.unshift adapter
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.establish_connection(request, dispatcher)
|
|
13
|
+
adapter = adapters.detect { |a| a.accepts?(request.env) } || raise(InvalidConnectionError)
|
|
14
|
+
adapter.new request, dispatcher
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Base
|
|
18
|
+
|
|
19
|
+
include Logging
|
|
20
|
+
|
|
21
|
+
def self.accepts?(env)
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.inherited(adapter)
|
|
26
|
+
ConnectionAdapters.register adapter
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :dispatcher, :queue, :env, :request, :data_store
|
|
30
|
+
|
|
31
|
+
# The ConnectionManager will set the connection ID when the
|
|
32
|
+
# connection is opened.
|
|
33
|
+
attr_accessor :id
|
|
34
|
+
|
|
35
|
+
def initialize(request, dispatcher)
|
|
36
|
+
@env = request.env.dup
|
|
37
|
+
@request = request
|
|
38
|
+
@dispatcher = dispatcher
|
|
39
|
+
@connected = true
|
|
40
|
+
@queue = EventQueue.new
|
|
41
|
+
@data_store = DataStore::Connection.new(self)
|
|
42
|
+
@delegate = WebsocketRails::DelegationController.new
|
|
43
|
+
@delegate.instance_variable_set(:@_env, request.env)
|
|
44
|
+
@delegate.instance_variable_set(:@_request, request)
|
|
45
|
+
|
|
46
|
+
start_ping_timer
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def on_open(data=nil)
|
|
50
|
+
event = Event.new_on_open( self, data )
|
|
51
|
+
dispatch event
|
|
52
|
+
trigger event
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def on_message(encoded_data)
|
|
56
|
+
event = Event.new_from_json( encoded_data, self )
|
|
57
|
+
dispatch event
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def on_close(data=nil)
|
|
61
|
+
@ping_timer.try(:cancel)
|
|
62
|
+
dispatch Event.new_on_close( self, data )
|
|
63
|
+
close_connection
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def on_error(data=nil)
|
|
67
|
+
event = Event.new_on_error( self, data )
|
|
68
|
+
dispatch event
|
|
69
|
+
on_close event.data
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def enqueue(event)
|
|
73
|
+
@queue << event
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def trigger(event)
|
|
77
|
+
send "[#{event.serialize}]"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def flush
|
|
81
|
+
message = []
|
|
82
|
+
@queue.flush do |event|
|
|
83
|
+
message << event.as_json
|
|
84
|
+
end
|
|
85
|
+
send message.to_json
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def send_message(event_name, data = {}, options = {})
|
|
89
|
+
options.merge! :user_id => user_identifier, :connection => self
|
|
90
|
+
options[:data] = data
|
|
91
|
+
|
|
92
|
+
event = Event.new(event_name, options)
|
|
93
|
+
event.trigger
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def send(message)
|
|
97
|
+
raise NotImplementedError, "Override this method in the connection specific adapter class"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def rack_response
|
|
101
|
+
[ -1, {}, [] ]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def controller_delegate
|
|
105
|
+
@delegate
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def connected?
|
|
109
|
+
true & @connected
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def inspect
|
|
113
|
+
"#<Connection::#{id}>"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def to_s
|
|
117
|
+
inspect
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def user_connection?
|
|
121
|
+
not user_identifier.nil?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def user
|
|
125
|
+
return unless user_connection?
|
|
126
|
+
controller_delegate.current_user
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def user_identifier
|
|
130
|
+
@user_identifier ||= begin
|
|
131
|
+
identifier = WebsocketRails.config.user_identifier
|
|
132
|
+
|
|
133
|
+
return unless current_user_responds_to?(identifier)
|
|
134
|
+
|
|
135
|
+
controller_delegate.current_user.send(identifier)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def ping_interval
|
|
140
|
+
@ping_interval ||= WebsocketRails.config.default_ping_interval
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def ping_interval=(interval)
|
|
144
|
+
@ping_interval = interval.to_i
|
|
145
|
+
@ping_timer.try(:cancel)
|
|
146
|
+
start_ping_timer
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def dispatch(event)
|
|
152
|
+
dispatcher.dispatch event
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def connection_manager
|
|
156
|
+
dispatcher.connection_manager
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def close_connection
|
|
160
|
+
@data_store.destroy!
|
|
161
|
+
@ping_timer.try(:cancel)
|
|
162
|
+
dispatcher.connection_manager.close_connection self
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def current_user_responds_to?(identifier)
|
|
166
|
+
controller_delegate &&
|
|
167
|
+
controller_delegate.respond_to?(:current_user) &&
|
|
168
|
+
controller_delegate.current_user &&
|
|
169
|
+
controller_delegate.current_user.respond_to?(identifier)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
attr_accessor :pong
|
|
173
|
+
public :pong, :pong=
|
|
174
|
+
|
|
175
|
+
def start_ping_timer
|
|
176
|
+
@pong = true
|
|
177
|
+
|
|
178
|
+
# Set negative interval to nil to deactivate periodic pings
|
|
179
|
+
if ping_interval > 0
|
|
180
|
+
@ping_timer = EM::PeriodicTimer.new(ping_interval) do
|
|
181
|
+
if pong == true
|
|
182
|
+
self.pong = false
|
|
183
|
+
ping = Event.new_on_ping self
|
|
184
|
+
trigger ping
|
|
185
|
+
else
|
|
186
|
+
@ping_timer.cancel
|
|
187
|
+
on_error
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
class Http < Base
|
|
4
|
+
TERM = "\r\n".freeze
|
|
5
|
+
TAIL = "0#{TERM}#{TERM}".freeze
|
|
6
|
+
HttpHeaders = {
|
|
7
|
+
'Content-Type' => 'text/json',
|
|
8
|
+
'Transfer-Encoding' => 'chunked'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def self.accepts?(env)
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_accessor :headers
|
|
16
|
+
|
|
17
|
+
def initialize(env,dispatcher)
|
|
18
|
+
super
|
|
19
|
+
@body = DeferrableBody.new
|
|
20
|
+
@headers = HttpHeaders
|
|
21
|
+
|
|
22
|
+
define_deferrable_callbacks
|
|
23
|
+
|
|
24
|
+
origin = "#{request.protocol}#{request.raw_host_with_port}"
|
|
25
|
+
@headers.merge!({'Access-Control-Allow-Origin' => origin}) if WebsocketRails.config.allowed_origins.include?(origin)
|
|
26
|
+
# IE < 10.0 hack
|
|
27
|
+
# XDomainRequest will not bubble up notifications of download progress in the first 2kb of the response
|
|
28
|
+
# http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx
|
|
29
|
+
@body.chunk(encode_chunk(" " * 2048))
|
|
30
|
+
|
|
31
|
+
EM.next_tick do
|
|
32
|
+
@env['async.callback'].call [200, @headers, @body]
|
|
33
|
+
on_open
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def send(message)
|
|
38
|
+
@body.chunk encode_chunk( message )
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def close!
|
|
42
|
+
@body.close!
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def define_deferrable_callbacks
|
|
48
|
+
@body.callback do |event|
|
|
49
|
+
on_close(event)
|
|
50
|
+
end
|
|
51
|
+
@body.errback do |event|
|
|
52
|
+
on_close(event)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# From [Rack::Stream](https://github.com/intridea/rack-stream)
|
|
57
|
+
def encode_chunk(c)
|
|
58
|
+
return nil if c.nil?
|
|
59
|
+
# hack to work with Rack::File for now, should not TE chunked
|
|
60
|
+
# things that aren't strings or respond to bytesize
|
|
61
|
+
c = ::File.read(c.path) if c.kind_of?(Rack::File)
|
|
62
|
+
size = Rack::Utils.bytesize(c)
|
|
63
|
+
return nil if size == 0
|
|
64
|
+
c.dup.force_encoding(Encoding::BINARY) if c.respond_to?(:force_encoding)
|
|
65
|
+
[size.to_s(16), TERM, c, TERM].join
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# From [thin_async](https://github.com/macournoyer/thin_async)
|
|
69
|
+
class DeferrableBody
|
|
70
|
+
include EM::Deferrable
|
|
71
|
+
|
|
72
|
+
# @param chunks - object that responds to each. holds initial chunks of content
|
|
73
|
+
def initialize(chunks = [])
|
|
74
|
+
@queue = []
|
|
75
|
+
chunks.each {|c| chunk(c)}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Enqueue a chunk of content to be flushed to stream at a later time
|
|
79
|
+
def chunk(*chunks)
|
|
80
|
+
@queue += chunks
|
|
81
|
+
schedule_dequeue
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# When rack attempts to iterate over `body`, save the block,
|
|
85
|
+
# and execute at a later time when `@queue` has elements
|
|
86
|
+
def each(&blk)
|
|
87
|
+
@body_callback = blk
|
|
88
|
+
schedule_dequeue
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def empty?
|
|
92
|
+
@queue.empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def close!(flush = true)
|
|
96
|
+
EM.next_tick {
|
|
97
|
+
if !flush || empty?
|
|
98
|
+
succeed
|
|
99
|
+
else
|
|
100
|
+
schedule_dequeue
|
|
101
|
+
close!(flush)
|
|
102
|
+
end
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def schedule_dequeue
|
|
109
|
+
return unless @body_callback
|
|
110
|
+
EM.next_tick do
|
|
111
|
+
next unless c = @queue.shift
|
|
112
|
+
@body_callback.call(c)
|
|
113
|
+
schedule_dequeue unless empty?
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
class WebSocket < Base
|
|
4
|
+
|
|
5
|
+
def self.accepts?(env)
|
|
6
|
+
Faye::WebSocket.websocket?( env )
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(request, dispatcher)
|
|
10
|
+
super
|
|
11
|
+
@connection = Faye::WebSocket.new(request.env)
|
|
12
|
+
@connection.onmessage = method(:on_message)
|
|
13
|
+
@connection.onerror = method(:on_error)
|
|
14
|
+
@connection.onclose = method(:on_close)
|
|
15
|
+
@connection.rack_response
|
|
16
|
+
EM.next_tick do
|
|
17
|
+
on_open
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def send(message)
|
|
22
|
+
@connection.send message
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def on_message(event)
|
|
26
|
+
data = event.respond_to?(:data) ? event.data : event
|
|
27
|
+
super data
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def close!
|
|
31
|
+
@connection.close
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require 'faye/websocket'
|
|
2
|
+
require 'rack'
|
|
3
|
+
require 'thin'
|
|
4
|
+
|
|
5
|
+
Faye::WebSocket.load_adapter('thin')
|
|
6
|
+
|
|
7
|
+
module WebsocketRails
|
|
8
|
+
# The +ConnectionManager+ class implements the core Rack application that handles
|
|
9
|
+
# incoming WebSocket connections.
|
|
10
|
+
class ConnectionManager
|
|
11
|
+
|
|
12
|
+
include Logging
|
|
13
|
+
|
|
14
|
+
SuccessfulResponse = [200,{'Content-Type' => 'text/plain'},['success']].freeze
|
|
15
|
+
BadRequestResponse = [400,{'Content-Type' => 'text/plain'},['invalid']].freeze
|
|
16
|
+
ExceptionResponse = [500,{'Content-Type' => 'text/plain'},['exception']].freeze
|
|
17
|
+
|
|
18
|
+
# Contains a Hash of currently open connections.
|
|
19
|
+
# @return [Hash]
|
|
20
|
+
attr_reader :connections
|
|
21
|
+
|
|
22
|
+
# Contains the {Dispatcher} instance for the active server.
|
|
23
|
+
# @return [Dispatcher]
|
|
24
|
+
attr_reader :dispatcher
|
|
25
|
+
|
|
26
|
+
# Contains the {Synchronization} instance for the active server.
|
|
27
|
+
# @return [Synchronization]
|
|
28
|
+
attr_reader :synchronization
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@connections = {}
|
|
32
|
+
@dispatcher = Dispatcher.new(self)
|
|
33
|
+
|
|
34
|
+
if WebsocketRails.synchronize?
|
|
35
|
+
EM.next_tick do
|
|
36
|
+
Fiber.new {
|
|
37
|
+
Synchronization.synchronize!
|
|
38
|
+
EM.add_shutdown_hook { Synchronization.shutdown! }
|
|
39
|
+
}.resume
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def inspect
|
|
45
|
+
"websocket_rails"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Primary entry point for the Rack application
|
|
49
|
+
def call(env)
|
|
50
|
+
request = ActionDispatch::Request.new(env)
|
|
51
|
+
|
|
52
|
+
if request.post?
|
|
53
|
+
response = parse_incoming_event(request.params)
|
|
54
|
+
else
|
|
55
|
+
response = open_connection(request)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
response
|
|
59
|
+
rescue InvalidConnectionError
|
|
60
|
+
BadRequestResponse
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def parse_incoming_event(params)
|
|
66
|
+
connection = find_connection_by_id(params["client_id"])
|
|
67
|
+
connection.on_message params["data"]
|
|
68
|
+
SuccessfulResponse
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def find_connection_by_id(id)
|
|
72
|
+
connections[id] || raise(InvalidConnectionError)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Opens a persistent connection using the appropriate {ConnectionAdapter}. Stores
|
|
76
|
+
# active connections in the {connections} Hash.
|
|
77
|
+
def open_connection(request)
|
|
78
|
+
connection = ConnectionAdapters.establish_connection(request, dispatcher)
|
|
79
|
+
|
|
80
|
+
assign_connection_id connection
|
|
81
|
+
register_user_connection connection
|
|
82
|
+
|
|
83
|
+
connections[connection.id] = connection
|
|
84
|
+
|
|
85
|
+
info "Connection opened: #{connection}"
|
|
86
|
+
connection.rack_response
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def close_connection(connection)
|
|
90
|
+
WebsocketRails.channel_manager.unsubscribe connection
|
|
91
|
+
destroy_user_connection connection
|
|
92
|
+
|
|
93
|
+
connections.delete connection.id
|
|
94
|
+
|
|
95
|
+
info "Connection closed: #{connection}"
|
|
96
|
+
connection = nil
|
|
97
|
+
end
|
|
98
|
+
public :close_connection
|
|
99
|
+
|
|
100
|
+
def assign_connection_id(connection)
|
|
101
|
+
begin
|
|
102
|
+
id = SecureRandom.hex(10)
|
|
103
|
+
end while connections.has_key?(id)
|
|
104
|
+
|
|
105
|
+
connection.id = id
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def register_user_connection(connection)
|
|
109
|
+
return unless connection.user_connection?
|
|
110
|
+
WebsocketRails.users[connection.user_identifier] = connection
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def destroy_user_connection(connection)
|
|
114
|
+
return unless connection.user_connection?
|
|
115
|
+
WebsocketRails.users.delete(connection)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
end
|