em-websocket 0.2.1 → 0.3.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.
- data/CHANGELOG.rdoc +10 -0
- data/README.md +16 -0
- data/examples/test.html +11 -8
- data/lib/em-websocket.rb +6 -3
- 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 +58 -32
- data/lib/em-websocket/framing03.rb +9 -30
- data/lib/em-websocket/framing04.rb +15 -0
- data/lib/em-websocket/framing05.rb +157 -0
- data/lib/em-websocket/framing76.rb +5 -6
- data/lib/em-websocket/handler.rb +2 -4
- data/lib/em-websocket/handler03.rb +2 -6
- data/lib/em-websocket/handler05.rb +10 -0
- data/lib/em-websocket/handler06.rb +10 -0
- data/lib/em-websocket/handler75.rb +1 -0
- data/lib/em-websocket/handler76.rb +1 -0
- data/lib/em-websocket/handler_factory.rb +41 -22
- data/lib/em-websocket/handshake04.rb +35 -0
- data/lib/em-websocket/handshake75.rb +4 -4
- data/lib/em-websocket/handshake76.rb +8 -8
- data/lib/em-websocket/masking04.rb +27 -0
- data/lib/em-websocket/message_processor_03.rb +33 -0
- data/lib/em-websocket/message_processor_06.rb +46 -0
- data/lib/em-websocket/version.rb +1 -1
- data/spec/helper.rb +54 -2
- data/spec/integration/common_spec.rb +115 -0
- data/spec/integration/draft03_spec.rb +26 -11
- data/spec/integration/draft05_spec.rb +45 -0
- data/spec/integration/draft06_spec.rb +79 -0
- data/spec/integration/draft75_spec.rb +115 -0
- data/spec/integration/draft76_spec.rb +25 -10
- data/spec/integration/shared_examples.rb +62 -0
- data/spec/unit/framing_spec.rb +55 -0
- data/spec/unit/masking_spec.rb +18 -0
- metadata +29 -33
- data/spec/websocket_spec.rb +0 -210
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
# These tests are not specifi to any particular draft of the specification
|
4
|
+
#
|
5
|
+
describe "WebSocket server" do
|
6
|
+
it "should fail on non WebSocket requests" do
|
7
|
+
EM.run do
|
8
|
+
EventMachine.add_timer(0.1) do
|
9
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:12345/').get :timeout => 0
|
10
|
+
http.errback { EM.stop }
|
11
|
+
http.callback { failed }
|
12
|
+
end
|
13
|
+
|
14
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should populate ws.request with appropriate headers" do
|
19
|
+
EM.run do
|
20
|
+
EventMachine.add_timer(0.1) do
|
21
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
|
22
|
+
http.errback { failed }
|
23
|
+
http.callback {
|
24
|
+
http.response_header.status.should == 101
|
25
|
+
http.close_connection
|
26
|
+
}
|
27
|
+
http.stream { |msg| }
|
28
|
+
end
|
29
|
+
|
30
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
31
|
+
ws.onopen {
|
32
|
+
ws.request["user-agent"].should == "EventMachine HttpClient"
|
33
|
+
ws.request["connection"].should == "Upgrade"
|
34
|
+
ws.request["upgrade"].should == "WebSocket"
|
35
|
+
ws.request["path"].should == "/"
|
36
|
+
ws.request["origin"].should == "127.0.0.1"
|
37
|
+
ws.request["host"].to_s.should == "ws://127.0.0.1:12345"
|
38
|
+
}
|
39
|
+
ws.onclose {
|
40
|
+
ws.state.should == :closed
|
41
|
+
EventMachine.stop
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should allow sending and retrieving query string args passed in on the connection request." do
|
48
|
+
EM.run do
|
49
|
+
EventMachine.add_timer(0.1) do
|
50
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:query => {'foo' => 'bar', 'baz' => 'qux'}, :timeout => 0)
|
51
|
+
http.errback { failed }
|
52
|
+
http.callback {
|
53
|
+
http.response_header.status.should == 101
|
54
|
+
http.close_connection
|
55
|
+
}
|
56
|
+
http.stream { |msg| }
|
57
|
+
end
|
58
|
+
|
59
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
60
|
+
ws.onopen {
|
61
|
+
path, query = ws.request["path"].split('?')
|
62
|
+
path.should == '/'
|
63
|
+
Hash[*query.split(/&|=/)].should == {"foo"=>"bar", "baz"=>"qux"}
|
64
|
+
ws.request["query"]["foo"].should == "bar"
|
65
|
+
ws.request["query"]["baz"].should == "qux"
|
66
|
+
}
|
67
|
+
ws.onclose {
|
68
|
+
ws.state.should == :closed
|
69
|
+
EventMachine.stop
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should ws.response['Query'] to empty hash when no query string params passed in connection URI" do
|
76
|
+
EM.run do
|
77
|
+
EventMachine.add_timer(0.1) do
|
78
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get(:timeout => 0)
|
79
|
+
http.errback { failed }
|
80
|
+
http.callback {
|
81
|
+
http.response_header.status.should == 101
|
82
|
+
http.close_connection
|
83
|
+
}
|
84
|
+
http.stream { |msg| }
|
85
|
+
end
|
86
|
+
|
87
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
88
|
+
ws.onopen {
|
89
|
+
ws.request["path"].should == "/"
|
90
|
+
ws.request["query"].should == {}
|
91
|
+
}
|
92
|
+
ws.onclose {
|
93
|
+
ws.state.should == :closed
|
94
|
+
EventMachine.stop
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should raise an exception if frame sent before handshake complete" do
|
101
|
+
EM.run {
|
102
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |c|
|
103
|
+
# We're not using a real client so the handshake will not be sent
|
104
|
+
EM.add_timer(0.1) {
|
105
|
+
lambda {
|
106
|
+
c.send('early message')
|
107
|
+
}.should raise_error('Cannot send data before onopen callback')
|
108
|
+
EM.stop
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
client = EM.connect('0.0.0.0', 12345, EM::Connection)
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'helper'
|
2
|
+
require 'integration/shared_examples'
|
2
3
|
|
3
4
|
describe "draft03" do
|
4
5
|
before :each do
|
@@ -31,6 +32,20 @@ describe "draft03" do
|
|
31
32
|
}
|
32
33
|
end
|
33
34
|
|
35
|
+
it_behaves_like "a websocket server" do
|
36
|
+
def start_server
|
37
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
38
|
+
yield ws
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def start_client
|
43
|
+
client = EM.connect('0.0.0.0', 12345, Draft03FakeWebSocketClient)
|
44
|
+
client.send_data(format_request(@request))
|
45
|
+
yield client if block_given?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
34
49
|
# These examples are straight from the spec
|
35
50
|
# http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03#section-4.6
|
36
51
|
describe "examples from the spec" do
|
@@ -48,7 +63,7 @@ describe "draft03" do
|
|
48
63
|
connection.send_data(format_request(@request))
|
49
64
|
|
50
65
|
# Send frame
|
51
|
-
connection.onopen
|
66
|
+
connection.onopen {
|
52
67
|
connection.send_data("\x04\x05Hello")
|
53
68
|
}
|
54
69
|
end
|
@@ -68,7 +83,7 @@ describe "draft03" do
|
|
68
83
|
connection.send_data(format_request(@request))
|
69
84
|
|
70
85
|
# Send frame
|
71
|
-
connection.onopen
|
86
|
+
connection.onopen {
|
72
87
|
connection.send_data("\x84\x03Hel")
|
73
88
|
connection.send_data("\x00\x02lo")
|
74
89
|
}
|
@@ -84,11 +99,11 @@ describe "draft03" do
|
|
84
99
|
connection.send_data(format_request(@request))
|
85
100
|
|
86
101
|
# Send frame
|
87
|
-
connection.onopen
|
102
|
+
connection.onopen {
|
88
103
|
connection.send_data("\x02\x05Hello")
|
89
104
|
}
|
90
105
|
|
91
|
-
connection.onmessage
|
106
|
+
connection.onmessage { |frame|
|
92
107
|
next if frame.nil?
|
93
108
|
frame.should == "\x03\x05Hello"
|
94
109
|
EM.stop
|
@@ -112,7 +127,7 @@ describe "draft03" do
|
|
112
127
|
connection.send_data(format_request(@request))
|
113
128
|
|
114
129
|
# Send frame
|
115
|
-
connection.onopen
|
130
|
+
connection.onopen {
|
116
131
|
connection.send_data("\x05\x7E\x01\x00" + data)
|
117
132
|
}
|
118
133
|
end
|
@@ -134,7 +149,7 @@ describe "draft03" do
|
|
134
149
|
connection.send_data(format_request(@request))
|
135
150
|
|
136
151
|
# Send frame
|
137
|
-
connection.onopen
|
152
|
+
connection.onopen {
|
138
153
|
connection.send_data("\x05\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data)
|
139
154
|
}
|
140
155
|
end
|
@@ -151,12 +166,12 @@ describe "draft03" do
|
|
151
166
|
connection.send_data(format_request(@request))
|
152
167
|
|
153
168
|
# Send close frame
|
154
|
-
connection.onopen
|
169
|
+
connection.onopen {
|
155
170
|
connection.send_data("\x01\x00")
|
156
171
|
}
|
157
172
|
|
158
173
|
# Check that close ack received
|
159
|
-
connection.onmessage
|
174
|
+
connection.onmessage { |frame|
|
160
175
|
frame.should == "\x01\x00"
|
161
176
|
EM.stop
|
162
177
|
}
|
@@ -181,14 +196,14 @@ describe "draft03" do
|
|
181
196
|
connection.send_data(format_request(@request))
|
182
197
|
|
183
198
|
# 3. Check that close frame recieved and acknowlege it
|
184
|
-
connection.onmessage
|
199
|
+
connection.onmessage { |frame|
|
185
200
|
frame.should == "\x01\x00"
|
186
201
|
ack_received = true
|
187
202
|
connection.send_data("\x01\x00")
|
188
203
|
}
|
189
204
|
|
190
205
|
# 4. Check that connection is closed _after_ the ack
|
191
|
-
connection.onclose
|
206
|
+
connection.onclose {
|
192
207
|
ack_received.should == true
|
193
208
|
EM.stop
|
194
209
|
}
|
@@ -235,7 +250,7 @@ describe "draft03" do
|
|
235
250
|
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
236
251
|
connection.send_data(format_request(@request))
|
237
252
|
|
238
|
-
connection.onmessage
|
253
|
+
connection.onmessage { |frame|
|
239
254
|
if frame == "\x01\x00"
|
240
255
|
# 3. After the close frame is received send a ping frame, but
|
241
256
|
# don't respond with a close ack
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe "draft05" do
|
4
|
+
before :each do
|
5
|
+
@request = {
|
6
|
+
:port => 80,
|
7
|
+
:method => "GET",
|
8
|
+
:path => "/demo",
|
9
|
+
:headers => {
|
10
|
+
'Host' => 'example.com',
|
11
|
+
'Upgrade' => 'websocket',
|
12
|
+
'Connection' => 'Upgrade',
|
13
|
+
'Sec-WebSocket-Key' => 'dGhlIHNhbXBsZSBub25jZQ==',
|
14
|
+
'Sec-WebSocket-Protocol' => 'sample',
|
15
|
+
'Sec-WebSocket-Origin' => 'http://example.com',
|
16
|
+
'Sec-WebSocket-Version' => '5'
|
17
|
+
}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def start_server
|
22
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
23
|
+
yield ws
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def start_client
|
28
|
+
client = EM.connect('0.0.0.0', 12345, Draft03FakeWebSocketClient)
|
29
|
+
client.send_data(format_request(@request))
|
30
|
+
yield client if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should open connection" do
|
34
|
+
EM.run {
|
35
|
+
start_server { |server|
|
36
|
+
server.onopen {
|
37
|
+
server.instance_variable_get(:@handler).class.should == EventMachine::WebSocket::Handler05
|
38
|
+
EM.stop
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
start_client
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe "draft06" do
|
4
|
+
before :each do
|
5
|
+
@request = {
|
6
|
+
:port => 80,
|
7
|
+
:method => "GET",
|
8
|
+
:path => "/demo",
|
9
|
+
:headers => {
|
10
|
+
'Host' => 'example.com',
|
11
|
+
'Upgrade' => 'websocket',
|
12
|
+
'Connection' => 'Upgrade',
|
13
|
+
'Sec-WebSocket-Key' => 'dGhlIHNhbXBsZSBub25jZQ==',
|
14
|
+
'Sec-WebSocket-Protocol' => 'sample',
|
15
|
+
'Sec-WebSocket-Origin' => 'http://example.com',
|
16
|
+
'Sec-WebSocket-Version' => '6'
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
@response = {
|
21
|
+
:protocol => "HTTP/1.1 101 Switching Protocols\r\n",
|
22
|
+
:headers => {
|
23
|
+
"Upgrade" => "websocket",
|
24
|
+
"Connection" => "Upgrade",
|
25
|
+
"Sec-WebSocket-Accept" => "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def start_server
|
31
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
32
|
+
yield ws
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def start_client
|
37
|
+
client = EM.connect('0.0.0.0', 12345, Draft03FakeWebSocketClient)
|
38
|
+
client.send_data(format_request(@request))
|
39
|
+
yield client if block_given?
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should open connection" do
|
43
|
+
EM.run {
|
44
|
+
start_server { |server|
|
45
|
+
server.onopen {
|
46
|
+
server.instance_variable_get(:@handler).class.should == EventMachine::WebSocket::Handler06
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
start_client { |client|
|
51
|
+
client.onopen {
|
52
|
+
client.handshake_response.lines.sort.
|
53
|
+
should == format_response(@response).lines.sort
|
54
|
+
EM.stop
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should accept a single-frame text message (masked)" do
|
61
|
+
EM.run do
|
62
|
+
start_server { |server|
|
63
|
+
server.onmessage { |msg|
|
64
|
+
msg.should == 'Hello'
|
65
|
+
EM.stop
|
66
|
+
}
|
67
|
+
server.onerror {
|
68
|
+
failed
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
start_client { |client|
|
73
|
+
client.onopen {
|
74
|
+
client.send_data("\x00\x00\x01\x00\x84\x05Ielln")
|
75
|
+
}
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'integration/shared_examples'
|
3
|
+
|
4
|
+
# These integration tests are older and use a different testing style to the
|
5
|
+
# integration tests for newer drafts. They use EM::HttpRequest which happens
|
6
|
+
# to currently estabish a websocket connection using the draft75 protocol.
|
7
|
+
#
|
8
|
+
describe "WebSocket server draft75" do
|
9
|
+
|
10
|
+
it_behaves_like "a websocket server" do
|
11
|
+
def start_server
|
12
|
+
EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
|
13
|
+
yield ws
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_client
|
18
|
+
client = Draft75WebSocketClient.new
|
19
|
+
yield client if block_given?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should automatically complete WebSocket handshake" do
|
24
|
+
EM.run do
|
25
|
+
MSG = "Hello World!"
|
26
|
+
EventMachine.add_timer(0.1) do
|
27
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
|
28
|
+
http.errback { failed }
|
29
|
+
http.callback { http.response_header.status.should == 101 }
|
30
|
+
|
31
|
+
http.stream { |msg|
|
32
|
+
msg.should == MSG
|
33
|
+
EventMachine.stop
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
38
|
+
ws.onopen {
|
39
|
+
ws.send MSG
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should split multiple messages into separate callbacks" do
|
46
|
+
EM.run do
|
47
|
+
messages = %w[1 2]
|
48
|
+
received = []
|
49
|
+
|
50
|
+
EventMachine.add_timer(0.1) do
|
51
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
|
52
|
+
http.errback { failed }
|
53
|
+
http.stream {|msg|}
|
54
|
+
http.callback {
|
55
|
+
http.response_header.status.should == 101
|
56
|
+
http.send messages[0]
|
57
|
+
http.send messages[1]
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
62
|
+
ws.onopen {}
|
63
|
+
ws.onclose {}
|
64
|
+
ws.onmessage {|msg|
|
65
|
+
msg.should == messages[received.size]
|
66
|
+
received.push msg
|
67
|
+
|
68
|
+
EventMachine.stop if received.size == messages.size
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should call onclose callback when client closes connection" do
|
75
|
+
EM.run do
|
76
|
+
EventMachine.add_timer(0.1) do
|
77
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
|
78
|
+
http.errback { failed }
|
79
|
+
http.callback {
|
80
|
+
http.response_header.status.should == 101
|
81
|
+
http.close_connection
|
82
|
+
}
|
83
|
+
http.stream{|msg|}
|
84
|
+
end
|
85
|
+
|
86
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
87
|
+
ws.onopen {}
|
88
|
+
ws.onclose {
|
89
|
+
ws.state.should == :closed
|
90
|
+
EventMachine.stop
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should call onerror callback with raised exception and close connection on bad handshake" do
|
97
|
+
EM.run do
|
98
|
+
EventMachine.add_timer(0.1) do
|
99
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:12345/').get :timeout => 0
|
100
|
+
http.errback { http.response_header.status.should == 0 }
|
101
|
+
http.callback { failed }
|
102
|
+
end
|
103
|
+
|
104
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
|
105
|
+
ws.onopen { failed }
|
106
|
+
ws.onclose { EventMachine.stop }
|
107
|
+
ws.onerror {|e|
|
108
|
+
e.should be_an_instance_of EventMachine::WebSocket::HandshakeError
|
109
|
+
e.message.should match('Connection and Upgrade headers required')
|
110
|
+
EventMachine.stop
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|