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.
Files changed (61) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG.rdoc +80 -0
  3. data/Gemfile +3 -0
  4. data/README.md +98 -0
  5. data/Rakefile +11 -0
  6. data/em-websocket.gemspec +27 -0
  7. data/examples/echo.rb +8 -0
  8. data/examples/flash_policy_file_server.rb +21 -0
  9. data/examples/js/FABridge.js +604 -0
  10. data/examples/js/WebSocketMain.swf +0 -0
  11. data/examples/js/swfobject.js +4 -0
  12. data/examples/js/web_socket.js +312 -0
  13. data/examples/multicast.rb +47 -0
  14. data/examples/test.html +30 -0
  15. data/lib/em-websocket/client_connection.rb +19 -0
  16. data/lib/em-websocket/close03.rb +11 -0
  17. data/lib/em-websocket/close05.rb +11 -0
  18. data/lib/em-websocket/close06.rb +16 -0
  19. data/lib/em-websocket/close75.rb +10 -0
  20. data/lib/em-websocket/connection.rb +184 -0
  21. data/lib/em-websocket/debugger.rb +17 -0
  22. data/lib/em-websocket/framing03.rb +167 -0
  23. data/lib/em-websocket/framing04.rb +15 -0
  24. data/lib/em-websocket/framing05.rb +168 -0
  25. data/lib/em-websocket/framing07.rb +180 -0
  26. data/lib/em-websocket/framing76.rb +114 -0
  27. data/lib/em-websocket/handler.rb +56 -0
  28. data/lib/em-websocket/handler03.rb +10 -0
  29. data/lib/em-websocket/handler05.rb +10 -0
  30. data/lib/em-websocket/handler06.rb +10 -0
  31. data/lib/em-websocket/handler07.rb +10 -0
  32. data/lib/em-websocket/handler08.rb +10 -0
  33. data/lib/em-websocket/handler13.rb +10 -0
  34. data/lib/em-websocket/handler75.rb +9 -0
  35. data/lib/em-websocket/handler76.rb +12 -0
  36. data/lib/em-websocket/handler_factory.rb +107 -0
  37. data/lib/em-websocket/handshake04.rb +75 -0
  38. data/lib/em-websocket/handshake75.rb +21 -0
  39. data/lib/em-websocket/handshake76.rb +71 -0
  40. data/lib/em-websocket/masking04.rb +63 -0
  41. data/lib/em-websocket/message_processor_03.rb +38 -0
  42. data/lib/em-websocket/message_processor_06.rb +52 -0
  43. data/lib/em-websocket/version.rb +5 -0
  44. data/lib/em-websocket/websocket.rb +45 -0
  45. data/lib/em-websocket.rb +23 -0
  46. data/lib/sonixlabs-em-websocket.rb +1 -0
  47. data/spec/helper.rb +146 -0
  48. data/spec/integration/client_examples.rb +48 -0
  49. data/spec/integration/common_spec.rb +118 -0
  50. data/spec/integration/draft03_spec.rb +270 -0
  51. data/spec/integration/draft05_spec.rb +48 -0
  52. data/spec/integration/draft06_spec.rb +88 -0
  53. data/spec/integration/draft13_spec.rb +75 -0
  54. data/spec/integration/draft75_spec.rb +117 -0
  55. data/spec/integration/draft76_spec.rb +230 -0
  56. data/spec/integration/shared_examples.rb +91 -0
  57. data/spec/unit/framing_spec.rb +325 -0
  58. data/spec/unit/handler_spec.rb +147 -0
  59. data/spec/unit/masking_spec.rb +27 -0
  60. data/spec/unit/message_processor_spec.rb +36 -0
  61. 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
+ }
@@ -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,11 @@
1
+ module EventMachine
2
+ module WebSocket
3
+ module Close03
4
+ def close_websocket(code, body)
5
+ # TODO: Ideally send body data and check that it matches in ack
6
+ send_frame(:close, '')
7
+ @state = :closing
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module EventMachine
2
+ module WebSocket
3
+ module Close05
4
+ def close_websocket(code, body)
5
+ # TODO: Ideally send body data and check that it matches in ack
6
+ send_frame(:close, "\x53")
7
+ @state = :closing
8
+ end
9
+ end
10
+ end
11
+ 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,10 @@
1
+ module EventMachine
2
+ module WebSocket
3
+ module Close75
4
+ def close_websocket(code, body)
5
+ @state = :closed
6
+ @connection.close_connection_after_writing
7
+ end
8
+ end
9
+ end
10
+ 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
@@ -0,0 +1,17 @@
1
+ module EventMachine
2
+ module WebSocket
3
+ module Debugger
4
+
5
+ private
6
+
7
+ def debug(*data)
8
+ if @debug
9
+ require 'pp'
10
+ pp data
11
+ puts
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end