sonixlabs-em-websocket 0.3.8 → 0.5.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +69 -0
- data/Gemfile +6 -0
- data/LICENCE +7 -0
- data/README.md +100 -56
- data/README.md.BACKUP.14928.md +195 -0
- data/README.md.BASE.14928.md +77 -0
- data/README.md.LOCAL.14928.md +98 -0
- data/README.md.REMOTE.14928.md +142 -0
- data/examples/echo.rb +23 -7
- data/examples/ping.rb +24 -0
- data/examples/test.html +5 -6
- data/lib/em-websocket.rb +4 -2
- 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 +219 -73
- data/lib/em-websocket/framing03.rb +6 -11
- data/lib/em-websocket/framing05.rb +6 -11
- data/lib/em-websocket/framing07.rb +25 -20
- data/lib/em-websocket/framing76.rb +6 -15
- data/lib/em-websocket/handler.rb +69 -28
- 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 -56
- data/lib/em-websocket/handshake75.rb +15 -8
- data/lib/em-websocket/handshake76.rb +15 -14
- data/lib/em-websocket/masking04.rb +4 -30
- data/lib/em-websocket/message_processor_03.rb +13 -4
- data/lib/em-websocket/message_processor_06.rb +25 -13
- data/lib/em-websocket/version.rb +1 -1
- data/lib/em-websocket/websocket.rb +35 -24
- data/spec/helper.rb +82 -55
- data/spec/integration/common_spec.rb +90 -70
- data/spec/integration/draft03_spec.rb +84 -56
- data/spec/integration/draft05_spec.rb +14 -12
- data/spec/integration/draft06_spec.rb +66 -9
- data/spec/integration/draft13_spec.rb +59 -29
- data/spec/integration/draft75_spec.rb +46 -40
- data/spec/integration/draft76_spec.rb +113 -109
- data/spec/integration/gte_03_examples.rb +42 -0
- data/spec/integration/shared_examples.rb +174 -0
- data/spec/unit/framing_spec.rb +83 -110
- data/spec/unit/handshake_spec.rb +216 -0
- data/spec/unit/masking_spec.rb +2 -0
- metadata +31 -71
- 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 -107
- data/spec/unit/handler_spec.rb +0 -147
@@ -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
|
@@ -1,6 +1,127 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
+
# These tests are run against all draft versions
|
4
|
+
#
|
3
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
|
+
|
4
125
|
it "should call onerror if an application error raised in onopen" do
|
5
126
|
em {
|
6
127
|
start_server { |ws|
|
@@ -62,6 +183,41 @@ shared_examples_for "a websocket server" do
|
|
62
183
|
}
|
63
184
|
end
|
64
185
|
|
186
|
+
it "should close the connection when a too long frame is sent" do
|
187
|
+
em {
|
188
|
+
start_server { |server|
|
189
|
+
server.max_frame_size = 20
|
190
|
+
|
191
|
+
server.onerror { |e|
|
192
|
+
# 3: Error should be reported to server
|
193
|
+
e.class.should == EventMachine::WebSocket::WSMessageTooBigError
|
194
|
+
e.message.should =~ /Frame length too long/
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
start_client { |client|
|
199
|
+
client.onopen {
|
200
|
+
EM.next_tick {
|
201
|
+
client.send("This message is longer than 20 characters")
|
202
|
+
}
|
203
|
+
|
204
|
+
}
|
205
|
+
|
206
|
+
client.onmessage { |msg|
|
207
|
+
# 4: This is actually the close message. Really need to use a real
|
208
|
+
# WebSocket client in these tests...
|
209
|
+
done
|
210
|
+
}
|
211
|
+
|
212
|
+
client.onclose {
|
213
|
+
# 4: Drafts 75 & 76 don't send a close message, they just close the
|
214
|
+
# connection
|
215
|
+
done
|
216
|
+
}
|
217
|
+
}
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
65
221
|
# Only run these tests on ruby 1.9
|
66
222
|
if "a".respond_to?(:force_encoding)
|
67
223
|
it "should raise error if you try to send non utf8 text data to ws" do
|
@@ -87,5 +243,23 @@ shared_examples_for "a websocket server" do
|
|
87
243
|
start_client { }
|
88
244
|
}
|
89
245
|
end
|
246
|
+
|
247
|
+
it "should not change the encoding of strings sent to send [antiregression]" do
|
248
|
+
em {
|
249
|
+
start_server { |server|
|
250
|
+
server.onopen {
|
251
|
+
s = "example string"
|
252
|
+
s.force_encoding("UTF-8")
|
253
|
+
|
254
|
+
server.send(s)
|
255
|
+
|
256
|
+
s.encoding.should == Encoding.find("UTF-8")
|
257
|
+
done
|
258
|
+
}
|
259
|
+
}
|
260
|
+
|
261
|
+
start_client { }
|
262
|
+
}
|
263
|
+
end
|
90
264
|
end
|
91
265
|
end
|
data/spec/unit/framing_spec.rb
CHANGED
@@ -1,14 +1,23 @@
|
|
1
|
+
# encoding: BINARY
|
2
|
+
|
1
3
|
require 'helper'
|
2
4
|
|
3
5
|
describe EM::WebSocket::Framing03 do
|
4
6
|
class FramingContainer
|
5
7
|
include EM::WebSocket::Framing03
|
6
8
|
|
9
|
+
def initialize
|
10
|
+
@connection = Object.new
|
11
|
+
def @connection.max_frame_size
|
12
|
+
1000000
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
7
16
|
def <<(data)
|
8
17
|
@data << data
|
9
|
-
process_data
|
18
|
+
process_data
|
10
19
|
end
|
11
|
-
|
20
|
+
|
12
21
|
def debug(*args); end
|
13
22
|
end
|
14
23
|
|
@@ -122,9 +131,16 @@ describe EM::WebSocket::Framing04 do
|
|
122
131
|
class FramingContainer04
|
123
132
|
include EM::WebSocket::Framing04
|
124
133
|
|
134
|
+
def initialize
|
135
|
+
@connection = Object.new
|
136
|
+
def @connection.max_frame_size
|
137
|
+
1000000
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
125
141
|
def <<(data)
|
126
142
|
@data << data
|
127
|
-
process_data
|
143
|
+
process_data
|
128
144
|
end
|
129
145
|
|
130
146
|
def debug(*args); end
|
@@ -184,9 +200,16 @@ describe EM::WebSocket::Framing07 do
|
|
184
200
|
class FramingContainer07
|
185
201
|
include EM::WebSocket::Framing07
|
186
202
|
|
203
|
+
def initialize
|
204
|
+
@connection = Object.new
|
205
|
+
def @connection.max_frame_size
|
206
|
+
1000000
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
187
210
|
def <<(data)
|
188
211
|
@data << data
|
189
|
-
process_data
|
212
|
+
process_data
|
190
213
|
end
|
191
214
|
|
192
215
|
def debug(*args); end
|
@@ -199,127 +222,77 @@ describe EM::WebSocket::Framing07 do
|
|
199
222
|
|
200
223
|
# These examples are straight from the spec
|
201
224
|
# http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07#section-4.6
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
@f << "\x81\x05\x48\x65\x6c\x6c\x6f" # "\x84\x05Hello"
|
208
|
-
}.should raise_error(EventMachine::WebSocket::WebSocketError, 'Data from client must be masked')
|
209
|
-
end
|
210
|
-
|
211
|
-
it "a single-frame masked text message" do
|
212
|
-
@f.should_receive(:message).with(:text, '', 'Hello')
|
213
|
-
@f << "\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58" # "\x84\x05Hello"
|
214
|
-
end
|
215
|
-
|
216
|
-
it "a fragmented masked text message" do
|
217
|
-
@f.should_receive(:message).with(:text, '', 'Hello')
|
218
|
-
@f << "\x01\x83\x01\x01\x01\x01Idm" # with mask x01x01x01x01, Hello -> Idmmn
|
219
|
-
@f << "\x80\x82\x01\x01\x01\x01mn"
|
220
|
-
end
|
225
|
+
describe "examples from the spec" do
|
226
|
+
it "a single-frame unmakedtext message" do
|
227
|
+
@f.should_receive(:message).with(:text, '', 'Hello')
|
228
|
+
@f << "\x81\x05\x48\x65\x6c\x6c\x6f" # "\x84\x05Hello"
|
229
|
+
end
|
221
230
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
231
|
+
it "a single-frame masked text message" do
|
232
|
+
@f.should_receive(:message).with(:text, '', 'Hello')
|
233
|
+
@f << "\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58" # "\x84\x05Hello"
|
234
|
+
end
|
226
235
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
236
|
+
it "a fragmented unmasked text message" do
|
237
|
+
@f.should_receive(:message).with(:text, '', 'Hello')
|
238
|
+
@f << "\x01\x03Hel"
|
239
|
+
@f << "\x80\x02lo"
|
240
|
+
end
|
231
241
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
@f << "\x82\xFE\x01\x00\x01\x01\x01\x01" + masked_data
|
237
|
-
end
|
242
|
+
it "Ping request" do
|
243
|
+
@f.should_receive(:message).with(:ping, '', 'Hello')
|
244
|
+
@f << "\x89\x05Hello"
|
245
|
+
end
|
238
246
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
@f.should_receive(:message).with(:binary, '', data)
|
243
|
-
@f << "\x82\xFF\x00\x00\x00\x00\x00\x01\x00\x00\x01\x01\x01\x01" + masked_data
|
244
|
-
end
|
247
|
+
it "a pong response" do
|
248
|
+
@f.should_receive(:message).with(:pong, '', 'Hello')
|
249
|
+
@f << "\x8a\x05Hello"
|
245
250
|
end
|
246
251
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
}.should raise_error(EventMachine::WebSocket::DataError, "Unknown opcode")
|
253
|
-
end
|
252
|
+
it "256 bytes binary message in a single unmasked frame" do
|
253
|
+
data = "a"*256
|
254
|
+
@f.should_receive(:message).with(:binary, '', data)
|
255
|
+
@f << "\x82\x7E\x01\x00" + data
|
256
|
+
end
|
254
257
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
@f << "\x80\x86\x01\x01\x01\x01\x21vnsme"
|
260
|
-
end
|
258
|
+
it "64KiB binary message in a single unmasked frame" do
|
259
|
+
data = "a"*65536
|
260
|
+
@f.should_receive(:message).with(:binary, '', data)
|
261
|
+
@f << "\x82\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data
|
261
262
|
end
|
262
263
|
end
|
263
264
|
|
264
|
-
describe "
|
265
|
-
|
266
|
-
|
267
|
-
|
265
|
+
describe "other tests" do
|
266
|
+
it "should raise a WSProtocolError if an invalid frame type is requested" do
|
267
|
+
lambda {
|
268
|
+
# Opcode 3 is not supported by this draft
|
269
|
+
@f << "\x83\x05Hello"
|
270
|
+
}.should raise_error(EventMachine::WebSocket::WSProtocolError, "Unknown opcode 3")
|
268
271
|
end
|
269
|
-
describe "examples from the spec" do
|
270
|
-
it "accepts a single-frame unmasked text message from the client" do
|
271
|
-
@f.should_receive(:message).with(:text, '', 'Hello')
|
272
|
-
@f << "\x81\x05\x48\x65\x6c\x6c\x6f" # "\x84\x05Hello"
|
273
|
-
end
|
274
|
-
|
275
|
-
it "a single-frame masked text message" do
|
276
|
-
@f.should_receive(:message).with(:text, '', 'Hello')
|
277
|
-
@f << "\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58" # "\x84\x05Hello"
|
278
|
-
end
|
279
|
-
|
280
|
-
it "a fragmented unmasked text message" do
|
281
|
-
@f.should_receive(:message).with(:text, '', 'Hello')
|
282
|
-
@f << "\x01\x03\Hel"
|
283
|
-
@f << "\x80\x02\lo"
|
284
|
-
end
|
285
|
-
|
286
|
-
it "Ping request" do
|
287
|
-
@f.should_receive(:message).with(:ping, '', 'Hello')
|
288
|
-
@f << "\x89\x05Hello"
|
289
|
-
end
|
290
272
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
it "256 bytes binary message in a single unmasked frame" do
|
297
|
-
data = "a"*256
|
298
|
-
@f.should_receive(:message).with(:binary, '', data)
|
299
|
-
@f << "\x82\x7E\x01\x00" + data
|
300
|
-
end
|
301
|
-
|
302
|
-
it "64KiB binary message in a single unmasked frame" do
|
303
|
-
data = "a"*65536
|
304
|
-
@f.should_receive(:message).with(:binary, '', data)
|
305
|
-
@f << "\x82\x7F\x00\x00\x00\x00\x00\x01\x00\x00" + data
|
306
|
-
end
|
273
|
+
it "should accept a fragmented unmasked text message in 3 frames" do
|
274
|
+
@f.should_receive(:message).with(:text, '', 'Hello world')
|
275
|
+
@f << "\x01\x03Hel"
|
276
|
+
@f << "\x00\x02lo"
|
277
|
+
@f << "\x80\x06 world"
|
307
278
|
end
|
308
279
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
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
|
316
290
|
|
317
|
-
|
318
|
-
|
319
|
-
@f <<
|
320
|
-
@f <<
|
321
|
-
|
322
|
-
end
|
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')
|
323
296
|
end
|
324
297
|
end
|
325
298
|
end
|