sonixlabs-em-websocket 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/CHANGELOG.rdoc +80 -0
- data/Gemfile +3 -0
- data/README.md +98 -0
- data/Rakefile +11 -0
- data/em-websocket.gemspec +27 -0
- data/examples/echo.rb +8 -0
- data/examples/flash_policy_file_server.rb +21 -0
- data/examples/js/FABridge.js +604 -0
- data/examples/js/WebSocketMain.swf +0 -0
- data/examples/js/swfobject.js +4 -0
- data/examples/js/web_socket.js +312 -0
- data/examples/multicast.rb +47 -0
- data/examples/test.html +30 -0
- data/lib/em-websocket/client_connection.rb +19 -0
- data/lib/em-websocket/close03.rb +11 -0
- data/lib/em-websocket/close05.rb +11 -0
- data/lib/em-websocket/close06.rb +16 -0
- data/lib/em-websocket/close75.rb +10 -0
- data/lib/em-websocket/connection.rb +184 -0
- data/lib/em-websocket/debugger.rb +17 -0
- data/lib/em-websocket/framing03.rb +167 -0
- data/lib/em-websocket/framing04.rb +15 -0
- data/lib/em-websocket/framing05.rb +168 -0
- data/lib/em-websocket/framing07.rb +180 -0
- data/lib/em-websocket/framing76.rb +114 -0
- data/lib/em-websocket/handler.rb +56 -0
- data/lib/em-websocket/handler03.rb +10 -0
- data/lib/em-websocket/handler05.rb +10 -0
- data/lib/em-websocket/handler06.rb +10 -0
- data/lib/em-websocket/handler07.rb +10 -0
- data/lib/em-websocket/handler08.rb +10 -0
- data/lib/em-websocket/handler13.rb +10 -0
- data/lib/em-websocket/handler75.rb +9 -0
- data/lib/em-websocket/handler76.rb +12 -0
- data/lib/em-websocket/handler_factory.rb +107 -0
- data/lib/em-websocket/handshake04.rb +75 -0
- data/lib/em-websocket/handshake75.rb +21 -0
- data/lib/em-websocket/handshake76.rb +71 -0
- data/lib/em-websocket/masking04.rb +63 -0
- data/lib/em-websocket/message_processor_03.rb +38 -0
- data/lib/em-websocket/message_processor_06.rb +52 -0
- data/lib/em-websocket/version.rb +5 -0
- data/lib/em-websocket/websocket.rb +45 -0
- data/lib/em-websocket.rb +23 -0
- data/lib/sonixlabs-em-websocket.rb +1 -0
- data/spec/helper.rb +146 -0
- data/spec/integration/client_examples.rb +48 -0
- data/spec/integration/common_spec.rb +118 -0
- data/spec/integration/draft03_spec.rb +270 -0
- data/spec/integration/draft05_spec.rb +48 -0
- data/spec/integration/draft06_spec.rb +88 -0
- data/spec/integration/draft13_spec.rb +75 -0
- data/spec/integration/draft75_spec.rb +117 -0
- data/spec/integration/draft76_spec.rb +230 -0
- data/spec/integration/shared_examples.rb +91 -0
- data/spec/unit/framing_spec.rb +325 -0
- data/spec/unit/handler_spec.rb +147 -0
- data/spec/unit/masking_spec.rb +27 -0
- data/spec/unit/message_processor_spec.rb +36 -0
- metadata +198 -0
@@ -0,0 +1,312 @@
|
|
1
|
+
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
2
|
+
// Lincense: New BSD Lincense
|
3
|
+
// Reference: http://dev.w3.org/html5/websockets/
|
4
|
+
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
|
5
|
+
|
6
|
+
(function() {
|
7
|
+
|
8
|
+
if (window.WebSocket) return;
|
9
|
+
|
10
|
+
var console = window.console;
|
11
|
+
if (!console) console = {log: function(){ }, error: function(){ }};
|
12
|
+
|
13
|
+
function hasFlash() {
|
14
|
+
if ('navigator' in window && 'plugins' in navigator && navigator.plugins['Shockwave Flash']) {
|
15
|
+
return !!navigator.plugins['Shockwave Flash'].description;
|
16
|
+
}
|
17
|
+
if ('ActiveXObject' in window) {
|
18
|
+
try {
|
19
|
+
return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
|
20
|
+
} catch (e) {}
|
21
|
+
}
|
22
|
+
return false;
|
23
|
+
}
|
24
|
+
|
25
|
+
if (!hasFlash()) {
|
26
|
+
console.error("Flash Player is not installed.");
|
27
|
+
return;
|
28
|
+
}
|
29
|
+
|
30
|
+
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
|
31
|
+
var self = this;
|
32
|
+
self.readyState = WebSocket.CONNECTING;
|
33
|
+
self.bufferedAmount = 0;
|
34
|
+
WebSocket.__addTask(function() {
|
35
|
+
self.__flash =
|
36
|
+
WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null);
|
37
|
+
|
38
|
+
self.__flash.addEventListener("open", function(fe) {
|
39
|
+
try {
|
40
|
+
if (self.onopen) self.onopen();
|
41
|
+
} catch (e) {
|
42
|
+
console.error(e.toString());
|
43
|
+
}
|
44
|
+
});
|
45
|
+
|
46
|
+
self.__flash.addEventListener("close", function(fe) {
|
47
|
+
try {
|
48
|
+
if (self.onclose) self.onclose();
|
49
|
+
} catch (e) {
|
50
|
+
console.error(e.toString());
|
51
|
+
}
|
52
|
+
});
|
53
|
+
|
54
|
+
self.__flash.addEventListener("message", function(fe) {
|
55
|
+
var data = decodeURIComponent(fe.getData());
|
56
|
+
try {
|
57
|
+
if (self.onmessage) {
|
58
|
+
var e;
|
59
|
+
if (window.MessageEvent) {
|
60
|
+
e = document.createEvent("MessageEvent");
|
61
|
+
e.initMessageEvent("message", false, false, data, null, null, window);
|
62
|
+
} else { // IE
|
63
|
+
e = {data: data};
|
64
|
+
}
|
65
|
+
self.onmessage(e);
|
66
|
+
}
|
67
|
+
} catch (e) {
|
68
|
+
console.error(e.toString());
|
69
|
+
}
|
70
|
+
});
|
71
|
+
|
72
|
+
self.__flash.addEventListener("stateChange", function(fe) {
|
73
|
+
try {
|
74
|
+
self.readyState = fe.getReadyState();
|
75
|
+
self.bufferedAmount = fe.getBufferedAmount();
|
76
|
+
} catch (e) {
|
77
|
+
console.error(e.toString());
|
78
|
+
}
|
79
|
+
});
|
80
|
+
|
81
|
+
//console.log("[WebSocket] Flash object is ready");
|
82
|
+
});
|
83
|
+
}
|
84
|
+
|
85
|
+
WebSocket.prototype.send = function(data) {
|
86
|
+
if (!this.__flash || this.readyState == WebSocket.CONNECTING) {
|
87
|
+
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
|
88
|
+
}
|
89
|
+
var result = this.__flash.send(data);
|
90
|
+
if (result < 0) { // success
|
91
|
+
return true;
|
92
|
+
} else {
|
93
|
+
this.bufferedAmount = result;
|
94
|
+
return false;
|
95
|
+
}
|
96
|
+
};
|
97
|
+
|
98
|
+
WebSocket.prototype.close = function() {
|
99
|
+
if (!this.__flash) return;
|
100
|
+
if (this.readyState != WebSocket.OPEN) return;
|
101
|
+
this.__flash.close();
|
102
|
+
// Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events
|
103
|
+
// which causes weird error:
|
104
|
+
// > You are trying to call recursively into the Flash Player which is not allowed.
|
105
|
+
this.readyState = WebSocket.CLOSED;
|
106
|
+
if (this.onclose) this.onclose();
|
107
|
+
};
|
108
|
+
|
109
|
+
/**
|
110
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
111
|
+
*
|
112
|
+
* @param {string} type
|
113
|
+
* @param {function} listener
|
114
|
+
* @param {boolean} useCapture !NB Not implemented yet
|
115
|
+
* @return void
|
116
|
+
*/
|
117
|
+
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
|
118
|
+
if (!('__events' in this)) {
|
119
|
+
this.__events = {};
|
120
|
+
}
|
121
|
+
if (!(type in this.__events)) {
|
122
|
+
this.__events[type] = [];
|
123
|
+
if ('function' == typeof this['on' + type]) {
|
124
|
+
this.__events[type].defaultHandler = this['on' + type];
|
125
|
+
this['on' + type] = WebSocket_FireEvent(this, type);
|
126
|
+
}
|
127
|
+
}
|
128
|
+
this.__events[type].push(listener);
|
129
|
+
};
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
133
|
+
*
|
134
|
+
* @param {string} type
|
135
|
+
* @param {function} listener
|
136
|
+
* @param {boolean} useCapture NB! Not implemented yet
|
137
|
+
* @return void
|
138
|
+
*/
|
139
|
+
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
|
140
|
+
if (!('__events' in this)) {
|
141
|
+
this.__events = {};
|
142
|
+
}
|
143
|
+
if (!(type in this.__events)) return;
|
144
|
+
for (var i = this.__events.length; i > -1; --i) {
|
145
|
+
if (listener === this.__events[type][i]) {
|
146
|
+
this.__events[type].splice(i, 1);
|
147
|
+
break;
|
148
|
+
}
|
149
|
+
}
|
150
|
+
};
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
154
|
+
*
|
155
|
+
* @param {WebSocketEvent} event
|
156
|
+
* @return void
|
157
|
+
*/
|
158
|
+
WebSocket.prototype.dispatchEvent = function(event) {
|
159
|
+
if (!('__events' in this)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
|
160
|
+
if (!(event.type in this.__events)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
|
161
|
+
|
162
|
+
for (var i = 0, l = this.__events[event.type].length; i < l; ++ i) {
|
163
|
+
this.__events[event.type][i](event);
|
164
|
+
if (event.cancelBubble) break;
|
165
|
+
}
|
166
|
+
|
167
|
+
if (false !== event.returnValue &&
|
168
|
+
'function' == typeof this.__events[event.type].defaultHandler)
|
169
|
+
{
|
170
|
+
this.__events[event.type].defaultHandler(event);
|
171
|
+
}
|
172
|
+
};
|
173
|
+
|
174
|
+
/**
|
175
|
+
*
|
176
|
+
* @param {object} object
|
177
|
+
* @param {string} type
|
178
|
+
*/
|
179
|
+
function WebSocket_FireEvent(object, type) {
|
180
|
+
return function(data) {
|
181
|
+
var event = new WebSocketEvent();
|
182
|
+
event.initEvent(type, true, true);
|
183
|
+
event.target = event.currentTarget = object;
|
184
|
+
for (var key in data) {
|
185
|
+
event[key] = data[key];
|
186
|
+
}
|
187
|
+
object.dispatchEvent(event, arguments);
|
188
|
+
};
|
189
|
+
}
|
190
|
+
|
191
|
+
/**
|
192
|
+
* Basic implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface">DOM 2 EventInterface</a>}
|
193
|
+
*
|
194
|
+
* @class
|
195
|
+
* @constructor
|
196
|
+
*/
|
197
|
+
function WebSocketEvent(){}
|
198
|
+
|
199
|
+
/**
|
200
|
+
*
|
201
|
+
* @type boolean
|
202
|
+
*/
|
203
|
+
WebSocketEvent.prototype.cancelable = true;
|
204
|
+
|
205
|
+
/**
|
206
|
+
*
|
207
|
+
* @type boolean
|
208
|
+
*/
|
209
|
+
WebSocketEvent.prototype.cancelBubble = false;
|
210
|
+
|
211
|
+
/**
|
212
|
+
*
|
213
|
+
* @return void
|
214
|
+
*/
|
215
|
+
WebSocketEvent.prototype.preventDefault = function() {
|
216
|
+
if (this.cancelable) {
|
217
|
+
this.returnValue = false;
|
218
|
+
}
|
219
|
+
};
|
220
|
+
|
221
|
+
/**
|
222
|
+
*
|
223
|
+
* @return void
|
224
|
+
*/
|
225
|
+
WebSocketEvent.prototype.stopPropagation = function() {
|
226
|
+
this.cancelBubble = true;
|
227
|
+
};
|
228
|
+
|
229
|
+
/**
|
230
|
+
*
|
231
|
+
* @param {string} eventTypeArg
|
232
|
+
* @param {boolean} canBubbleArg
|
233
|
+
* @param {boolean} cancelableArg
|
234
|
+
* @return void
|
235
|
+
*/
|
236
|
+
WebSocketEvent.prototype.initEvent = function(eventTypeArg, canBubbleArg, cancelableArg) {
|
237
|
+
this.type = eventTypeArg;
|
238
|
+
this.cancelable = cancelableArg;
|
239
|
+
this.timeStamp = new Date();
|
240
|
+
};
|
241
|
+
|
242
|
+
|
243
|
+
WebSocket.CONNECTING = 0;
|
244
|
+
WebSocket.OPEN = 1;
|
245
|
+
WebSocket.CLOSED = 2;
|
246
|
+
|
247
|
+
WebSocket.__tasks = [];
|
248
|
+
|
249
|
+
WebSocket.__initialize = function() {
|
250
|
+
if (!WebSocket.__swfLocation) {
|
251
|
+
//console.error("[WebSocket] set WebSocket.__swfLocation to location of WebSocketMain.swf");
|
252
|
+
//return;
|
253
|
+
WebSocket.__swfLocation = "js/WebSocketMain.swf";
|
254
|
+
}
|
255
|
+
var container = document.createElement("div");
|
256
|
+
container.id = "webSocketContainer";
|
257
|
+
// Puts the Flash out of the window. Note that we cannot use display: none or visibility: hidden
|
258
|
+
// here because it prevents Flash from loading at least in IE.
|
259
|
+
container.style.position = "absolute";
|
260
|
+
container.style.left = "-100px";
|
261
|
+
container.style.top = "-100px";
|
262
|
+
var holder = document.createElement("div");
|
263
|
+
holder.id = "webSocketFlash";
|
264
|
+
container.appendChild(holder);
|
265
|
+
document.body.appendChild(container);
|
266
|
+
swfobject.embedSWF(
|
267
|
+
WebSocket.__swfLocation, "webSocketFlash", "8", "8", "9.0.0",
|
268
|
+
null, {bridgeName: "webSocket"}, null, null,
|
269
|
+
function(e) {
|
270
|
+
if (!e.success) console.error("[WebSocket] swfobject.embedSWF failed");
|
271
|
+
}
|
272
|
+
);
|
273
|
+
FABridge.addInitializationCallback("webSocket", function() {
|
274
|
+
try {
|
275
|
+
//console.log("[WebSocket] FABridge initializad");
|
276
|
+
WebSocket.__flash = FABridge.webSocket.root();
|
277
|
+
WebSocket.__flash.setCallerUrl(location.href);
|
278
|
+
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
|
279
|
+
WebSocket.__tasks[i]();
|
280
|
+
}
|
281
|
+
WebSocket.__tasks = [];
|
282
|
+
} catch (e) {
|
283
|
+
console.error("[WebSocket] " + e.toString());
|
284
|
+
}
|
285
|
+
});
|
286
|
+
};
|
287
|
+
|
288
|
+
WebSocket.__addTask = function(task) {
|
289
|
+
if (WebSocket.__flash) {
|
290
|
+
task();
|
291
|
+
} else {
|
292
|
+
WebSocket.__tasks.push(task);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
// called from Flash
|
297
|
+
function webSocketLog(message) {
|
298
|
+
console.log(decodeURIComponent(message));
|
299
|
+
}
|
300
|
+
|
301
|
+
// called from Flash
|
302
|
+
function webSocketError(message) {
|
303
|
+
console.error(decodeURIComponent(message));
|
304
|
+
}
|
305
|
+
|
306
|
+
if (window.addEventListener) {
|
307
|
+
window.addEventListener("load", WebSocket.__initialize, false);
|
308
|
+
} else {
|
309
|
+
window.attachEvent("onload", WebSocket.__initialize);
|
310
|
+
}
|
311
|
+
|
312
|
+
})();
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'em-websocket'
|
2
|
+
# requires the twitter-stream gem
|
3
|
+
require 'twitter/json_stream'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
#
|
7
|
+
# broadcast all ruby related tweets to all connected users!
|
8
|
+
#
|
9
|
+
|
10
|
+
username = ARGV.shift
|
11
|
+
password = ARGV.shift
|
12
|
+
raise "need username and password" if !username or !password
|
13
|
+
|
14
|
+
EventMachine.run {
|
15
|
+
@channel = EM::Channel.new
|
16
|
+
|
17
|
+
@twitter = Twitter::JSONStream.connect(
|
18
|
+
:path => '/1/statuses/filter.json?track=ruby',
|
19
|
+
:auth => "#{username}:#{password}",
|
20
|
+
:ssl => true
|
21
|
+
)
|
22
|
+
|
23
|
+
@twitter.each_item do |status|
|
24
|
+
status = JSON.parse(status)
|
25
|
+
@channel.push "#{status['user']['screen_name']}: #{status['text']}"
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => true) do |ws|
|
30
|
+
|
31
|
+
ws.onopen {
|
32
|
+
sid = @channel.subscribe { |msg| ws.send msg }
|
33
|
+
@channel.push "#{sid} connected!"
|
34
|
+
|
35
|
+
ws.onmessage { |msg|
|
36
|
+
@channel.push "<#{sid}>: #{msg}"
|
37
|
+
}
|
38
|
+
|
39
|
+
ws.onclose {
|
40
|
+
@channel.unsubscribe(sid)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
puts "Server started"
|
47
|
+
}
|
data/examples/test.html
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<script src='js/swfobject.js'></script>
|
4
|
+
<script src='js/FABridge.js'></script>
|
5
|
+
<script src='js/web_socket.js'></script>
|
6
|
+
<script>
|
7
|
+
function init() {
|
8
|
+
function debug(string) {
|
9
|
+
var element = document.getElementById("debug");
|
10
|
+
var p = document.createElement("p");
|
11
|
+
p.appendChild(document.createTextNode(string));
|
12
|
+
element.appendChild(p);
|
13
|
+
}
|
14
|
+
|
15
|
+
var Socket = "MozWebSocket" in window ? MozWebSocket : WebSocket;
|
16
|
+
var ws = new Socket("ws://localhost:8080/");
|
17
|
+
ws.onmessage = function(evt) { debug("Message: " + evt.data); };
|
18
|
+
ws.onclose = function() { debug("socket closed"); };
|
19
|
+
ws.onopen = function() {
|
20
|
+
debug("connected...");
|
21
|
+
ws.send("hello server");
|
22
|
+
ws.send("hello again");
|
23
|
+
};
|
24
|
+
};
|
25
|
+
</script>
|
26
|
+
</head>
|
27
|
+
<body onload="init();">
|
28
|
+
<div id="debug"></div>
|
29
|
+
</body>
|
30
|
+
</html>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
class ClientConnection < EventMachine::WebSocket::Connection
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
super
|
9
|
+
@handler = Handler08.new( self, options, options[:debug] )
|
10
|
+
@handler.run_client
|
11
|
+
end
|
12
|
+
|
13
|
+
def dispatch(data)
|
14
|
+
# server's handshake response
|
15
|
+
@handler.client_handle_server_handshake_response(data)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
module Close06
|
4
|
+
def close_websocket(code, body)
|
5
|
+
if code
|
6
|
+
close_data = [code].pack('n')
|
7
|
+
close_data << body if body
|
8
|
+
send_frame(:close, close_data)
|
9
|
+
else
|
10
|
+
send_frame(:close, '')
|
11
|
+
end
|
12
|
+
@state = :closing
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
module WebSocket
|
5
|
+
class Connection < EventMachine::Connection
|
6
|
+
include Debugger
|
7
|
+
|
8
|
+
# define WebSocket callbacks
|
9
|
+
def onopen(&blk); @onopen = blk; end
|
10
|
+
def onclose(&blk); @onclose = blk; end
|
11
|
+
def onerror(&blk); @onerror = blk; end
|
12
|
+
def onmessage(&blk); @onmessage = blk; end
|
13
|
+
|
14
|
+
def trigger_on_message(msg, type=:text)
|
15
|
+
if @onmessage
|
16
|
+
if @onmessage.arity == 2
|
17
|
+
@onmessage.call msg, type
|
18
|
+
else
|
19
|
+
@onmessage.call msg
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def trigger_on_open
|
24
|
+
@onopen.call if @onopen
|
25
|
+
end
|
26
|
+
def trigger_on_close
|
27
|
+
@onclose.call if @onclose
|
28
|
+
end
|
29
|
+
def trigger_on_error(reason)
|
30
|
+
return false unless @onerror
|
31
|
+
@onerror.call(reason)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(options)
|
36
|
+
@options = options
|
37
|
+
@debug = options[:debug] || false
|
38
|
+
@secure = options[:secure] || false
|
39
|
+
@tls_options = options[:tls_options] || {}
|
40
|
+
@data = ''
|
41
|
+
|
42
|
+
debug [:initialize]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Use this method to close the websocket connection cleanly
|
46
|
+
# This sends a close frame and waits for acknowlegement before closing
|
47
|
+
# the connection
|
48
|
+
def close_websocket(code = nil, body = nil)
|
49
|
+
if code && !(4000..4999).include?(code)
|
50
|
+
raise "Application code may only use codes in the range 4000-4999"
|
51
|
+
end
|
52
|
+
|
53
|
+
# If code not defined then set to 1000 (normal closure)
|
54
|
+
code ||= 1000
|
55
|
+
|
56
|
+
close_websocket_private(code, body)
|
57
|
+
end
|
58
|
+
|
59
|
+
def post_init
|
60
|
+
start_tls(@tls_options) if @secure
|
61
|
+
end
|
62
|
+
|
63
|
+
def receive_data(data)
|
64
|
+
debug [:receive_data, data]
|
65
|
+
|
66
|
+
if @handler and state != :handshake #TODO a different way to catch client sent handshake, is waiting state
|
67
|
+
@handler.receive_data(data)
|
68
|
+
else
|
69
|
+
dispatch(data)
|
70
|
+
end
|
71
|
+
rescue HandshakeError => e
|
72
|
+
debug [:error, e]
|
73
|
+
trigger_on_error(e)
|
74
|
+
# Errors during the handshake require the connection to be aborted
|
75
|
+
abort
|
76
|
+
rescue WebSocketError => e
|
77
|
+
debug [:error, e]
|
78
|
+
trigger_on_error(e)
|
79
|
+
close_websocket_private(1002) # 1002 indicates a protocol error
|
80
|
+
rescue => e
|
81
|
+
debug [:error, e]
|
82
|
+
# These are application errors - raise unless onerror defined
|
83
|
+
trigger_on_error(e) || raise(e)
|
84
|
+
# There is no code defined for application errors, so use 3000
|
85
|
+
# (which is reserved for frameworks)
|
86
|
+
close_websocket_private(3000)
|
87
|
+
end
|
88
|
+
|
89
|
+
def unbind
|
90
|
+
debug [:unbind, :connection]
|
91
|
+
|
92
|
+
@handler.unbind if @handler
|
93
|
+
rescue => e
|
94
|
+
debug [:error, e]
|
95
|
+
# These are application errors - raise unless onerror defined
|
96
|
+
trigger_on_error(e) || raise(e)
|
97
|
+
end
|
98
|
+
|
99
|
+
def dispatch(data)
|
100
|
+
if data.match(/\A<policy-file-request\s*\/>/)
|
101
|
+
send_flash_cross_domain_file
|
102
|
+
return false
|
103
|
+
else
|
104
|
+
debug [:inbound_headers, data]
|
105
|
+
@data << data
|
106
|
+
@handler = HandlerFactory.build(self, @data, @secure, @debug)
|
107
|
+
unless @handler
|
108
|
+
# The whole header has not been received yet.
|
109
|
+
return false
|
110
|
+
end
|
111
|
+
@data = nil
|
112
|
+
@handler.run_server
|
113
|
+
return true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def send_flash_cross_domain_file
|
118
|
+
file = '<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>'
|
119
|
+
debug [:cross_domain, file]
|
120
|
+
send_data file
|
121
|
+
|
122
|
+
# handle the cross-domain request transparently
|
123
|
+
# no need to notify the user about this connection
|
124
|
+
@onclose = nil
|
125
|
+
close_connection_after_writing
|
126
|
+
end
|
127
|
+
|
128
|
+
def send(data, type=:text)
|
129
|
+
if type == :text
|
130
|
+
# If we're using Ruby 1.9, be pedantic about encodings
|
131
|
+
if data.respond_to?(:force_encoding)
|
132
|
+
# Also accept ascii only data in other encodings for convenience
|
133
|
+
unless (data.encoding == Encoding.find("UTF-8") && data.valid_encoding?) || data.ascii_only?
|
134
|
+
raise WebSocketError, "Data sent to WebSocket must be valid UTF-8 but was #{data.encoding} (valid: #{data.valid_encoding?})"
|
135
|
+
end
|
136
|
+
# This labels the encoding as binary so that it can be combined with
|
137
|
+
# the BINARY framing
|
138
|
+
data.force_encoding("BINARY")
|
139
|
+
else
|
140
|
+
# TODO: Check that data is valid UTF-8
|
141
|
+
end
|
142
|
+
|
143
|
+
if @handler
|
144
|
+
@handler.send_text_frame(data)
|
145
|
+
else
|
146
|
+
raise WebSocketError, "Cannot send data before onopen callback"
|
147
|
+
end
|
148
|
+
else
|
149
|
+
if @handler
|
150
|
+
@handler.send_frame(type, data)
|
151
|
+
else
|
152
|
+
raise WebSocketError, "Cannot send data before onopen callback"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def request
|
158
|
+
@handler ? @handler.request : {}
|
159
|
+
end
|
160
|
+
|
161
|
+
def state
|
162
|
+
@handler ? @handler.state : :handshake
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
# As definited in draft 06 7.2.2, some failures require that the server
|
168
|
+
# abort the websocket connection rather than close cleanly
|
169
|
+
def abort
|
170
|
+
close_connection
|
171
|
+
end
|
172
|
+
|
173
|
+
def close_websocket_private(code, body = nil)
|
174
|
+
if @handler
|
175
|
+
debug [:closing, code]
|
176
|
+
@handler.close_websocket(code, body)
|
177
|
+
else
|
178
|
+
# The handshake hasn't completed - should be safe to terminate
|
179
|
+
abort
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|