em-websocket 0.4.0 → 0.5.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 +16 -1
- data/README.md +42 -1
- data/examples/test.html +3 -1
- data/lib/em-websocket/close03.rb +2 -0
- data/lib/em-websocket/close05.rb +2 -0
- data/lib/em-websocket/close06.rb +2 -0
- data/lib/em-websocket/close75.rb +2 -1
- data/lib/em-websocket/connection.rb +44 -19
- data/lib/em-websocket/framing03.rb +2 -1
- data/lib/em-websocket/framing05.rb +2 -1
- data/lib/em-websocket/framing07.rb +2 -1
- data/lib/em-websocket/handler.rb +8 -2
- data/lib/em-websocket/handler76.rb +2 -0
- data/lib/em-websocket/handshake.rb +5 -1
- data/lib/em-websocket/handshake04.rb +0 -5
- data/lib/em-websocket/masking04.rb +1 -5
- data/lib/em-websocket/message_processor_03.rb +5 -2
- data/lib/em-websocket/message_processor_06.rb +13 -6
- data/lib/em-websocket/version.rb +1 -1
- data/lib/em-websocket/websocket.rb +4 -0
- data/spec/helper.rb +52 -46
- data/spec/integration/common_spec.rb +11 -5
- data/spec/integration/draft03_spec.rb +82 -55
- data/spec/integration/draft05_spec.rb +13 -8
- data/spec/integration/draft06_spec.rb +65 -3
- data/spec/integration/draft13_spec.rb +31 -20
- data/spec/integration/draft75_spec.rb +32 -19
- data/spec/integration/draft76_spec.rb +62 -43
- data/spec/integration/shared_examples.rb +80 -0
- data/spec/unit/framing_spec.rb +3 -1
- data/spec/unit/masking_spec.rb +2 -0
- metadata +4 -4
data/lib/em-websocket/version.rb
CHANGED
data/spec/helper.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
1
3
|
require 'rubygems'
|
2
4
|
require 'rspec'
|
3
5
|
require 'em-spec/rspec'
|
4
|
-
require 'pp'
|
5
6
|
require 'em-http'
|
6
7
|
|
7
8
|
require 'em-websocket'
|
@@ -27,74 +28,79 @@ class FakeWebSocketClient < EM::Connection
|
|
27
28
|
# puts "RECEIVE DATA #{data}"
|
28
29
|
if @state == :new
|
29
30
|
@handshake_response = data
|
30
|
-
@onopen.call if @onopen
|
31
|
+
@onopen.call if defined? @onopen
|
31
32
|
@state = :open
|
32
33
|
else
|
33
|
-
@onmessage.call(data) if @onmessage
|
34
|
+
@onmessage.call(data) if defined? @onmessage
|
34
35
|
@packets << data
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
38
|
-
def send(
|
39
|
-
|
39
|
+
def send(application_data)
|
40
|
+
send_frame(:text, application_data)
|
41
|
+
end
|
42
|
+
|
43
|
+
def send_frame(type, application_data)
|
44
|
+
send_data construct_frame(type, application_data)
|
40
45
|
end
|
41
46
|
|
42
47
|
def unbind
|
43
|
-
@onclose.call if @onclose
|
48
|
+
@onclose.call if defined? @onclose
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def construct_frame(type, data)
|
54
|
+
"\x00#{data}\xff"
|
44
55
|
end
|
45
56
|
end
|
46
57
|
|
47
58
|
class Draft03FakeWebSocketClient < FakeWebSocketClient
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
frame <<
|
59
|
+
private
|
60
|
+
|
61
|
+
def construct_frame(type, data)
|
62
|
+
frame = ""
|
63
|
+
frame << EM::WebSocket::Framing03::FRAME_TYPES[type]
|
64
|
+
frame << encoded_length(data.size)
|
65
|
+
frame << data
|
66
|
+
end
|
53
67
|
|
54
|
-
|
68
|
+
def encoded_length(length)
|
55
69
|
if length <= 125
|
56
|
-
|
57
|
-
frame << byte2
|
70
|
+
[length].pack('C') # since rsv4 is 0
|
58
71
|
elsif length < 65536 # write 2 byte length
|
59
|
-
|
60
|
-
frame << [length].pack('n')
|
72
|
+
"\126#{[length].pack('n')}"
|
61
73
|
else # write 8 byte length
|
62
|
-
|
63
|
-
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
74
|
+
"\127#{[length >> 32, length & 0xFFFFFFFF].pack("NN")}"
|
64
75
|
end
|
65
|
-
|
66
|
-
frame << application_data
|
67
|
-
|
68
|
-
send_data(frame)
|
69
76
|
end
|
70
77
|
end
|
71
78
|
|
72
|
-
class
|
73
|
-
|
74
|
-
frame = ''
|
75
|
-
opcode = 1 # fake only supports text frames
|
76
|
-
byte1 = opcode | 0b10000000 # since more, rsv1-3 are 0
|
77
|
-
frame << byte1
|
79
|
+
class Draft05FakeWebSocketClient < Draft03FakeWebSocketClient
|
80
|
+
private
|
78
81
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
frame << 127
|
88
|
-
frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
|
89
|
-
end
|
82
|
+
def construct_frame(type, data)
|
83
|
+
frame = ""
|
84
|
+
frame << "\x00\x00\x00\x00" # Mask with nothing for simplicity
|
85
|
+
frame << (EM::WebSocket::Framing05::FRAME_TYPES[type] | 0b10000000)
|
86
|
+
frame << encoded_length(data.size)
|
87
|
+
frame << data
|
88
|
+
end
|
89
|
+
end
|
90
90
|
|
91
|
-
|
91
|
+
class Draft07FakeWebSocketClient < Draft05FakeWebSocketClient
|
92
|
+
private
|
92
93
|
|
93
|
-
|
94
|
+
def construct_frame(type, data)
|
95
|
+
frame = ""
|
96
|
+
frame << (EM::WebSocket::Framing07::FRAME_TYPES[type] | 0b10000000)
|
97
|
+
# Should probably mask the data, but I get away without bothering since
|
98
|
+
# the server doesn't enforce that incoming frames are masked
|
99
|
+
frame << encoded_length(data.size)
|
100
|
+
frame << data
|
94
101
|
end
|
95
102
|
end
|
96
103
|
|
97
|
-
|
98
104
|
# Wrap EM:HttpRequest in a websocket like interface so that it can be used in the specs with the same interface as FakeWebSocketClient
|
99
105
|
class Draft75WebSocketClient
|
100
106
|
def onopen(&blk); @onopen = blk; end
|
@@ -107,10 +113,10 @@ class Draft75WebSocketClient
|
|
107
113
|
:timeout => 0,
|
108
114
|
:origin => 'http://example.com',
|
109
115
|
})
|
110
|
-
@ws.errback { @onerror.call if @onerror }
|
111
|
-
@ws.callback { @onopen.call if @onopen }
|
112
|
-
@ws.stream { |msg| @onmessage.call(msg) if @onmessage }
|
113
|
-
@ws.disconnect { @onclose.call if @onclose }
|
116
|
+
@ws.errback { @onerror.call if defined? @onerror }
|
117
|
+
@ws.callback { @onopen.call if defined? @onopen }
|
118
|
+
@ws.stream { |msg| @onmessage.call(msg) if defined? @onmessage }
|
119
|
+
@ws.disconnect { @onclose.call if defined? @onclose }
|
114
120
|
end
|
115
121
|
|
116
122
|
def send(message)
|
@@ -6,6 +6,12 @@ describe "WebSocket server" do
|
|
6
6
|
include EM::SpecHelper
|
7
7
|
default_timeout 1
|
8
8
|
|
9
|
+
def start_server
|
10
|
+
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
11
|
+
yield ws if block_given?
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
9
15
|
it "should fail on non WebSocket requests" do
|
10
16
|
em {
|
11
17
|
EM.add_timer(0.1) do
|
@@ -14,7 +20,7 @@ describe "WebSocket server" do
|
|
14
20
|
http.callback { fail }
|
15
21
|
end
|
16
22
|
|
17
|
-
|
23
|
+
start_server
|
18
24
|
}
|
19
25
|
end
|
20
26
|
|
@@ -30,7 +36,7 @@ describe "WebSocket server" do
|
|
30
36
|
http.stream { |msg| }
|
31
37
|
end
|
32
38
|
|
33
|
-
|
39
|
+
start_server do |ws|
|
34
40
|
ws.onopen { |handshake|
|
35
41
|
headers = handshake.headers
|
36
42
|
headers["User-Agent"].should == "EventMachine HttpClient"
|
@@ -64,7 +70,7 @@ describe "WebSocket server" do
|
|
64
70
|
http.stream { |msg| }
|
65
71
|
end
|
66
72
|
|
67
|
-
|
73
|
+
start_server do |ws|
|
68
74
|
ws.onopen { |handshake|
|
69
75
|
handshake.path.should == '/hello'
|
70
76
|
handshake.query_string.split('&').sort.
|
@@ -82,7 +88,7 @@ describe "WebSocket server" do
|
|
82
88
|
it "should raise an exception if frame sent before handshake complete" do
|
83
89
|
em {
|
84
90
|
# 1. Start WebSocket server
|
85
|
-
|
91
|
+
start_server { |ws|
|
86
92
|
# 3. Try to send a message to the socket
|
87
93
|
lambda {
|
88
94
|
ws.send('early message')
|
@@ -107,7 +113,7 @@ describe "WebSocket server" do
|
|
107
113
|
http.stream { |msg| }
|
108
114
|
end
|
109
115
|
|
110
|
-
|
116
|
+
start_server do |ws|
|
111
117
|
ws.onopen { |handshake|
|
112
118
|
handshake.headers["User-Agent"].should == "EventMachine HttpClient"
|
113
119
|
}
|
@@ -35,20 +35,21 @@ describe "draft03" do
|
|
35
35
|
}
|
36
36
|
end
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
def start_server
|
39
|
+
EM::WebSocket.run(:host => "0.0.0.0", :port => 12345) { |ws|
|
40
|
+
yield ws if block_given?
|
41
|
+
}
|
42
|
+
end
|
40
43
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
def start_client
|
45
|
+
client = EM.connect('0.0.0.0', 12345, Draft03FakeWebSocketClient)
|
46
|
+
client.send_data(format_request(@request))
|
47
|
+
yield client if block_given?
|
48
|
+
return client
|
49
|
+
end
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
client.send_data(format_request(@request))
|
50
|
-
yield client if block_given?
|
51
|
-
end
|
51
|
+
it_behaves_like "a websocket server" do
|
52
|
+
let(:version) { 3 }
|
52
53
|
end
|
53
54
|
|
54
55
|
# These examples are straight from the spec
|
@@ -56,36 +57,30 @@ describe "draft03" do
|
|
56
57
|
describe "examples from the spec" do
|
57
58
|
it "should accept a single-frame text message" do
|
58
59
|
em {
|
59
|
-
|
60
|
+
start_server { |ws|
|
60
61
|
ws.onmessage { |msg|
|
61
62
|
msg.should == 'Hello'
|
62
63
|
done
|
63
64
|
}
|
64
65
|
}
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
# Send frame
|
71
|
-
connection.onopen {
|
72
|
-
connection.send_data("\x04\x05Hello")
|
66
|
+
start_client { |client|
|
67
|
+
client.onopen {
|
68
|
+
client.send_data("\x04\x05Hello")
|
69
|
+
}
|
73
70
|
}
|
74
71
|
}
|
75
72
|
end
|
76
73
|
|
77
74
|
it "should accept a fragmented text message" do
|
78
75
|
em {
|
79
|
-
|
76
|
+
start_server { |ws|
|
80
77
|
ws.onmessage { |msg|
|
81
78
|
msg.should == 'Hello'
|
82
79
|
done
|
83
80
|
}
|
84
81
|
}
|
85
82
|
|
86
|
-
|
87
|
-
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
88
|
-
connection.send_data(format_request(@request))
|
83
|
+
connection = start_client
|
89
84
|
|
90
85
|
# Send frame
|
91
86
|
connection.onopen {
|
@@ -97,11 +92,9 @@ describe "draft03" do
|
|
97
92
|
|
98
93
|
it "should accept a ping request and respond with the same body" do
|
99
94
|
em {
|
100
|
-
|
95
|
+
start_server
|
101
96
|
|
102
|
-
|
103
|
-
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
104
|
-
connection.send_data(format_request(@request))
|
97
|
+
connection = start_client
|
105
98
|
|
106
99
|
# Send frame
|
107
100
|
connection.onopen {
|
@@ -120,16 +113,14 @@ describe "draft03" do
|
|
120
113
|
em {
|
121
114
|
data = "a" * 256
|
122
115
|
|
123
|
-
|
116
|
+
start_server { |ws|
|
124
117
|
ws.onmessage { |msg|
|
125
118
|
msg.should == data
|
126
119
|
done
|
127
120
|
}
|
128
121
|
}
|
129
122
|
|
130
|
-
|
131
|
-
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
132
|
-
connection.send_data(format_request(@request))
|
123
|
+
connection = start_client
|
133
124
|
|
134
125
|
# Send frame
|
135
126
|
connection.onopen {
|
@@ -142,16 +133,14 @@ describe "draft03" do
|
|
142
133
|
em {
|
143
134
|
data = "a" * 65536
|
144
135
|
|
145
|
-
|
136
|
+
start_server { |ws|
|
146
137
|
ws.onmessage { |msg|
|
147
138
|
msg.should == data
|
148
139
|
done
|
149
140
|
}
|
150
141
|
}
|
151
142
|
|
152
|
-
|
153
|
-
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
154
|
-
connection.send_data(format_request(@request))
|
143
|
+
connection = start_client
|
155
144
|
|
156
145
|
# Send frame
|
157
146
|
connection.onopen {
|
@@ -164,11 +153,9 @@ describe "draft03" do
|
|
164
153
|
describe "close handling" do
|
165
154
|
it "should respond to a new close frame with a close frame" do
|
166
155
|
em {
|
167
|
-
|
156
|
+
start_server
|
168
157
|
|
169
|
-
|
170
|
-
connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
|
171
|
-
connection.send_data(format_request(@request))
|
158
|
+
connection = start_client
|
172
159
|
|
173
160
|
# Send close frame
|
174
161
|
connection.onopen {
|
@@ -183,22 +170,31 @@ describe "draft03" do
|
|
183
170
|
}
|
184
171
|
end
|
185
172
|
|
186
|
-
it "should close the connection on receiving a close acknowlegement" do
|
173
|
+
it "should close the connection on receiving a close acknowlegement and call onclose with close code 1005 and was_clean=true (initiated by server)" do
|
187
174
|
em {
|
188
175
|
ack_received = false
|
189
176
|
|
190
|
-
|
177
|
+
start_server { |ws|
|
191
178
|
ws.onopen {
|
192
179
|
# 2. Send a close frame
|
193
180
|
EM.next_tick {
|
194
|
-
ws.
|
181
|
+
ws.close
|
195
182
|
}
|
196
183
|
}
|
184
|
+
|
185
|
+
# 5. Onclose event on server
|
186
|
+
ws.onclose { |event|
|
187
|
+
event.should == {
|
188
|
+
:code => 1005,
|
189
|
+
:reason => "",
|
190
|
+
:was_clean => true,
|
191
|
+
}
|
192
|
+
done
|
193
|
+
}
|
197
194
|
}
|
198
195
|
|
199
196
|
# 1. Create a fake client which sends draft 76 handshake
|
200
|
-
connection =
|
201
|
-
connection.send_data(format_request(@request))
|
197
|
+
connection = start_client
|
202
198
|
|
203
199
|
# 3. Check that close frame recieved and acknowlege it
|
204
200
|
connection.onmessage { |frame|
|
@@ -210,18 +206,39 @@ describe "draft03" do
|
|
210
206
|
# 4. Check that connection is closed _after_ the ack
|
211
207
|
connection.onclose {
|
212
208
|
ack_received.should == true
|
213
|
-
|
209
|
+
}
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
# it "should repur"
|
214
|
+
#
|
215
|
+
it "should return close code 1005 and was_clean=true after closing handshake (initiated by client)" do
|
216
|
+
em {
|
217
|
+
start_server { |ws|
|
218
|
+
ws.onclose { |event|
|
219
|
+
event.should == {
|
220
|
+
:code => 1005,
|
221
|
+
:reason => "",
|
222
|
+
:was_clean => true,
|
223
|
+
}
|
224
|
+
done
|
225
|
+
}
|
226
|
+
}
|
227
|
+
start_client { |client|
|
228
|
+
client.onopen {
|
229
|
+
client.send_data("\x01\x00")
|
230
|
+
}
|
214
231
|
}
|
215
232
|
}
|
216
233
|
end
|
217
234
|
|
218
235
|
it "should not allow data frame to be sent after close frame sent" do
|
219
236
|
em {
|
220
|
-
|
237
|
+
start_server { |ws|
|
221
238
|
ws.onopen {
|
222
239
|
# 2. Send a close frame
|
223
240
|
EM.next_tick {
|
224
|
-
ws.
|
241
|
+
ws.close
|
225
242
|
}
|
226
243
|
|
227
244
|
# 3. Check that exception raised if I attempt to send more data
|
@@ -235,25 +252,23 @@ describe "draft03" do
|
|
235
252
|
}
|
236
253
|
|
237
254
|
# 1. Create a fake client which sends draft 76 handshake
|
238
|
-
|
239
|
-
connection.send_data(format_request(@request))
|
255
|
+
start_client
|
240
256
|
}
|
241
257
|
end
|
242
258
|
|
243
259
|
it "should still respond to control frames after close frame sent" do
|
244
260
|
em {
|
245
|
-
|
261
|
+
start_server { |ws|
|
246
262
|
ws.onopen {
|
247
263
|
# 2. Send a close frame
|
248
264
|
EM.next_tick {
|
249
|
-
ws.
|
265
|
+
ws.close
|
250
266
|
}
|
251
267
|
}
|
252
268
|
}
|
253
269
|
|
254
270
|
# 1. Create a fake client which sends draft 76 handshake
|
255
|
-
connection =
|
256
|
-
connection.send_data(format_request(@request))
|
271
|
+
connection = start_client
|
257
272
|
|
258
273
|
connection.onmessage { |frame|
|
259
274
|
if frame == "\x01\x00"
|
@@ -268,5 +283,17 @@ describe "draft03" do
|
|
268
283
|
}
|
269
284
|
}
|
270
285
|
end
|
286
|
+
|
287
|
+
it "should report that close codes are not supported" do
|
288
|
+
em {
|
289
|
+
start_server { |ws|
|
290
|
+
ws.onopen {
|
291
|
+
ws.supports_close_codes?.should == false
|
292
|
+
done
|
293
|
+
}
|
294
|
+
}
|
295
|
+
start_client
|
296
|
+
}
|
297
|
+
end
|
271
298
|
end
|
272
299
|
end
|