em-websocket 0.3.7 → 0.5.2
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.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +52 -0
- data/Gemfile +6 -0
- data/LICENCE +7 -0
- data/README.md +105 -40
- data/examples/echo.rb +22 -6
- data/examples/test.html +5 -6
- data/lib/em-websocket.rb +2 -1
- data/lib/em-websocket/close03.rb +3 -0
- data/lib/em-websocket/close05.rb +3 -0
- data/lib/em-websocket/close06.rb +3 -0
- data/lib/em-websocket/close75.rb +2 -1
- data/lib/em-websocket/connection.rb +154 -48
- data/lib/em-websocket/framing03.rb +3 -2
- data/lib/em-websocket/framing05.rb +3 -2
- data/lib/em-websocket/framing07.rb +16 -4
- data/lib/em-websocket/framing76.rb +1 -4
- data/lib/em-websocket/handler.rb +61 -15
- data/lib/em-websocket/handler03.rb +0 -1
- data/lib/em-websocket/handler05.rb +0 -1
- data/lib/em-websocket/handler06.rb +0 -1
- data/lib/em-websocket/handler07.rb +0 -1
- data/lib/em-websocket/handler08.rb +0 -1
- data/lib/em-websocket/handler13.rb +0 -1
- data/lib/em-websocket/handler76.rb +2 -0
- data/lib/em-websocket/handshake.rb +156 -0
- data/lib/em-websocket/handshake04.rb +18 -16
- data/lib/em-websocket/handshake75.rb +15 -8
- data/lib/em-websocket/handshake76.rb +15 -14
- data/lib/em-websocket/masking04.rb +3 -6
- data/lib/em-websocket/message_processor_03.rb +6 -3
- data/lib/em-websocket/message_processor_06.rb +30 -9
- data/lib/em-websocket/version.rb +1 -1
- data/lib/em-websocket/websocket.rb +24 -15
- data/spec/helper.rb +84 -51
- data/spec/integration/common_spec.rb +89 -69
- data/spec/integration/draft03_spec.rb +84 -56
- data/spec/integration/draft05_spec.rb +14 -12
- data/spec/integration/draft06_spec.rb +67 -7
- data/spec/integration/draft13_spec.rb +30 -19
- data/spec/integration/draft75_spec.rb +46 -40
- data/spec/integration/draft76_spec.rb +59 -45
- data/spec/integration/gte_03_examples.rb +42 -0
- data/spec/integration/shared_examples.rb +119 -0
- data/spec/unit/framing_spec.rb +24 -4
- data/spec/unit/handshake_spec.rb +216 -0
- data/spec/unit/masking_spec.rb +2 -0
- metadata +32 -86
- data/examples/flash_policy_file_server.rb +0 -21
- data/examples/js/FABridge.js +0 -604
- data/examples/js/WebSocketMain.swf +0 -0
- data/examples/js/swfobject.js +0 -4
- data/examples/js/web_socket.js +0 -312
- data/lib/em-websocket/handler_factory.rb +0 -109
- data/spec/unit/handler_spec.rb +0 -159
@@ -0,0 +1,42 @@
|
|
1
|
+
shared_examples_for "a WebSocket server drafts 3 and above" do
|
2
|
+
it "should force close connections after a timeout if close handshake is not sent by the client" do
|
3
|
+
em {
|
4
|
+
server_onerror_fired = false
|
5
|
+
server_onclose_fired = false
|
6
|
+
client_got_close_handshake = false
|
7
|
+
|
8
|
+
start_server(:close_timeout => 0.1) { |ws|
|
9
|
+
ws.onopen {
|
10
|
+
# 1: Send close handshake to client
|
11
|
+
EM.next_tick { ws.close(4999, "Close message") }
|
12
|
+
}
|
13
|
+
|
14
|
+
ws.onerror { |e|
|
15
|
+
# 3: Client should receive onerror
|
16
|
+
e.class.should == EM::WebSocket::WSProtocolError
|
17
|
+
e.message.should == "Close handshake un-acked after 0.1s, closing tcp connection"
|
18
|
+
server_onerror_fired = true
|
19
|
+
}
|
20
|
+
|
21
|
+
ws.onclose {
|
22
|
+
server_onclose_fired = true
|
23
|
+
}
|
24
|
+
}
|
25
|
+
start_client { |client|
|
26
|
+
client.onmessage { |msg|
|
27
|
+
# 2: Client does not respond to close handshake (the fake client
|
28
|
+
# doesn't understand them at all hence this is in onmessage)
|
29
|
+
msg.should =~ /Close message/ if version >= 6
|
30
|
+
client_got_close_handshake = true
|
31
|
+
}
|
32
|
+
|
33
|
+
client.onclose {
|
34
|
+
server_onerror_fired.should == true
|
35
|
+
server_onclose_fired.should == true
|
36
|
+
client_got_close_handshake.should == true
|
37
|
+
done
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
@@ -3,6 +3,125 @@
|
|
3
3
|
# These tests are run against all draft versions
|
4
4
|
#
|
5
5
|
shared_examples_for "a websocket server" do
|
6
|
+
it "should expose the protocol version" do
|
7
|
+
em {
|
8
|
+
start_server { |ws|
|
9
|
+
ws.onopen { |handshake|
|
10
|
+
handshake.protocol_version.should == version
|
11
|
+
done
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
start_client
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should expose the origin header" do
|
20
|
+
em {
|
21
|
+
start_server { |ws|
|
22
|
+
ws.onopen { |handshake|
|
23
|
+
handshake.origin.should == 'http://example.com'
|
24
|
+
done
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
start_client
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should expose the remote IP address" do
|
33
|
+
em {
|
34
|
+
start_server { |ws|
|
35
|
+
ws.onopen {
|
36
|
+
ws.remote_ip.should == "127.0.0.1"
|
37
|
+
done
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
start_client
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should send messages successfully" do
|
46
|
+
em {
|
47
|
+
start_server { |ws|
|
48
|
+
ws.onmessage { |message|
|
49
|
+
message.should == "hello server"
|
50
|
+
done
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
start_client { |client|
|
55
|
+
client.onopen {
|
56
|
+
client.send("hello server")
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should allow connection to be closed with valid close code" do
|
63
|
+
em {
|
64
|
+
start_server { |ws|
|
65
|
+
ws.onopen {
|
66
|
+
ws.close(4004, "Bye bye")
|
67
|
+
done
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
start_client
|
72
|
+
# TODO: Use a real client which understands how to respond to closing
|
73
|
+
# handshakes, sending the handshake currently untested
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should raise error if if invalid close code is used" do
|
78
|
+
em {
|
79
|
+
start_server { |ws|
|
80
|
+
ws.onopen {
|
81
|
+
lambda {
|
82
|
+
ws.close(2000)
|
83
|
+
}.should raise_error("Application code may only use codes from 1000, 3000-4999")
|
84
|
+
done
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
start_client
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should call onclose with was_clean set to false if connection closed without closing handshake by server" do
|
93
|
+
em {
|
94
|
+
start_server { |ws|
|
95
|
+
ws.onopen {
|
96
|
+
# Close tcp connection (no close handshake)
|
97
|
+
ws.close_connection
|
98
|
+
}
|
99
|
+
ws.onclose { |event|
|
100
|
+
event.should == {:code => 1006, :was_clean => false}
|
101
|
+
done
|
102
|
+
}
|
103
|
+
}
|
104
|
+
start_client
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should call onclose with was_clean set to false if connection closed without closing handshake by client" do
|
109
|
+
em {
|
110
|
+
start_server { |ws|
|
111
|
+
ws.onclose { |event|
|
112
|
+
event.should == {:code => 1006, :was_clean => false}
|
113
|
+
done
|
114
|
+
}
|
115
|
+
}
|
116
|
+
start_client { |client|
|
117
|
+
client.onopen {
|
118
|
+
# Close tcp connection (no close handshake)
|
119
|
+
client.close_connection
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
6
125
|
it "should call onerror if an application error raised in onopen" do
|
7
126
|
em {
|
8
127
|
start_server { |ws|
|
data/spec/unit/framing_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
1
3
|
require 'helper'
|
2
4
|
|
3
5
|
describe EM::WebSocket::Framing03 do
|
@@ -13,7 +15,7 @@ describe EM::WebSocket::Framing03 do
|
|
13
15
|
|
14
16
|
def <<(data)
|
15
17
|
@data << data
|
16
|
-
process_data
|
18
|
+
process_data
|
17
19
|
end
|
18
20
|
|
19
21
|
def debug(*args); end
|
@@ -138,7 +140,7 @@ describe EM::WebSocket::Framing04 do
|
|
138
140
|
|
139
141
|
def <<(data)
|
140
142
|
@data << data
|
141
|
-
process_data
|
143
|
+
process_data
|
142
144
|
end
|
143
145
|
|
144
146
|
def debug(*args); end
|
@@ -207,7 +209,7 @@ describe EM::WebSocket::Framing07 do
|
|
207
209
|
|
208
210
|
def <<(data)
|
209
211
|
@data << data
|
210
|
-
process_data
|
212
|
+
process_data
|
211
213
|
end
|
212
214
|
|
213
215
|
def debug(*args); end
|
@@ -265,7 +267,7 @@ describe EM::WebSocket::Framing07 do
|
|
265
267
|
lambda {
|
266
268
|
# Opcode 3 is not supported by this draft
|
267
269
|
@f << "\x83\x05Hello"
|
268
|
-
}.should raise_error(EventMachine::WebSocket::WSProtocolError, "Unknown opcode")
|
270
|
+
}.should raise_error(EventMachine::WebSocket::WSProtocolError, "Unknown opcode 3")
|
269
271
|
end
|
270
272
|
|
271
273
|
it "should accept a fragmented unmasked text message in 3 frames" do
|
@@ -274,5 +276,23 @@ describe EM::WebSocket::Framing07 do
|
|
274
276
|
@f << "\x00\x02lo"
|
275
277
|
@f << "\x80\x06 world"
|
276
278
|
end
|
279
|
+
|
280
|
+
it "should raise if non-fin frame is followed by a non-continuation data frame (continuation frame would be expected)" do
|
281
|
+
lambda {
|
282
|
+
@f << 0b00000001 # Not fin, text
|
283
|
+
@f << 0b00000001 # Length 1
|
284
|
+
@f << 'f'
|
285
|
+
@f << 0b10000001 # fin, text (continutation expected)
|
286
|
+
@f << 0b00000001 # Length 1
|
287
|
+
@f << 'b'
|
288
|
+
}.should raise_error(EM::WebSocket::WebSocketError, 'Continuation frame expected')
|
289
|
+
end
|
290
|
+
|
291
|
+
it "should raise on non-fin control frames (control frames must not be fragmented)" do
|
292
|
+
lambda {
|
293
|
+
@f << 0b00001010 # Not fin, pong (opcode 10)
|
294
|
+
@f << 0b00000000 # Length 1
|
295
|
+
}.should raise_error(EM::WebSocket::WebSocketError, 'Control frames must not be fragmented')
|
296
|
+
end
|
277
297
|
end
|
278
298
|
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe EM::WebSocket::Handshake do
|
4
|
+
def handshake(request, secure = false)
|
5
|
+
handshake = EM::WebSocket::Handshake.new(secure)
|
6
|
+
handshake.receive_data(format_request(request))
|
7
|
+
handshake
|
8
|
+
end
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
@request = {
|
12
|
+
:port => 80,
|
13
|
+
:method => "GET",
|
14
|
+
:path => "/demo",
|
15
|
+
:headers => {
|
16
|
+
'Host' => 'example.com',
|
17
|
+
'Connection' => 'Upgrade',
|
18
|
+
'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
|
19
|
+
'Sec-WebSocket-Protocol' => 'sample',
|
20
|
+
'Upgrade' => 'WebSocket',
|
21
|
+
'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
|
22
|
+
'Origin' => 'http://example.com'
|
23
|
+
},
|
24
|
+
:body => '^n:ds[4U'
|
25
|
+
}
|
26
|
+
@secure_request = @request.merge(:port => 443)
|
27
|
+
|
28
|
+
@response = {
|
29
|
+
:headers => {
|
30
|
+
"Upgrade" => "WebSocket",
|
31
|
+
"Connection" => "Upgrade",
|
32
|
+
"Sec-WebSocket-Location" => "ws://example.com/demo",
|
33
|
+
"Sec-WebSocket-Origin" => "http://example.com",
|
34
|
+
"Sec-WebSocket-Protocol" => "sample"
|
35
|
+
},
|
36
|
+
:body => "8jKS\'y:G*Co,Wxa-"
|
37
|
+
}
|
38
|
+
@secure_response = @response.merge(:headers => @response[:headers].merge('Sec-WebSocket-Location' => "wss://example.com/demo"))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should handle good request" do
|
42
|
+
handshake(@request).should succeed_with_upgrade(@response)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should handle good request to secure default port if secure mode is enabled" do
|
46
|
+
handshake(@secure_request, true).
|
47
|
+
should succeed_with_upgrade(@secure_response)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not handle good request to secure default port if secure mode is disabled" do
|
51
|
+
handshake(@secure_request, false).
|
52
|
+
should_not succeed_with_upgrade(@secure_response)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should handle good request on nondefault port" do
|
56
|
+
@request[:port] = 8081
|
57
|
+
@request[:headers]['Host'] = 'example.com:8081'
|
58
|
+
@response[:headers]['Sec-WebSocket-Location'] =
|
59
|
+
'ws://example.com:8081/demo'
|
60
|
+
|
61
|
+
handshake(@request).should succeed_with_upgrade(@response)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should handle good request to secure nondefault port" do
|
65
|
+
@secure_request[:port] = 8081
|
66
|
+
@secure_request[:headers]['Host'] = 'example.com:8081'
|
67
|
+
@secure_response[:headers]['Sec-WebSocket-Location'] = 'wss://example.com:8081/demo'
|
68
|
+
|
69
|
+
handshake(@secure_request, true).
|
70
|
+
should succeed_with_upgrade(@secure_response)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should handle good request with no protocol" do
|
74
|
+
@request[:headers].delete('Sec-WebSocket-Protocol')
|
75
|
+
@response[:headers].delete("Sec-WebSocket-Protocol")
|
76
|
+
|
77
|
+
handshake(@request).should succeed_with_upgrade(@response)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should handle extra headers by simply ignoring them" do
|
81
|
+
@request[:headers]['EmptyValue'] = ""
|
82
|
+
@request[:headers]['AKey'] = "AValue"
|
83
|
+
|
84
|
+
handshake(@request).should succeed_with_upgrade(@response)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should raise error on HTTP request" do
|
88
|
+
@request[:headers] = {
|
89
|
+
'Host' => 'www.google.com',
|
90
|
+
'User-Agent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB6 GTBA',
|
91
|
+
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
92
|
+
'Accept-Language' => 'en-us,en;q=0.5',
|
93
|
+
'Accept-Encoding' => 'gzip,deflate',
|
94
|
+
'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
95
|
+
'Keep-Alive' => '300',
|
96
|
+
'Connection' => 'keep-alive',
|
97
|
+
}
|
98
|
+
|
99
|
+
handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should raise error on wrong method" do
|
103
|
+
@request[:method] = 'POST'
|
104
|
+
|
105
|
+
handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should raise error if upgrade header incorrect" do
|
109
|
+
@request[:headers]['Upgrade'] = 'NonWebSocket'
|
110
|
+
|
111
|
+
handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should raise error if Sec-WebSocket-Protocol is empty" do
|
115
|
+
@request[:headers]['Sec-WebSocket-Protocol'] = ''
|
116
|
+
|
117
|
+
handshake(@request).should fail_with_error(EM::WebSocket::HandshakeError)
|
118
|
+
end
|
119
|
+
|
120
|
+
%w[Sec-WebSocket-Key1 Sec-WebSocket-Key2].each do |header|
|
121
|
+
it "should raise error if #{header} has zero spaces" do
|
122
|
+
@request[:headers][header] = 'nospaces'
|
123
|
+
|
124
|
+
handshake(@request).
|
125
|
+
should fail_with_error(EM::WebSocket::HandshakeError, 'Websocket Key1 or Key2 does not contain spaces - this is a symptom of a cross-protocol attack')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should raise error if Sec-WebSocket-Key1 is missing" do
|
130
|
+
@request[:headers].delete("Sec-WebSocket-Key1")
|
131
|
+
|
132
|
+
# The error message isn't correct since key1 is used to heuristically
|
133
|
+
# determine the protocol version in use, however this test at least checks
|
134
|
+
# that the handshake does correctly fail
|
135
|
+
handshake(@request).
|
136
|
+
should fail_with_error(EM::WebSocket::HandshakeError, 'Extra bytes after header')
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should raise error if Sec-WebSocket-Key2 is missing" do
|
140
|
+
@request[:headers].delete("Sec-WebSocket-Key2")
|
141
|
+
|
142
|
+
handshake(@request).
|
143
|
+
should fail_with_error(EM::WebSocket::HandshakeError, 'WebSocket key1 or key2 is missing')
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should raise error if spaces do not divide numbers in Sec-WebSocket-Key* " do
|
147
|
+
@request[:headers]['Sec-WebSocket-Key2'] = '12998 5 Y3 1.P00'
|
148
|
+
|
149
|
+
handshake(@request).
|
150
|
+
should fail_with_error(EM::WebSocket::HandshakeError, 'Invalid Key "12998 5 Y3 1.P00"')
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should raise error if the HTTP header is empty" do
|
154
|
+
handshake = EM::WebSocket::Handshake.new(false)
|
155
|
+
handshake.receive_data("\r\n\r\nfoobar")
|
156
|
+
|
157
|
+
handshake.
|
158
|
+
should fail_with_error(EM::WebSocket::HandshakeError, 'Invalid HTTP header: Could not parse data entirely (4 != 10)')
|
159
|
+
end
|
160
|
+
|
161
|
+
# This might seems crazy, but very occasionally we saw multiple "Upgrade:
|
162
|
+
# WebSocket" headers in the wild. RFC 4.2.1 isn't particularly clear on this
|
163
|
+
# point, so for now I have decided not to accept --@mloughran
|
164
|
+
it "should raise error on multiple upgrade headers" do
|
165
|
+
handshake = EM::WebSocket::Handshake.new(false)
|
166
|
+
|
167
|
+
# Add a duplicate upgrade header
|
168
|
+
headers = format_request(@request)
|
169
|
+
upgrade_header = "Upgrade: WebSocket\r\n"
|
170
|
+
headers.gsub!(upgrade_header, "#{upgrade_header}#{upgrade_header}")
|
171
|
+
|
172
|
+
handshake.receive_data(headers)
|
173
|
+
|
174
|
+
handshake.errback { |e|
|
175
|
+
e.class.should == EM::WebSocket::HandshakeError
|
176
|
+
e.message.should == 'Invalid upgrade header: ["WebSocket", "WebSocket"]'
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should cope with requests where the header is split" do
|
181
|
+
request = format_request(@request)
|
182
|
+
incomplete_request = request[0...(request.length / 2)]
|
183
|
+
rest = request[(request.length / 2)..-1]
|
184
|
+
handshake = EM::WebSocket::Handshake.new(false)
|
185
|
+
handshake.receive_data(incomplete_request)
|
186
|
+
|
187
|
+
handshake.instance_variable_get(:@deferred_status).should == nil
|
188
|
+
|
189
|
+
# Send the remaining header
|
190
|
+
handshake.receive_data(rest)
|
191
|
+
|
192
|
+
handshake(@request).should succeed_with_upgrade(@response)
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should cope with requests where the third key is split" do
|
196
|
+
request = format_request(@request)
|
197
|
+
# Removes last two bytes of the third key
|
198
|
+
incomplete_request = request[0..-3]
|
199
|
+
rest = request[-2..-1]
|
200
|
+
handshake = EM::WebSocket::Handshake.new(false)
|
201
|
+
handshake.receive_data(incomplete_request)
|
202
|
+
|
203
|
+
handshake.instance_variable_get(:@deferred_status).should == nil
|
204
|
+
|
205
|
+
# Send the remaining third key
|
206
|
+
handshake.receive_data(rest)
|
207
|
+
|
208
|
+
handshake(@request).should succeed_with_upgrade(@response)
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should fail if the request URI is invalid" do
|
212
|
+
@request[:path] = "/%"
|
213
|
+
handshake(@request).should \
|
214
|
+
fail_with_error(EM::WebSocket::HandshakeError, 'Invalid request URI: /%')
|
215
|
+
end
|
216
|
+
end
|