websocket-rack 0.1.0

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.
@@ -0,0 +1,115 @@
1
+ # encoding: BINARY
2
+
3
+ module Rack
4
+ module WebSocket
5
+ module Framing76
6
+
7
+ # Set the max frame lenth to very high value (10MB) until there is a
8
+ # limit specified in the spec to protect against malicious attacks
9
+ MAXIMUM_FRAME_LENGTH = 10 * 1024 * 1024
10
+
11
+ def initialize_framing
12
+ @data = ''
13
+ end
14
+
15
+ def process_data(newdata)
16
+ debug [:message, @data]
17
+
18
+ # This algorithm comes straight from the spec
19
+ # http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#section-5.3
20
+
21
+ error = false
22
+
23
+ while !error
24
+ return if @data.size == 0
25
+
26
+ pointer = 0
27
+ frame_type = @data.getbyte(pointer)
28
+ pointer += 1
29
+
30
+ if (frame_type & 0x80) == 0x80
31
+ # If the high-order bit of the /frame type/ byte is set
32
+ length = 0
33
+
34
+ loop do
35
+ return false if !@data.getbyte(pointer)
36
+ b = @data.getbyte(pointer)
37
+ pointer += 1
38
+ b_v = b & 0x7F
39
+ length = length * 128 + b_v
40
+ break unless (b & 0x80) == 0x80
41
+ end
42
+
43
+ # Addition to the spec to protect against malicious requests
44
+ if length > MAXIMUM_FRAME_LENGTH
45
+ @connection.close_with_error(DataError.new("Frame length too long (#{length} bytes)"))
46
+ return false
47
+ end
48
+
49
+ if @data.getbyte(pointer+length-1) == nil
50
+ debug [:buffer_incomplete, @data.inspect]
51
+ # Incomplete data - leave @data to accumulate
52
+ error = true
53
+ else
54
+ # Straight from spec - I'm sure this isn't crazy...
55
+ # 6. Read /length/ bytes.
56
+ # 7. Discard the read bytes.
57
+ @data = @data[(pointer+length)..-1]
58
+
59
+ # If the /frame type/ is 0xFF and the /length/ was 0, then close
60
+ if length == 0
61
+ @connection.send_data("\xff\x00")
62
+ @state = :closing
63
+ @connection.close_connection_after_writing
64
+ else
65
+ error = true
66
+ end
67
+ end
68
+ else
69
+ # If the high-order bit of the /frame type/ byte is _not_ set
70
+
71
+ if @data.getbyte(0) != 0x00
72
+ # Close the connection since this buffer can never match
73
+ @connection.close_with_error(DataError.new("Invalid frame received"))
74
+ end
75
+
76
+ # Addition to the spec to protect against malicious requests
77
+ if @data.size > MAXIMUM_FRAME_LENGTH
78
+ @connection.close_with_error(DataError.new("Frame length too long (#{@data.size} bytes)"))
79
+ return false
80
+ end
81
+
82
+ # Optimization to avoid calling slice! unnecessarily
83
+ error = true and next unless newdata =~ /\xff/
84
+
85
+ msg = @data.slice!(/\A\x00[^\xff]*\xff/)
86
+ if msg
87
+ msg.gsub!(/\A\x00|\xff\z/, '')
88
+ if @state == :closing
89
+ debug [:ignored_message, msg]
90
+ else
91
+ msg.force_encoding('UTF-8') if msg.respond_to?(:force_encoding)
92
+ @connection.trigger_on_message(msg)
93
+ end
94
+ else
95
+ error = true
96
+ end
97
+ end
98
+ end
99
+
100
+ false
101
+ end
102
+
103
+ # frames need to start with 0x00-0x7f byte and end with
104
+ # an 0xFF byte. Per spec, we can also set the first
105
+ # byte to a value betweent 0x80 and 0xFF, followed by
106
+ # a leading length indicator
107
+ def send_text_frame(data)
108
+ ary = ["\x00", data, "\xff"]
109
+ ary.collect{ |s| s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) }
110
+ @connection.send_data(ary.join)
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,43 @@
1
+ module Rack
2
+ module WebSocket
3
+ class Handler
4
+ include Debugger
5
+
6
+ attr_reader :request, :state
7
+
8
+ def initialize(connection, request, debug = false)
9
+ @connection, @request = connection, request
10
+ @debug = debug
11
+ @state = :handshake
12
+ initialize_framing
13
+ end
14
+
15
+ def run
16
+ @connection.send_data handshake
17
+ @state = :connected
18
+ @connection.trigger_on_open
19
+ end
20
+
21
+ # Handshake response
22
+ def handshake
23
+ # Implemented in subclass
24
+ end
25
+
26
+ def receive_data(data)
27
+ @data << data
28
+ process_data(data)
29
+ end
30
+
31
+ def close_websocket
32
+ # Unless redefined in a subclass, just close the connection
33
+ @state = :closed
34
+ @connection.close_connection_after_writing
35
+ end
36
+
37
+ def unbind
38
+ @state = :closed
39
+ @connection.trigger_on_close
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,14 @@
1
+ module Rack
2
+ module WebSocket
3
+ class Handler03 < Handler
4
+ include Handshake76
5
+ include Framing03
6
+
7
+ def close_websocket
8
+ # TODO: Should we send data and check the response matches?
9
+ send_frame(:close, '')
10
+ @state = :closing
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ module Rack
2
+ module WebSocket
3
+ class Handler75 < Handler
4
+ include Handshake75
5
+ include Framing76
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module Rack
2
+ module WebSocket
3
+ class Handler76 < Handler
4
+ include Handshake76
5
+ include Framing76
6
+
7
+ # "\377\000" is octet version and "\xff\x00" is hex version
8
+ TERMINATE_STRING = "\xff\x00"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,61 @@
1
+ module Rack
2
+ module WebSocket
3
+ class HandlerFactory
4
+
5
+ def self.build(connection, data, debug = false)
6
+ request = Rack::Request.new(data)
7
+
8
+ unless request.env['rack.input'].nil?
9
+ request.env['rack.input'].rewind
10
+ remains = request.env['rack.input'].read
11
+ else
12
+ # The whole header has not been received yet.
13
+ return nil
14
+ end
15
+
16
+ unless request.get?
17
+ raise HandshakeError, "Must be GET request"
18
+ end
19
+
20
+ version = request.env['HTTP_SEC_WEBSOCKET_KEY1'] ? 76 : 75
21
+ case version
22
+ when 75
23
+ if !remains.empty?
24
+ raise HandshakeError, "Extra bytes after header"
25
+ end
26
+ when 76
27
+ if remains.length < 8
28
+ # The whole third-key has not been received yet.
29
+ return nil
30
+ elsif remains.length > 8
31
+ raise HandshakeError, "Extra bytes after third key"
32
+ end
33
+ request.env['HTTP_THIRD_KEY'] = remains
34
+ else
35
+ raise WebSocketError, "Must not happen"
36
+ end
37
+
38
+ unless request.env['HTTP_CONNECTION'] == 'Upgrade' and request.env['HTTP_UPGRADE'] == 'WebSocket'
39
+ raise HandshakeError, "Connection and Upgrade headers required"
40
+ end
41
+
42
+ # transform headers
43
+ request.env['rack.url_scheme'] = (request.scheme == 'https' ? "wss" : "ws")
44
+
45
+ if version = request.env['HTTP_SEC_WEBSOCKET_DRAFT']
46
+ if version == '1' || version == '2' || version == '3'
47
+ # We'll use handler03 - I believe they're all compatible
48
+ Handler03.new(connection, request, debug)
49
+ else
50
+ # According to spec should abort the connection
51
+ raise WebSocketError, "Unknown draft version: #{version}"
52
+ end
53
+ elsif request.env['HTTP_SEC_WEBSOCKET_KEY1']
54
+ Handler76.new(connection, request, debug)
55
+ else
56
+ Handler75.new(connection, request, debug)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ module Rack
2
+ module WebSocket
3
+ module Handshake75
4
+ def handshake
5
+ location = "#{request.env['rack.url_scheme']}://#{request.host}"
6
+ location << ":#{request.port}" if request.port > 0
7
+ location << request.path
8
+
9
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
10
+ upgrade << "Upgrade: WebSocket\r\n"
11
+ upgrade << "Connection: Upgrade\r\n"
12
+ upgrade << "WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
13
+ upgrade << "WebSocket-Location: #{location}\r\n\r\n"
14
+
15
+ debug [:upgrade_headers, upgrade]
16
+
17
+ return upgrade
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,71 @@
1
+ require 'digest/md5'
2
+
3
+ module Rack
4
+ module WebSocket
5
+ module Handshake76
6
+ def handshake
7
+ challenge_response = solve_challenge(
8
+ request.env['HTTP_SEC_WEBSOCKET_KEY1'],
9
+ request.env['HTTP_SEC_WEBSOCKET_KEY2'],
10
+ request.env['HTTP_THIRD_KEY']
11
+ )
12
+
13
+ location = "#{request.env['rack.url_scheme']}://#{request.host}"
14
+ location << ":#{request.port}" if request.port > 0
15
+ location << request.path
16
+
17
+ upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
18
+ upgrade << "Upgrade: WebSocket\r\n"
19
+ upgrade << "Connection: Upgrade\r\n"
20
+ upgrade << "Sec-WebSocket-Location: #{location}\r\n"
21
+ upgrade << "Sec-WebSocket-Origin: #{request.env['HTTP_ORIGIN']}\r\n"
22
+ if protocol = request.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
23
+ validate_protocol!(protocol)
24
+ upgrade << "Sec-WebSocket-Protocol: #{protocol}\r\n"
25
+ end
26
+ upgrade << "\r\n"
27
+ upgrade << challenge_response
28
+
29
+ debug [:upgrade_headers, upgrade]
30
+
31
+ return upgrade
32
+ end
33
+
34
+ private
35
+
36
+ def solve_challenge(first, second, third)
37
+ # Refer to 5.2 4-9 of the draft 76
38
+ sum = [numbers_over_spaces(first)].pack("N*") +
39
+ [numbers_over_spaces(second)].pack("N*") +
40
+ third
41
+ Digest::MD5.digest(sum)
42
+ end
43
+
44
+ def numbers_over_spaces(string)
45
+ numbers = string.scan(/[0-9]/).join.to_i
46
+
47
+ spaces = string.scan(/ /).size
48
+ # As per 5.2.5, abort the connection if spaces are zero.
49
+ raise HandshakeError, "Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack" if spaces == 0
50
+
51
+ # As per 5.2.6, abort if numbers is not an integral multiple of spaces
52
+ if numbers % spaces != 0
53
+ raise HandshakeError, "Invalid Key #{string.inspect}"
54
+ end
55
+
56
+ quotient = numbers / spaces
57
+
58
+ if quotient > 2**32-1
59
+ raise HandshakeError, "Challenge computation out of range for key #{string.inspect}"
60
+ end
61
+
62
+ return quotient
63
+ end
64
+
65
+ def validate_protocol!(protocol)
66
+ raise HandshakeError, "Invalid WebSocket-Protocol: empty" if protocol.empty?
67
+ # TODO: Validate characters
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,40 @@
1
+ require 'rack'
2
+
3
+ module Rack
4
+ module WebSocket
5
+ VERSION = "0.1.0"
6
+ ROOT_PATH = ::File.expand_path(::File.dirname(__FILE__))
7
+
8
+ class WebSocketError < RuntimeError; end
9
+ class HandshakeError < WebSocketError; end
10
+ class DataError < WebSocketError; end
11
+
12
+ autoload :Application, "#{ROOT_PATH}/websocket/application"
13
+ autoload :Connection, "#{ROOT_PATH}/websocket/connection"
14
+ autoload :Debugger, "#{ROOT_PATH}/websocket/debugger"
15
+ autoload :Framing03, "#{ROOT_PATH}/websocket/framing03"
16
+ autoload :Framing76, "#{ROOT_PATH}/websocket/framing76"
17
+ autoload :Handler, "#{ROOT_PATH}/websocket/handler"
18
+ autoload :Handler03, "#{ROOT_PATH}/websocket/handler03"
19
+ autoload :Handler75, "#{ROOT_PATH}/websocket/handler75"
20
+ autoload :Handler76, "#{ROOT_PATH}/websocket/handler76"
21
+ autoload :HandlerFactory, "#{ROOT_PATH}/websocket/handler_factory"
22
+ autoload :Handshake75, "#{ROOT_PATH}/websocket/handshake75"
23
+ autoload :Handshake76, "#{ROOT_PATH}/websocket/handshake76"
24
+
25
+ module Extensions
26
+ autoload :Thin, "#{ROOT_PATH}/websocket/extensions/thin"
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ ::Thin.send(:include, ::Rack::WebSocket::Extensions::Thin) if defined?(Thin)
33
+
34
+ unless ''.respond_to?(:getbyte)
35
+ class String
36
+ def getbyte(i)
37
+ self[i]
38
+ end
39
+ end
40
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+ require 'pp'
4
+ require 'stringio'
5
+
6
+ require 'rack/websocket'
7
+
8
+ Rspec.configure do |c|
9
+ c.mock_with :rspec
10
+ end
11
+
12
+ def format_request(r)
13
+ data = {}
14
+ data['REQUEST_METHOD'] = r[:method] if r[:method]
15
+ data['PATH_INFO'] = r[:path] if r[:path]
16
+ data['SERVER_PORT'] = r[:port] if r[:port] && r[:port] != 80
17
+ r[:headers].each do |key, value|
18
+ data['HTTP_' + key.upcase.gsub('-','_')] = value
19
+ end
20
+ data['rack.input'] = StringIO.new(r[:body]) if r[:body]
21
+ # data = "#{r[:method]} #{r[:path]} HTTP/1.1\r\n"
22
+ # header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
23
+ # data << [header_lines, '', r[:body]].join("\r\n")
24
+ data
25
+ end
26
+
27
+ def format_response(r)
28
+ data = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
29
+ header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
30
+ data << [header_lines, '', r[:body]].join("\r\n")
31
+ data
32
+ end
33
+
34
+ def handler(request, secure = false)
35
+ connection = Object.new
36
+ secure_hash = secure ? {'rack.url_scheme' => 'https'} : {}
37
+ Rack::WebSocket::HandlerFactory.build(connection, format_request(request).merge(secure_hash))
38
+ end
39
+
40
+ RSpec::Matchers.define :send_handshake do |response|
41
+ match do |actual|
42
+ actual.handshake.lines.sort == format_response(response).lines.sort
43
+ end
44
+ end
@@ -0,0 +1,252 @@
1
+ # require 'helper'
2
+ #
3
+ # describe "draft03" do
4
+ # before :each do
5
+ # @request = {
6
+ # :port => 80,
7
+ # :method => "GET",
8
+ # :path => "/demo",
9
+ # :headers => {
10
+ # 'Host' => 'example.com',
11
+ # 'Connection' => 'Upgrade',
12
+ # 'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
13
+ # 'Sec-WebSocket-Protocol' => 'sample',
14
+ # 'Upgrade' => 'WebSocket',
15
+ # 'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
16
+ # 'Origin' => 'http://example.com',
17
+ # 'Sec-WebSocket-Draft' => '3'
18
+ # },
19
+ # :body => '^n:ds[4U'
20
+ # }
21
+ #
22
+ # @response = {
23
+ # :headers => {
24
+ # "Upgrade" => "WebSocket",
25
+ # "Connection" => "Upgrade",
26
+ # "Sec-WebSocket-Location" => "ws://example.com/demo",
27
+ # "Sec-WebSocket-Origin" => "http://example.com",
28
+ # "Sec-WebSocket-Protocol" => "sample"
29
+ # },
30
+ # :body => "8jKS\'y:G*Co,Wxa-"
31
+ # }
32
+ # end
33
+ #
34
+ # # These examples are straight from the spec
35
+ # # http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03#section-4.6
36
+ # describe "examples from the spec" do
37
+ # it "should accept a single-frame text message" do
38
+ # EM.run do
39
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
40
+ # ws.onmessage { |msg|
41
+ # msg.should == 'Hello'
42
+ # EM.stop
43
+ # }
44
+ # }
45
+ #
46
+ # # Create a fake client which sends draft 76 handshake
47
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
48
+ # connection.send_data(format_request(@request))
49
+ #
50
+ # # Send frame
51
+ # connection.onopen = lambda {
52
+ # connection.send_data("\x04\x05Hello")
53
+ # }
54
+ # end
55
+ # end
56
+ #
57
+ # it "should accept a fragmented text message" do
58
+ # EM.run do
59
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
60
+ # ws.onmessage { |msg|
61
+ # msg.should == 'Hello'
62
+ # EM.stop
63
+ # }
64
+ # }
65
+ #
66
+ # # Create a fake client which sends draft 76 handshake
67
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
68
+ # connection.send_data(format_request(@request))
69
+ #
70
+ # # Send frame
71
+ # connection.onopen = lambda {
72
+ # connection.send_data("\x84\x03Hel")
73
+ # connection.send_data("\x00\x02lo")
74
+ # }
75
+ # end
76
+ # end
77
+ #
78
+ # it "should accept a ping request and respond with the same body" do
79
+ # EM.run do
80
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws| }
81
+ #
82
+ # # Create a fake client which sends draft 76 handshake
83
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
84
+ # connection.send_data(format_request(@request))
85
+ #
86
+ # # Send frame
87
+ # connection.onopen = lambda {
88
+ # connection.send_data("\x02\x05Hello")
89
+ # }
90
+ #
91
+ # connection.onmessage = lambda { |frame|
92
+ # next if frame.nil?
93
+ # frame.should == "\x03\x05Hello"
94
+ # EM.stop
95
+ # }
96
+ # end
97
+ # end
98
+ #
99
+ # it "should accept a 256 bytes binary message in a single frame" do
100
+ # EM.run do
101
+ # data = "a" * 256
102
+ #
103
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
104
+ # ws.onmessage { |msg|
105
+ # msg.should == data
106
+ # EM.stop
107
+ # }
108
+ # }
109
+ #
110
+ # # Create a fake client which sends draft 76 handshake
111
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
112
+ # connection.send_data(format_request(@request))
113
+ #
114
+ # # Send frame
115
+ # connection.onopen = lambda {
116
+ # connection.send_data("\x05\x7E\x01\x00" + data)
117
+ # }
118
+ # end
119
+ # end
120
+ #
121
+ # it "should accept a 64KiB binary message in a single frame" do
122
+ # EM.run do
123
+ # data = "a" * 65536
124
+ #
125
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
126
+ # ws.onmessage { |msg|
127
+ # msg.should == data
128
+ # EM.stop
129
+ # }
130
+ # }
131
+ #
132
+ # # Create a fake client which sends draft 76 handshake
133
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
134
+ # connection.send_data(format_request(@request))
135
+ #
136
+ # # Send frame
137
+ # connection.onopen = lambda {
138
+ # connection.send_data("\x05\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data)
139
+ # }
140
+ # end
141
+ # end
142
+ # end
143
+ #
144
+ # describe "close handling" do
145
+ # it "should respond to a new close frame with a close frame" do
146
+ # EM.run do
147
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws| }
148
+ #
149
+ # # Create a fake client which sends draft 76 handshake
150
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
151
+ # connection.send_data(format_request(@request))
152
+ #
153
+ # # Send close frame
154
+ # connection.onopen = lambda {
155
+ # connection.send_data("\x01\x00")
156
+ # }
157
+ #
158
+ # # Check that close ack received
159
+ # connection.onmessage = lambda { |frame|
160
+ # frame.should == "\x01\x00"
161
+ # EM.stop
162
+ # }
163
+ # end
164
+ # end
165
+ #
166
+ # it "should close the connection on receiving a close acknowlegement" do
167
+ # EM.run do
168
+ # ack_received = false
169
+ #
170
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
171
+ # ws.onopen {
172
+ # # 2. Send a close frame
173
+ # EM.next_tick {
174
+ # ws.close_websocket
175
+ # }
176
+ # }
177
+ # }
178
+ #
179
+ # # 1. Create a fake client which sends draft 76 handshake
180
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
181
+ # connection.send_data(format_request(@request))
182
+ #
183
+ # # 3. Check that close frame recieved and acknowlege it
184
+ # connection.onmessage = lambda { |frame|
185
+ # frame.should == "\x01\x00"
186
+ # ack_received = true
187
+ # connection.send_data("\x01\x00")
188
+ # }
189
+ #
190
+ # # 4. Check that connection is closed _after_ the ack
191
+ # connection.onclose = lambda {
192
+ # ack_received.should == true
193
+ # EM.stop
194
+ # }
195
+ # end
196
+ # end
197
+ #
198
+ # it "should not allow data frame to be sent after close frame sent" do
199
+ # EM.run {
200
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
201
+ # ws.onopen {
202
+ # # 2. Send a close frame
203
+ # EM.next_tick {
204
+ # ws.close_websocket
205
+ # }
206
+ #
207
+ # # 3. Check that exception raised if I attempt to send more data
208
+ # EM.add_timer(0.1) {
209
+ # lambda {
210
+ # ws.send('hello world')
211
+ # }.should raise_error(EM::WebSocket::WebSocketError, 'Cannot send data frame since connection is closing')
212
+ # EM.stop
213
+ # }
214
+ # }
215
+ # }
216
+ #
217
+ # # 1. Create a fake client which sends draft 76 handshake
218
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
219
+ # connection.send_data(format_request(@request))
220
+ # }
221
+ # end
222
+ #
223
+ # it "should still respond to control frames after close frame sent" do
224
+ # EM.run {
225
+ # EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
226
+ # ws.onopen {
227
+ # # 2. Send a close frame
228
+ # EM.next_tick {
229
+ # ws.close_websocket
230
+ # }
231
+ # }
232
+ # }
233
+ #
234
+ # # 1. Create a fake client which sends draft 76 handshake
235
+ # connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
236
+ # connection.send_data(format_request(@request))
237
+ #
238
+ # connection.onmessage = lambda { |frame|
239
+ # if frame == "\x01\x00"
240
+ # # 3. After the close frame is received send a ping frame, but
241
+ # # don't respond with a close ack
242
+ # connection.send_data("\x02\x05Hello")
243
+ # else
244
+ # # 4. Check that the pong is received
245
+ # frame.should == "\x03\x05Hello"
246
+ # EM.stop
247
+ # end
248
+ # }
249
+ # }
250
+ # end
251
+ # end
252
+ # end