websocket-rails 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +88 -63
- data/Guardfile +9 -0
- data/README.md +25 -8
- data/assets/javascripts/http_dispatcher.js +77 -0
- data/assets/javascripts/websocket_dispatcher.js +65 -0
- data/lib/websocket-rails.rb +4 -0
- data/lib/websocket_rails/base_controller.rb +4 -4
- data/lib/websocket_rails/connection_adapters.rb +52 -0
- data/lib/websocket_rails/connection_adapters/http.rb +105 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +28 -0
- data/lib/websocket_rails/connection_manager.rb +49 -22
- data/lib/websocket_rails/dispatcher.rb +7 -7
- data/lib/websocket_rails/version.rb +2 -2
- data/spec/dummy/app/controllers/chat_controller.rb +4 -0
- data/spec/integration/connection_manager_spec.rb +74 -21
- data/spec/spec_helper.rb +0 -1
- data/spec/support/mock_web_socket.rb +2 -0
- data/spec/unit/connection_adapters/http_spec.rb +62 -0
- data/spec/unit/connection_adapters/web_socket_spec.rb +33 -0
- data/spec/unit/connection_adapters_spec.rb +61 -0
- data/spec/unit/connection_manager_spec.rb +26 -11
- data/spec/unit/dispatcher_spec.rb +1 -0
- metadata +13 -5
- data/spec/dummy/log/test.log +0 -0
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
websocket-rails (0.1.
|
4
|
+
websocket-rails (0.1.2)
|
5
5
|
faye-websocket
|
6
6
|
rack
|
7
7
|
thin
|
@@ -9,101 +9,124 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: http://rubygems.org/
|
11
11
|
specs:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
rack (~> 1.2
|
23
|
-
rack-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
arel (
|
40
|
-
builder (
|
12
|
+
actionmailer (3.2.5)
|
13
|
+
actionpack (= 3.2.5)
|
14
|
+
mail (~> 2.4.4)
|
15
|
+
actionpack (3.2.5)
|
16
|
+
activemodel (= 3.2.5)
|
17
|
+
activesupport (= 3.2.5)
|
18
|
+
builder (~> 3.0.0)
|
19
|
+
erubis (~> 2.7.0)
|
20
|
+
journey (~> 1.0.1)
|
21
|
+
rack (~> 1.4.0)
|
22
|
+
rack-cache (~> 1.2)
|
23
|
+
rack-test (~> 0.6.1)
|
24
|
+
sprockets (~> 2.1.3)
|
25
|
+
activemodel (3.2.5)
|
26
|
+
activesupport (= 3.2.5)
|
27
|
+
builder (~> 3.0.0)
|
28
|
+
activerecord (3.2.5)
|
29
|
+
activemodel (= 3.2.5)
|
30
|
+
activesupport (= 3.2.5)
|
31
|
+
arel (~> 3.0.2)
|
32
|
+
tzinfo (~> 0.3.29)
|
33
|
+
activeresource (3.2.5)
|
34
|
+
activemodel (= 3.2.5)
|
35
|
+
activesupport (= 3.2.5)
|
36
|
+
activesupport (3.2.5)
|
37
|
+
i18n (~> 0.6)
|
38
|
+
multi_json (~> 1.0)
|
39
|
+
arel (3.0.2)
|
40
|
+
builder (3.0.0)
|
41
41
|
daemons (1.1.8)
|
42
42
|
diff-lcs (1.1.3)
|
43
|
-
erubis (2.
|
44
|
-
abstract (>= 1.0.0)
|
43
|
+
erubis (2.7.0)
|
45
44
|
eventmachine (1.0.0.beta.4)
|
46
45
|
faye-websocket (0.4.5)
|
47
46
|
eventmachine (>= 0.12.0)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
ffi (1.0.11)
|
48
|
+
guard (1.1.1)
|
49
|
+
listen (>= 0.4.2)
|
50
|
+
thor (>= 0.14.6)
|
51
|
+
guard-rspec (1.0.0)
|
52
|
+
guard (>= 1.1)
|
53
|
+
hike (1.2.1)
|
54
|
+
i18n (0.6.0)
|
55
|
+
journey (1.0.3)
|
56
|
+
json (1.7.3)
|
57
|
+
listen (0.4.3)
|
58
|
+
rb-fchange (~> 0.0.5)
|
59
|
+
rb-fsevent (~> 0.9.1)
|
60
|
+
rb-inotify (~> 0.8.8)
|
61
|
+
mail (2.4.4)
|
52
62
|
i18n (>= 0.4.0)
|
53
63
|
mime-types (~> 1.16)
|
54
64
|
treetop (~> 1.4.8)
|
55
65
|
mime-types (1.18)
|
56
66
|
multi_json (1.3.6)
|
57
67
|
polyglot (0.3.3)
|
58
|
-
rack (1.
|
59
|
-
rack-
|
60
|
-
rack (>=
|
61
|
-
rack-
|
68
|
+
rack (1.4.1)
|
69
|
+
rack-cache (1.2)
|
70
|
+
rack (>= 0.4)
|
71
|
+
rack-ssl (1.3.2)
|
72
|
+
rack
|
73
|
+
rack-test (0.6.1)
|
62
74
|
rack (>= 1.0)
|
63
|
-
rails (3.
|
64
|
-
actionmailer (= 3.
|
65
|
-
actionpack (= 3.
|
66
|
-
activerecord (= 3.
|
67
|
-
activeresource (= 3.
|
68
|
-
activesupport (= 3.
|
75
|
+
rails (3.2.5)
|
76
|
+
actionmailer (= 3.2.5)
|
77
|
+
actionpack (= 3.2.5)
|
78
|
+
activerecord (= 3.2.5)
|
79
|
+
activeresource (= 3.2.5)
|
80
|
+
activesupport (= 3.2.5)
|
69
81
|
bundler (~> 1.0)
|
70
|
-
railties (= 3.
|
71
|
-
railties (3.
|
72
|
-
actionpack (= 3.
|
73
|
-
activesupport (= 3.
|
82
|
+
railties (= 3.2.5)
|
83
|
+
railties (3.2.5)
|
84
|
+
actionpack (= 3.2.5)
|
85
|
+
activesupport (= 3.2.5)
|
86
|
+
rack-ssl (~> 1.3.2)
|
74
87
|
rake (>= 0.8.7)
|
75
88
|
rdoc (~> 3.4)
|
76
|
-
thor (
|
89
|
+
thor (>= 0.14.6, < 2.0)
|
77
90
|
rake (0.9.2.2)
|
91
|
+
rb-fchange (0.0.5)
|
92
|
+
ffi
|
93
|
+
rb-fsevent (0.9.1)
|
94
|
+
rb-inotify (0.8.8)
|
95
|
+
ffi (>= 0.5.0)
|
78
96
|
rdoc (3.12)
|
79
97
|
json (~> 1.4)
|
80
|
-
rspec (2.
|
81
|
-
rspec-core (~> 2.
|
82
|
-
rspec-expectations (~> 2.
|
83
|
-
rspec-mocks (~> 2.
|
84
|
-
rspec-core (2.
|
85
|
-
rspec-expectations (2.
|
98
|
+
rspec (2.10.0)
|
99
|
+
rspec-core (~> 2.10.0)
|
100
|
+
rspec-expectations (~> 2.10.0)
|
101
|
+
rspec-mocks (~> 2.10.0)
|
102
|
+
rspec-core (2.10.1)
|
103
|
+
rspec-expectations (2.10.0)
|
86
104
|
diff-lcs (~> 1.1.3)
|
87
|
-
rspec-mocks (2.
|
88
|
-
rspec-rails (2.
|
105
|
+
rspec-mocks (2.10.1)
|
106
|
+
rspec-rails (2.10.1)
|
89
107
|
actionpack (>= 3.0)
|
90
108
|
activesupport (>= 3.0)
|
91
109
|
railties (>= 3.0)
|
92
|
-
rspec (~> 2.
|
110
|
+
rspec (~> 2.10.0)
|
93
111
|
simplecov (0.6.4)
|
94
112
|
multi_json (~> 1.0)
|
95
113
|
simplecov-html (~> 0.5.3)
|
96
114
|
simplecov-html (0.5.3)
|
97
|
-
|
115
|
+
sprockets (2.1.3)
|
116
|
+
hike (~> 1.2)
|
117
|
+
rack (~> 1.0)
|
118
|
+
tilt (~> 1.1, != 1.3.0)
|
119
|
+
sqlite3 (1.3.6)
|
98
120
|
thin (1.3.1)
|
99
121
|
daemons (>= 1.0.9)
|
100
122
|
eventmachine (>= 0.12.6)
|
101
123
|
rack (>= 1.0.0)
|
102
|
-
thor (0.
|
124
|
+
thor (0.15.2)
|
125
|
+
tilt (1.3.3)
|
103
126
|
treetop (1.4.10)
|
104
127
|
polyglot
|
105
128
|
polyglot (>= 0.3.1)
|
106
|
-
tzinfo (0.3.
|
129
|
+
tzinfo (0.3.33)
|
107
130
|
|
108
131
|
PLATFORMS
|
109
132
|
ruby
|
@@ -111,6 +134,8 @@ PLATFORMS
|
|
111
134
|
DEPENDENCIES
|
112
135
|
eventmachine (>= 1.0.0.beta.3)
|
113
136
|
faye-websocket
|
137
|
+
guard
|
138
|
+
guard-rspec
|
114
139
|
rails
|
115
140
|
rake
|
116
141
|
rspec-rails
|
data/Guardfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/websocket_rails/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
end
|
9
|
+
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ If you haven't done so yet, check out the [Project Page](http://danknox.github.c
|
|
6
6
|
|
7
7
|
## Overview
|
8
8
|
|
9
|
-
Plug and play WebSocket support for ruby on rails. Includes event router for mapping javascript events to controller actions. There is no need for a separate WebSocket server process. Requests to `/websocket` will be passed through to the `ConnectionManager` class which is a simple Rack based WebSocket server developed using the `Faye::WebSocket` library.
|
9
|
+
Plug and play WebSocket support for ruby on rails with streaming HTTP fallback for improved cross browser compatibility. Includes event router for mapping javascript events to controller actions. There is no need for a separate WebSocket server process. Requests to `/websocket` will be passed through to the `ConnectionManager` class which is a simple Rack based WebSocket server developed using the `Faye::WebSocket` library.
|
10
10
|
|
11
11
|
*Important Note*
|
12
12
|
|
@@ -69,7 +69,8 @@ The websocket client must connect to `/websocket`. You can connect using the fol
|
|
69
69
|
````javascript
|
70
70
|
var conn = new WebSocket("ws://localhost:3000/websocket")
|
71
71
|
conn.onopen = function(evt) {
|
72
|
-
|
72
|
+
// Example dispatcher located in the assets/ directory
|
73
|
+
dispatcher.trigger('new_user',current_user)
|
73
74
|
}
|
74
75
|
|
75
76
|
conn.onmessage = function(evt) {
|
@@ -80,7 +81,23 @@ conn.onmessage = function(evt) {
|
|
80
81
|
}
|
81
82
|
````
|
82
83
|
|
83
|
-
|
84
|
+
There are two example dispatchers located in the
|
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.
|
84
101
|
|
85
102
|
## Controllers
|
86
103
|
|
@@ -97,7 +114,7 @@ class ChatController < WebsocketRails::BaseController
|
|
97
114
|
end
|
98
115
|
````
|
99
116
|
|
100
|
-
The
|
117
|
+
The WebsocketRails::BaseController class provides methods for working with the WebSocket connection. Make sure you extend this class for controllers that you are using. The two most important methods are `send_message` and `broadcast_message`. The `send_message` method sends a message to the client that initiated this event, the `broadcast_message` method broadcasts messages to all connected clients. Both methods take two arguments, the event name to trigger on the client, and the message that accompanies it.
|
101
118
|
|
102
119
|
````ruby
|
103
120
|
new_message = {:message => 'this is a message'}
|
@@ -149,7 +166,7 @@ end
|
|
149
166
|
If you wish to output an Array of the assigned values in the data store for every connected client, you can use the `each_<key>` method, replacing `<key>` with the hash key that you wish to collect.
|
150
167
|
|
151
168
|
Given our ongoing chat server example, we could collect all of the current `User` objects like so:
|
152
|
-
|
169
|
+
d
|
153
170
|
````ruby
|
154
171
|
data_store[:user] = 'User3'
|
155
172
|
data_store.each_user
|
@@ -167,7 +184,7 @@ end
|
|
167
184
|
|
168
185
|
## Message Format
|
169
186
|
|
170
|
-
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 three element serialized array with the `client_id` as the first element,`event_name` string as the second element, and the message object you passed to the `message` parameter of the `send_message` method as the third element.
|
171
188
|
|
172
189
|
If you executed this code in your controller:
|
173
190
|
|
@@ -179,7 +196,7 @@ send_message :new_message, new_message
|
|
179
196
|
The message that arrives on the client would look like:
|
180
197
|
|
181
198
|
````javascript
|
182
|
-
['new_message',{message: 'this is a message'}]
|
199
|
+
['70291412510420','new_message',{message: 'this is a message'}]
|
183
200
|
````
|
184
201
|
|
185
202
|
## Development
|
@@ -188,4 +205,4 @@ This gem is created and maintained by Dan Knox and Kyle Whalen under the MIT Lic
|
|
188
205
|
|
189
206
|
Brought to you by:
|
190
207
|
|
191
|
-
Three Dot Loft LLC
|
208
|
+
Three Dot Loft LLC
|
@@ -0,0 +1,77 @@
|
|
1
|
+
/*
|
2
|
+
* Example HTTP event dispatcher.
|
3
|
+
*
|
4
|
+
* Setting up the dispatcher:
|
5
|
+
* var dispatcher = new ServerEventsDispatcher()
|
6
|
+
* dispatcher.onopen(function() {
|
7
|
+
* // trigger a server event immediately after opening connection
|
8
|
+
* dispatcher.trigger('new_user',{user_name: 'guest'})
|
9
|
+
* })
|
10
|
+
*
|
11
|
+
* Triggering a new event on the server
|
12
|
+
* dispatcher.trigger('event_name',object_to_be_serialized_to_json)
|
13
|
+
*
|
14
|
+
* Listening for new events from the server
|
15
|
+
* dispatcher.bind('event_name', function(data) {
|
16
|
+
* alert(data.user_name)
|
17
|
+
* })
|
18
|
+
*/
|
19
|
+
var ServerEventsDispatcher = function(){
|
20
|
+
var conn = new XMLHttpRequest(),
|
21
|
+
open_handler = function(){},
|
22
|
+
loaded = false,
|
23
|
+
lastPos = 0,
|
24
|
+
client_id = '';
|
25
|
+
|
26
|
+
conn.onreadystatechange = function() {
|
27
|
+
if (conn.readyState == 3) {
|
28
|
+
var data = conn.responseText.substring(lastPos);
|
29
|
+
lastPos = conn.responseText.length;
|
30
|
+
var json_data = JSON.parse(data),
|
31
|
+
id = json_data[0],
|
32
|
+
event_name = json_data[1],
|
33
|
+
message = json_data[2];
|
34
|
+
|
35
|
+
client_id = id
|
36
|
+
|
37
|
+
if (loaded == false) {
|
38
|
+
open_handler();
|
39
|
+
loaded = true
|
40
|
+
}
|
41
|
+
console.log(json_data)
|
42
|
+
dispatch(event_name, message)
|
43
|
+
}
|
44
|
+
}
|
45
|
+
conn.open("GET","/websocket",true)
|
46
|
+
conn.send()
|
47
|
+
|
48
|
+
var callbacks = {}
|
49
|
+
|
50
|
+
this.bind = function(event_name, callback) {
|
51
|
+
callbacks[event_name] = callbacks[event_name] || [];
|
52
|
+
callbacks[event_name].push(callback)
|
53
|
+
}
|
54
|
+
|
55
|
+
this.trigger = function(event_name, data) {
|
56
|
+
var payload = JSON.stringify([event_name,data])
|
57
|
+
$.ajax({
|
58
|
+
type: 'POST',
|
59
|
+
url: '/websocket',
|
60
|
+
data: {client_id: client_id, data: payload},
|
61
|
+
success: function(){console.log('success');}
|
62
|
+
});
|
63
|
+
return this;
|
64
|
+
}
|
65
|
+
|
66
|
+
this.onopen = function(handler) {
|
67
|
+
open_handler = handler
|
68
|
+
}
|
69
|
+
|
70
|
+
var dispatch = function(event_name, message) {
|
71
|
+
var chain = callbacks[event_name]
|
72
|
+
if (typeof chain == 'undefined') return;
|
73
|
+
for(var i = 0; i < chain.length; i++) {
|
74
|
+
chain[i]( message )
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
/*
|
2
|
+
* Example WebSocket event dispatcher.
|
3
|
+
*
|
4
|
+
* Setting up the dispatcher:
|
5
|
+
* var dispatcher = new ServerEventsDispatcher()
|
6
|
+
* dispatcher.onopen(function() {
|
7
|
+
* // trigger a server event immediately after opening connection
|
8
|
+
* dispatcher.trigger('new_user',{user_name: 'guest'})
|
9
|
+
* })
|
10
|
+
*
|
11
|
+
* Triggering a new event on the server
|
12
|
+
* dispatcher.trigger('event_name',object_to_be_serialized_to_json)
|
13
|
+
*
|
14
|
+
* Listening for new events from the server
|
15
|
+
* dispatcher.bind('event_name', function(data) {
|
16
|
+
* alert(data.user_name)
|
17
|
+
* })
|
18
|
+
*/
|
19
|
+
var ServerEventsDispatcher = function(){
|
20
|
+
var conn = new WebSocket("ws://localhost:3000/websocket"),
|
21
|
+
open_handler = function(){},
|
22
|
+
callbacks = {},
|
23
|
+
client_id = '';
|
24
|
+
|
25
|
+
this.bind = function(event_name, callback) {
|
26
|
+
callbacks[event_name] = callbacks[event_name] || [];
|
27
|
+
callbacks[event_name].push(callback)
|
28
|
+
}
|
29
|
+
|
30
|
+
this.trigger = function(event_name, data) {
|
31
|
+
var payload = JSON.stringify([event_name,data])
|
32
|
+
conn.send( payload )
|
33
|
+
return this;
|
34
|
+
}
|
35
|
+
|
36
|
+
conn.onopen = function(evt) {
|
37
|
+
open_handler()
|
38
|
+
}
|
39
|
+
this.onopen = function(handler) {
|
40
|
+
open_handler = handler
|
41
|
+
}
|
42
|
+
|
43
|
+
conn.onmessage = function(evt) {
|
44
|
+
var data = JSON.parse(evt.data),
|
45
|
+
id = data[0],
|
46
|
+
event_name = data[1],
|
47
|
+
message = data[2];
|
48
|
+
|
49
|
+
client_id = id
|
50
|
+
console.log(data)
|
51
|
+
dispatch(event_name, message)
|
52
|
+
}
|
53
|
+
|
54
|
+
conn.onclose = function(evt) {
|
55
|
+
dispatch('connection_closed', '')
|
56
|
+
}
|
57
|
+
|
58
|
+
var dispatch = function(event_name, message) {
|
59
|
+
var chain = callbacks[event_name]
|
60
|
+
if (typeof chain == 'undefined') return;
|
61
|
+
for(var i = 0; i < chain.length; i++) {
|
62
|
+
chain[i]( message )
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
data/lib/websocket-rails.rb
CHANGED
@@ -23,5 +23,9 @@ require 'websocket_rails/dispatcher'
|
|
23
23
|
require 'websocket_rails/events'
|
24
24
|
require 'websocket_rails/base_controller'
|
25
25
|
|
26
|
+
require 'websocket_rails/connection_adapters'
|
27
|
+
require 'websocket_rails/connection_adapters/http'
|
28
|
+
require 'websocket_rails/connection_adapters/web_socket'
|
29
|
+
|
26
30
|
::Thin::Server.send( :remove_const, 'DEFAULT_TIMEOUT' )
|
27
31
|
::Thin::Server.const_set( 'DEFAULT_TIMEOUT', 0 )
|
@@ -60,7 +60,7 @@ module WebsocketRails
|
|
60
60
|
# for each currently active connection but can not be used to associate a client between
|
61
61
|
# multiple connection attempts.
|
62
62
|
def client_id
|
63
|
-
connection.
|
63
|
+
connection.id
|
64
64
|
end
|
65
65
|
|
66
66
|
# The current message that was passed from the client when the event was initiated. The
|
@@ -77,12 +77,12 @@ module WebsocketRails
|
|
77
77
|
# # Will arrive on the client as JSON string like the following:
|
78
78
|
# # ['new_message',{message: 'new message for the client'}]
|
79
79
|
def send_message(event, message)
|
80
|
-
@_dispatcher.send_message event.to_s, message, connection if @_dispatcher.respond_to?(:send_message)
|
80
|
+
@_dispatcher.send_message client_id, event.to_s, message, connection if @_dispatcher.respond_to?(:send_message)
|
81
81
|
end
|
82
82
|
|
83
83
|
# Broadcasts a message to all connected clients. See {#send_message} for message passing details.
|
84
84
|
def broadcast_message(event, message)
|
85
|
-
@_dispatcher.broadcast_message event.to_s, message if @_dispatcher.respond_to?(:broadcast_message)
|
85
|
+
@_dispatcher.broadcast_message client_id, event.to_s, message if @_dispatcher.respond_to?(:broadcast_message)
|
86
86
|
end
|
87
87
|
|
88
88
|
# Provides access to the {DataStore} for the current controller. The {DataStore} provides convenience
|
@@ -107,4 +107,4 @@ module WebsocketRails
|
|
107
107
|
end
|
108
108
|
|
109
109
|
end
|
110
|
-
end
|
110
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module WebsocketRails
|
2
|
+
module ConnectionAdapters
|
3
|
+
|
4
|
+
attr_reader :adapters
|
5
|
+
module_function :adapters
|
6
|
+
|
7
|
+
def self.register_adapter(adapter)
|
8
|
+
@adapters ||= []
|
9
|
+
@adapters.unshift adapter
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.establish_connection(env)
|
13
|
+
adapter = adapters.detect { |a| a.accepts?( env ) } || return
|
14
|
+
adapter.new( env )
|
15
|
+
end
|
16
|
+
|
17
|
+
class Base
|
18
|
+
|
19
|
+
ADAPTER_EVENTS = [:onmessage, :onerror, :onclose]
|
20
|
+
|
21
|
+
def self.inherited(adapter)
|
22
|
+
ConnectionAdapters.register_adapter( adapter )
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(env)
|
26
|
+
@env = env
|
27
|
+
end
|
28
|
+
|
29
|
+
ADAPTER_EVENTS.each do |adapter_event|
|
30
|
+
define_method "#{adapter_event}" do |event=nil|
|
31
|
+
instance_variable_get( "@#{adapter_event}" ).call( event )
|
32
|
+
end
|
33
|
+
define_method "#{adapter_event}=" do |block=nil|
|
34
|
+
instance_variable_set( "@#{adapter_event}", block )
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def send(message)
|
39
|
+
raise NotImplementedError, "Override this method in the connection specific adapter class"
|
40
|
+
end
|
41
|
+
|
42
|
+
def rack_response
|
43
|
+
[ -1, {}, [] ]
|
44
|
+
end
|
45
|
+
|
46
|
+
def id
|
47
|
+
object_id.to_i
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module WebsocketRails
|
2
|
+
module ConnectionAdapters
|
3
|
+
class Http < Base
|
4
|
+
TERM = "\r\n".freeze
|
5
|
+
TAIL = "0#{TERM}#{TERM}".freeze
|
6
|
+
|
7
|
+
def self.accepts?(env)
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :headers
|
12
|
+
|
13
|
+
def initialize(env)
|
14
|
+
super
|
15
|
+
@body = DeferrableBody.new
|
16
|
+
@headers = Hash.new
|
17
|
+
@headers['Content-Type'] = 'text/json'
|
18
|
+
@headers['Transfer-Encoding'] = 'chunked'
|
19
|
+
|
20
|
+
define_deferrable_callbacks
|
21
|
+
EM.next_tick { @env['async.callback'].call [200, @headers, @body] }
|
22
|
+
end
|
23
|
+
|
24
|
+
def send(message)
|
25
|
+
@body.chunk encode_chunk( message )
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def define_deferrable_callbacks
|
31
|
+
@body.callback do |event|
|
32
|
+
onclose(event)
|
33
|
+
end
|
34
|
+
@body.errback do |event|
|
35
|
+
onclose(event)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# From [Rack::Stream](https://github.com/intridea/rack-stream)
|
40
|
+
def encode_chunk(c)
|
41
|
+
return nil if c.nil?
|
42
|
+
# hack to work with Rack::File for now, should not TE chunked
|
43
|
+
# things that aren't strings or respond to bytesize
|
44
|
+
c = ::File.read(c.path) if c.kind_of?(Rack::File)
|
45
|
+
size = Rack::Utils.bytesize(c)
|
46
|
+
return nil if size == 0
|
47
|
+
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
|
+
[size.to_s(16), TERM, c, TERM].join
|
51
|
+
end
|
52
|
+
|
53
|
+
# From [thin_async](https://github.com/macournoyer/thin_async)
|
54
|
+
class DeferrableBody
|
55
|
+
include EM::Deferrable
|
56
|
+
|
57
|
+
# @param chunks - object that responds to each. holds initial chunks of content
|
58
|
+
def initialize(chunks = [])
|
59
|
+
@queue = []
|
60
|
+
chunks.each {|c| chunk(c)}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Enqueue a chunk of content to be flushed to stream at a later time
|
64
|
+
def chunk(*chunks)
|
65
|
+
@queue += chunks
|
66
|
+
schedule_dequeue
|
67
|
+
end
|
68
|
+
|
69
|
+
# When rack attempts to iterate over `body`, save the block,
|
70
|
+
# and execute at a later time when `@queue` has elements
|
71
|
+
def each(&blk)
|
72
|
+
@body_callback = blk
|
73
|
+
schedule_dequeue
|
74
|
+
end
|
75
|
+
|
76
|
+
def empty?
|
77
|
+
@queue.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def close!(flush = true)
|
81
|
+
EM.next_tick {
|
82
|
+
if !flush || empty?
|
83
|
+
succeed
|
84
|
+
else
|
85
|
+
schedule_dequeue
|
86
|
+
close!(flush)
|
87
|
+
end
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def schedule_dequeue
|
94
|
+
return unless @body_callback
|
95
|
+
EM.next_tick do
|
96
|
+
next unless c = @queue.shift
|
97
|
+
@body_callback.call(c)
|
98
|
+
schedule_dequeue unless empty?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module WebsocketRails
|
2
|
+
module ConnectionAdapters
|
3
|
+
class WebSocket < Base
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def self.accepts?(env)
|
8
|
+
::Faye::WebSocket.websocket?( env )
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.delegated_methods
|
12
|
+
setter_methods = ADAPTER_EVENTS.map {|e| "#{e}=".to_sym }
|
13
|
+
setter_methods + ADAPTER_EVENTS
|
14
|
+
end
|
15
|
+
def_delegators :@connection, *delegated_methods
|
16
|
+
|
17
|
+
def initialize(env)
|
18
|
+
super
|
19
|
+
@connection = ::Faye::WebSocket.new( env )
|
20
|
+
end
|
21
|
+
|
22
|
+
def send(message)
|
23
|
+
@connection.send message
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -3,30 +3,68 @@ require 'rack'
|
|
3
3
|
require 'thin'
|
4
4
|
|
5
5
|
module WebsocketRails
|
6
|
+
class InvalidConnection < StandardError; end
|
6
7
|
# The +ConnectionManager+ class implements the core Rack application that handles
|
7
8
|
# incoming WebSocket connections.
|
8
9
|
class ConnectionManager
|
9
10
|
|
10
11
|
# Contains an Array of currently open Faye::WebSocket connections.
|
11
12
|
# @return [Array]
|
12
|
-
|
13
|
+
attr_reader :connections
|
14
|
+
|
15
|
+
# Contains the {Dispatcher} instance for the active server.
|
16
|
+
# @return [Dispatcher]
|
17
|
+
attr_reader :dispatcher
|
13
18
|
|
14
19
|
def initialize
|
15
20
|
@connections = []
|
16
21
|
@dispatcher = Dispatcher.new( self )
|
17
22
|
end
|
18
23
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
# Primary entry point for the Rack application
|
25
|
+
def call(env)
|
26
|
+
request = Rack::Request.new( env )
|
27
|
+
if request.post?
|
28
|
+
response = parse_incoming_event( request.params )
|
29
|
+
else
|
30
|
+
response = open_connection( env )
|
31
|
+
end
|
32
|
+
response
|
33
|
+
end
|
34
|
+
|
35
|
+
# Used to broadcast a message to all connected clients. This method should never
|
36
|
+
# be called directly. Instead, users should use {BaseController#broadcast_message}
|
37
|
+
# and {BaseController#send_message} in their applications.
|
38
|
+
def broadcast_message(message)
|
39
|
+
@connections.map do |connection|
|
40
|
+
connection.send message
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def parse_incoming_event(params)
|
47
|
+
connection = find_connection_by_id params["client_id"]
|
48
|
+
data = params["data"]
|
49
|
+
@dispatcher.receive( data, connection )
|
50
|
+
[200,{'Content-Type' => 'text/plain'},['success']]
|
51
|
+
rescue InvalidConnection
|
52
|
+
[400,{'Content-Type' => 'text/plain'},['invalid connection']]
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_connection_by_id(id)
|
56
|
+
connections.detect { |connection| connection.id == id.to_i } || (raise InvalidConnection)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Opens a persistent connection using the appropriate {ConnectionAdapter}. Stores
|
60
|
+
# active connections in the {connections} array.
|
61
|
+
def open_connection(env)
|
62
|
+
connection = ConnectionAdapters.establish_connection( env )
|
63
|
+
return invalid_connection_attempt unless connection
|
27
64
|
|
28
65
|
puts "Client #{connection} connected\n"
|
29
66
|
@dispatcher.dispatch( 'client_connected', {}, connection )
|
67
|
+
@dispatcher.send_message( connection.id, :connection_open, {}, connection )
|
30
68
|
|
31
69
|
connection.onmessage = lambda do |event|
|
32
70
|
@dispatcher.receive( event.data, connection )
|
@@ -48,21 +86,10 @@ module WebsocketRails
|
|
48
86
|
connections << connection
|
49
87
|
connection.rack_response
|
50
88
|
end
|
51
|
-
|
52
|
-
# Used to broadcast a message to all connected clients. This method should never
|
53
|
-
# be called directly. Instead, users should use {BaseController#broadcast_message}
|
54
|
-
# and {BaseController#send_message} in their applications.
|
55
|
-
def broadcast_message(message)
|
56
|
-
@connections.map do |connection|
|
57
|
-
connection.send message
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
89
|
+
|
63
90
|
def invalid_connection_attempt
|
64
91
|
[400,{'Content-Type' => 'text/plain'}, ['Connection was not a valid WebSocket connection']]
|
65
92
|
end
|
66
93
|
|
67
94
|
end
|
68
|
-
end
|
95
|
+
end
|
@@ -22,12 +22,12 @@ module WebsocketRails
|
|
22
22
|
dispatch( event_name, data, connection )
|
23
23
|
end
|
24
24
|
|
25
|
-
def send_message(event_name,data,connection)
|
26
|
-
connection.send encoded_message( event_name, data )
|
25
|
+
def send_message(client_id,event_name,data,connection)
|
26
|
+
connection.send encoded_message( client_id, event_name, data )
|
27
27
|
end
|
28
28
|
|
29
|
-
def broadcast_message(event_name,data)
|
30
|
-
@connection_manager.broadcast_message encoded_message( event_name, data )
|
29
|
+
def broadcast_message(client_id,event_name,data)
|
30
|
+
@connection_manager.broadcast_message encoded_message( client_id, event_name, data )
|
31
31
|
end
|
32
32
|
|
33
33
|
def dispatch(event_name,message,connection)
|
@@ -42,9 +42,9 @@ module WebsocketRails
|
|
42
42
|
}.resume
|
43
43
|
end
|
44
44
|
|
45
|
-
def encoded_message(event_name,data)
|
46
|
-
[event_name, data].to_json
|
45
|
+
def encoded_message(client_id,event_name,data)
|
46
|
+
[client_id, event_name, data].to_json
|
47
47
|
end
|
48
48
|
|
49
49
|
end
|
50
|
-
end
|
50
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
1
|
module WebsocketRails
|
2
|
-
VERSION = "0.1.
|
3
|
-
end
|
2
|
+
VERSION = "0.1.2"
|
3
|
+
end
|
@@ -26,6 +26,10 @@ class ChatController < WebsocketRails::BaseController
|
|
26
26
|
# do something when a client connects
|
27
27
|
end
|
28
28
|
|
29
|
+
def error_occurred
|
30
|
+
# do something when an error occurs
|
31
|
+
end
|
32
|
+
|
29
33
|
def new_message
|
30
34
|
puts "Message from UID: #{client_id}\n"
|
31
35
|
@message_counter += 1
|
@@ -1,38 +1,91 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'support/mock_web_socket'
|
2
3
|
|
3
4
|
module WebsocketRails
|
4
|
-
describe ConnectionManager do
|
5
|
+
describe ConnectionManager, "integration test" do
|
5
6
|
|
6
7
|
def define_test_events
|
7
8
|
WebsocketRails.route_block = nil
|
8
9
|
WebsocketRails::Events.describe_events do
|
9
10
|
subscribe :client_connected, to: ChatController, with_method: :new_user
|
10
11
|
subscribe :change_username, to: ChatController, with_method: :change_username
|
12
|
+
subscribe :client_error, to: ChatController, with_method: :error_occurred
|
13
|
+
subscribe :client_disconnected, to: ChatController, with_method: :delete_user
|
11
14
|
end
|
12
15
|
end
|
13
16
|
|
14
|
-
before(:all) {
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
define_test_events
|
28
|
-
@server.call( env )
|
29
|
-
#ChatController.new.send(:new_user)
|
17
|
+
before(:all) {
|
18
|
+
define_test_events
|
19
|
+
if defined?(ConnectionAdapters::Test)
|
20
|
+
ConnectionAdapters.adapters.delete( ConnectionAdapters::Test )
|
21
|
+
end
|
22
|
+
}
|
23
|
+
|
24
|
+
shared_examples "an evented rack server" do
|
25
|
+
context "new connections" do
|
26
|
+
it "should execute the controller action associated with the 'client_connected' event" do
|
27
|
+
ChatController.any_instance.should_receive(:new_user)
|
28
|
+
@server.call( env )
|
29
|
+
end
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
32
|
+
context "active connections" do
|
33
|
+
context "new message from client" do
|
34
|
+
let(:test_message) { ['change_username',{user_name: 'Joe User'}] }
|
35
|
+
let(:encoded_message) { test_message.to_json }
|
36
|
+
|
37
|
+
before(:each) { MockEvent = Struct.new(:data) }
|
38
|
+
|
39
|
+
it "should execute the controller action associated with the received event" do
|
40
|
+
mock_event = MockEvent.new( encoded_message )
|
41
|
+
ChatController.any_instance.should_receive(:change_username)
|
42
|
+
@server.call( env )
|
43
|
+
socket.onmessage( mock_event )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "client error" do
|
48
|
+
it "should execute the controller action associated with the 'client_error' event" do
|
49
|
+
ChatController.any_instance.should_receive(:error_occurred)
|
50
|
+
@server.call( env )
|
51
|
+
socket.onerror
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "client disconnects" do
|
56
|
+
it "should execute the controller action associated with the 'client_disconnected' event" do
|
57
|
+
ChatController.any_instance.should_receive(:delete_user)
|
58
|
+
@server.call( env )
|
59
|
+
socket.onclose
|
60
|
+
end
|
61
|
+
end
|
34
62
|
end
|
35
63
|
end
|
36
|
-
|
64
|
+
|
65
|
+
let(:env) { Rack::MockRequest.env_for('/websocket') }
|
66
|
+
|
67
|
+
context "WebSocket Adapter" do
|
68
|
+
let(:socket) { MockWebSocket.new }
|
69
|
+
|
70
|
+
before do
|
71
|
+
::Faye::WebSocket.stub(:websocket?).and_return(true)
|
72
|
+
::Faye::WebSocket.stub(:new).and_return(socket)
|
73
|
+
@server = ConnectionManager.new
|
74
|
+
end
|
75
|
+
|
76
|
+
it_behaves_like 'an evented rack server'
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "HTTP Adapter" do
|
80
|
+
let(:socket) { ConnectionAdapters::Http.new( env ) }
|
81
|
+
|
82
|
+
before do
|
83
|
+
ConnectionAdapters.stub(:establish_connection).and_return(socket)
|
84
|
+
@server = ConnectionManager.new
|
85
|
+
end
|
86
|
+
|
87
|
+
it_behaves_like 'an evented rack server'
|
88
|
+
end
|
89
|
+
|
37
90
|
end
|
38
|
-
end
|
91
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module WebsocketRails
|
4
|
+
module ConnectionAdapters
|
5
|
+
describe Http do
|
6
|
+
|
7
|
+
let(:env) { Rack::MockRequest.env_for('/websocket') }
|
8
|
+
|
9
|
+
subject { Http.new( env ) }
|
10
|
+
|
11
|
+
it "should be a subclass of ConnectionAdapters::Base" do
|
12
|
+
subject.class.superclass.should == ConnectionAdapters::Base
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should set the Content-Length header to text/json" do
|
16
|
+
subject.headers['Content-Type'].should == "text/json"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should set the Transfer-Encoding header to chunked" do
|
20
|
+
subject.headers['Transfer-Encoding'].should == "chunked"
|
21
|
+
end
|
22
|
+
|
23
|
+
context "#encode_chunk" do
|
24
|
+
it "should properly encode strings" do
|
25
|
+
subject.__send__(:encode_chunk,"test").should == "4\r\ntest\r\n"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "adapter methods" do
|
30
|
+
before do
|
31
|
+
@body = double('DeferrableBody').as_null_object
|
32
|
+
Http::DeferrableBody.stub(:new).and_return(@body)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "#define_deferrable_callbacks" do
|
36
|
+
it "should define a callback for :succeeded" do
|
37
|
+
@body.should_receive(:callback)
|
38
|
+
subject
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should define a callback for :failed" do
|
42
|
+
@body.should_receive(:errback)
|
43
|
+
subject
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "#send" do
|
48
|
+
it "should encode the message before sending" do
|
49
|
+
subject.should_receive(:encode_chunk).with('test message')
|
50
|
+
subject.send 'test message'
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should enqueue the message on DeferrableBody" do
|
54
|
+
encoded_message = subject.__send__(:encode_chunk,'test message')
|
55
|
+
@body.should_receive(:chunk).with(encoded_message)
|
56
|
+
subject.send 'test message'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/mock_web_socket'
|
3
|
+
|
4
|
+
module WebsocketRails
|
5
|
+
module ConnectionAdapters
|
6
|
+
describe WebSocket do
|
7
|
+
|
8
|
+
before do
|
9
|
+
@socket = MockWebSocket.new
|
10
|
+
Faye::WebSocket.stub(:new).and_return(@socket)
|
11
|
+
@adapter = WebSocket.new( Hash.new )
|
12
|
+
end
|
13
|
+
|
14
|
+
WebSocket::ADAPTER_EVENTS.each do |event|
|
15
|
+
it "should delegate ##{event} and ##{event}= to the Faye::WebSocket object" do
|
16
|
+
@socket.should_receive(event)
|
17
|
+
@socket.should_receive("#{event}=".to_sym)
|
18
|
+
|
19
|
+
@adapter.__send__( "#{event}=".to_sym, Proc.new {|e| true } )
|
20
|
+
@adapter.__send__( event )
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "#send" do
|
25
|
+
it "should send the message to the websocket connection" do
|
26
|
+
@socket.should_receive(:send).with(:message)
|
27
|
+
@adapter.send :message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module WebsocketRails
|
4
|
+
|
5
|
+
class ConnectionAdapters::Test < ConnectionAdapters::Base
|
6
|
+
def self.accepts?(env)
|
7
|
+
true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ConnectionAdapters do
|
12
|
+
|
13
|
+
let(:env) { Rack::MockRequest.env_for('/websocket') }
|
14
|
+
|
15
|
+
context ".register_adapter" do
|
16
|
+
it "should store a reference to the adapter in the adapters array" do
|
17
|
+
ConnectionAdapters.register_adapter( ConnectionAdapters::Test )
|
18
|
+
ConnectionAdapters.adapters.include?( ConnectionAdapters::Test ).should be_true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context ".establish_connection" do
|
23
|
+
it "should return the correct connection adapter instance" do
|
24
|
+
adapter = ConnectionAdapters.establish_connection( env )
|
25
|
+
adapter.class.should == ConnectionAdapters::Test
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
module ConnectionAdapters
|
32
|
+
describe Base do
|
33
|
+
|
34
|
+
let(:env) { Rack::MockRequest.env_for('/websocket') }
|
35
|
+
|
36
|
+
subject { Base.new( env ) }
|
37
|
+
|
38
|
+
context "new adapters" do
|
39
|
+
it "should register themselves in the adapters array when inherited" do
|
40
|
+
adapter = Class.new( ConnectionAdapters::Base )
|
41
|
+
ConnectionAdapters.adapters.include?( adapter ).should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
Base::ADAPTER_EVENTS.each do |event|
|
45
|
+
it "should define accessor methods for #{event}" do
|
46
|
+
proc = lambda { |event| true }
|
47
|
+
subject.__send__("#{event}=".to_sym,proc)
|
48
|
+
subject.__send__(event).should == true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "#send" do
|
54
|
+
it "should raise a NotImplementedError exception" do
|
55
|
+
expect { subject.send :message }.to raise_exception( NotImplementedError )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -1,22 +1,25 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'support/mock_web_socket'
|
3
2
|
|
4
3
|
module WebsocketRails
|
5
4
|
describe ConnectionManager do
|
6
|
-
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
def app
|
8
|
+
@app ||= ConnectionManager.new
|
9
|
+
end
|
10
|
+
|
7
11
|
def open_connection
|
8
|
-
subject.call(
|
12
|
+
subject.call(env)
|
9
13
|
end
|
10
14
|
|
11
15
|
let(:connections) { subject.connections }
|
16
|
+
let(:env) { Rack::MockRequest.env_for('/websocket') }
|
12
17
|
|
13
18
|
before(:each) do
|
14
|
-
|
15
|
-
@mock_socket
|
16
|
-
Faye::WebSocket.stub(:new).and_return(@mock_socket)
|
19
|
+
@mock_socket = ConnectionAdapters::Base.new(env)
|
20
|
+
ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket)
|
17
21
|
@dispatcher = double('dispatcher').as_null_object
|
18
22
|
Dispatcher.stub(:new).and_return(@dispatcher)
|
19
|
-
@env = {}
|
20
23
|
end
|
21
24
|
|
22
25
|
context "new connections" do
|
@@ -40,11 +43,23 @@ module WebsocketRails
|
|
40
43
|
it "should return an Async Rack response" do
|
41
44
|
open_connection.should == [ -1, {}, [] ]
|
42
45
|
end
|
43
|
-
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "new POST event" do
|
49
|
+
before(:each) do
|
50
|
+
@mock_http = ConnectionAdapters::Http.new(env)
|
51
|
+
app.connections << @mock_http
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should receive the new event for the correct connection" do
|
55
|
+
@dispatcher.should_receive(:receive).with('data',@mock_http)
|
56
|
+
post '/websocket', {:client_id => @mock_http.id, :data => 'data'}
|
57
|
+
end
|
58
|
+
end
|
44
59
|
|
45
60
|
context "open connections" do
|
46
61
|
before(:each) do
|
47
|
-
|
62
|
+
ConnectionAdapters.stub(:establish_connection).and_return(@mock_socket,ConnectionAdapters::Base.new(env))
|
48
63
|
4.times { open_connection }
|
49
64
|
end
|
50
65
|
|
@@ -100,7 +115,7 @@ module WebsocketRails
|
|
100
115
|
|
101
116
|
context "invalid connections" do
|
102
117
|
before(:each) do
|
103
|
-
|
118
|
+
ConnectionAdapters.stub(:establish_connection).and_return(false)
|
104
119
|
end
|
105
120
|
|
106
121
|
it "should return a 400 bad request error code" do
|
@@ -108,4 +123,4 @@ module WebsocketRails
|
|
108
123
|
end
|
109
124
|
end
|
110
125
|
end
|
111
|
-
end
|
126
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: websocket-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-06-
|
14
|
+
date: 2012-06-10 00:00:00.000000000Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rack
|
@@ -138,13 +138,19 @@ files:
|
|
138
138
|
- .travis.yml
|
139
139
|
- Gemfile
|
140
140
|
- Gemfile.lock
|
141
|
+
- Guardfile
|
141
142
|
- MIT-LICENSE
|
142
143
|
- README.md
|
143
144
|
- Rakefile
|
145
|
+
- assets/javascripts/http_dispatcher.js
|
146
|
+
- assets/javascripts/websocket_dispatcher.js
|
144
147
|
- bin/thin-socketrails
|
145
148
|
- config/routes.rb
|
146
149
|
- lib/websocket-rails.rb
|
147
150
|
- lib/websocket_rails/base_controller.rb
|
151
|
+
- lib/websocket_rails/connection_adapters.rb
|
152
|
+
- lib/websocket_rails/connection_adapters/http.rb
|
153
|
+
- lib/websocket_rails/connection_adapters/web_socket.rb
|
148
154
|
- lib/websocket_rails/connection_manager.rb
|
149
155
|
- lib/websocket_rails/data_store.rb
|
150
156
|
- lib/websocket_rails/dispatcher.rb
|
@@ -176,7 +182,6 @@ files:
|
|
176
182
|
- spec/dummy/log/development.log
|
177
183
|
- spec/dummy/log/production.log
|
178
184
|
- spec/dummy/log/server.log
|
179
|
-
- spec/dummy/log/test.log
|
180
185
|
- spec/dummy/public/404.html
|
181
186
|
- spec/dummy/public/422.html
|
182
187
|
- spec/dummy/public/500.html
|
@@ -192,6 +197,9 @@ files:
|
|
192
197
|
- spec/integration/connection_manager_spec.rb
|
193
198
|
- spec/spec_helper.rb
|
194
199
|
- spec/support/mock_web_socket.rb
|
200
|
+
- spec/unit/connection_adapters/http_spec.rb
|
201
|
+
- spec/unit/connection_adapters/web_socket_spec.rb
|
202
|
+
- spec/unit/connection_adapters_spec.rb
|
195
203
|
- spec/unit/connection_manager_spec.rb
|
196
204
|
- spec/unit/data_store_spec.rb
|
197
205
|
- spec/unit/dispatcher_spec.rb
|
@@ -211,7 +219,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
211
219
|
version: '0'
|
212
220
|
segments:
|
213
221
|
- 0
|
214
|
-
hash:
|
222
|
+
hash: 2040378908172396900
|
215
223
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
216
224
|
none: false
|
217
225
|
requirements:
|
@@ -220,7 +228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
228
|
version: '0'
|
221
229
|
segments:
|
222
230
|
- 0
|
223
|
-
hash:
|
231
|
+
hash: 2040378908172396900
|
224
232
|
requirements: []
|
225
233
|
rubyforge_project:
|
226
234
|
rubygems_version: 1.8.19
|
data/spec/dummy/log/test.log
DELETED
File without changes
|