websocket-rack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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