sonixlabs-em-websocket 0.3.7
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/.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
|