sonixlabs-em-websocket 0.3.8 → 0.5.1.1

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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +69 -0
  3. data/Gemfile +6 -0
  4. data/LICENCE +7 -0
  5. data/README.md +100 -56
  6. data/README.md.BACKUP.14928.md +195 -0
  7. data/README.md.BASE.14928.md +77 -0
  8. data/README.md.LOCAL.14928.md +98 -0
  9. data/README.md.REMOTE.14928.md +142 -0
  10. data/examples/echo.rb +23 -7
  11. data/examples/ping.rb +24 -0
  12. data/examples/test.html +5 -6
  13. data/lib/em-websocket.rb +4 -2
  14. data/lib/em-websocket/close03.rb +3 -0
  15. data/lib/em-websocket/close05.rb +3 -0
  16. data/lib/em-websocket/close06.rb +3 -0
  17. data/lib/em-websocket/close75.rb +2 -1
  18. data/lib/em-websocket/connection.rb +219 -73
  19. data/lib/em-websocket/framing03.rb +6 -11
  20. data/lib/em-websocket/framing05.rb +6 -11
  21. data/lib/em-websocket/framing07.rb +25 -20
  22. data/lib/em-websocket/framing76.rb +6 -15
  23. data/lib/em-websocket/handler.rb +69 -28
  24. data/lib/em-websocket/handler03.rb +0 -1
  25. data/lib/em-websocket/handler05.rb +0 -1
  26. data/lib/em-websocket/handler06.rb +0 -1
  27. data/lib/em-websocket/handler07.rb +0 -1
  28. data/lib/em-websocket/handler08.rb +0 -1
  29. data/lib/em-websocket/handler13.rb +0 -1
  30. data/lib/em-websocket/handler76.rb +2 -0
  31. data/lib/em-websocket/handshake.rb +156 -0
  32. data/lib/em-websocket/handshake04.rb +18 -56
  33. data/lib/em-websocket/handshake75.rb +15 -8
  34. data/lib/em-websocket/handshake76.rb +15 -14
  35. data/lib/em-websocket/masking04.rb +4 -30
  36. data/lib/em-websocket/message_processor_03.rb +13 -4
  37. data/lib/em-websocket/message_processor_06.rb +25 -13
  38. data/lib/em-websocket/version.rb +1 -1
  39. data/lib/em-websocket/websocket.rb +35 -24
  40. data/spec/helper.rb +82 -55
  41. data/spec/integration/common_spec.rb +90 -70
  42. data/spec/integration/draft03_spec.rb +84 -56
  43. data/spec/integration/draft05_spec.rb +14 -12
  44. data/spec/integration/draft06_spec.rb +66 -9
  45. data/spec/integration/draft13_spec.rb +59 -29
  46. data/spec/integration/draft75_spec.rb +46 -40
  47. data/spec/integration/draft76_spec.rb +113 -109
  48. data/spec/integration/gte_03_examples.rb +42 -0
  49. data/spec/integration/shared_examples.rb +174 -0
  50. data/spec/unit/framing_spec.rb +83 -110
  51. data/spec/unit/handshake_spec.rb +216 -0
  52. data/spec/unit/masking_spec.rb +2 -0
  53. metadata +31 -71
  54. data/examples/flash_policy_file_server.rb +0 -21
  55. data/examples/js/FABridge.js +0 -604
  56. data/examples/js/WebSocketMain.swf +0 -0
  57. data/examples/js/swfobject.js +0 -4
  58. data/examples/js/web_socket.js +0 -312
  59. data/lib/em-websocket/handler_factory.rb +0 -107
  60. 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
@@ -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(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(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(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
- # NOTE I modified these to be compliant with the rule that client data must be masked
203
- describe "server side" do
204
- describe "examples from the spec" do
205
- it "rejects a single-frame unmasked text message from the client" do
206
- lambda {
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
- it "Ping request" do
223
- @f.should_receive(:message).with(:ping, '', 'Hello')
224
- @f << "\x89\x85\x01\x01\x01\x01Idmmn"
225
- end
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
- it "a pong response" do
228
- @f.should_receive(:message).with(:pong, '', 'Hello')
229
- @f << "\x8a\x85\x01\x01\x01\x01Idmmn"
230
- end
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
- it "256 bytes binary message in a single masked frame" do
233
- data = "b"*256
234
- masked_data = "c"*256
235
- @f.should_receive(:message).with(:binary, '', data)
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
- it "64KiB binary message in a single masked frame" do
240
- data = "b"*65536
241
- masked_data = "c"*65536
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
- describe "other tests" do
248
- it "should raise a DataError if an invalid frame type is requested" do
249
- lambda {
250
- # Opcode 3 is not supported by this draft
251
- @f << "\x83\x85\x01\x01\x01\x01Idmmn"
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
- it "should accept a fragmented masked text message in 3 frames" do
256
- @f.should_receive(:message).with(:text, '', 'Hello world')
257
- @f << "\x01\x83\x01\x01\x01\x01Idm"
258
- @f << "\x00\x82\x01\x01\x01\x01mn"
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 "client side" do
265
- before do
266
- @f.mask_outbound_messages = true
267
- @f.require_masked_inbound_messages = false
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
- it "a pong response" do
292
- @f.should_receive(:message).with(:pong, '', 'Hello')
293
- @f << "\x8a\x05Hello"
294
- end
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
- describe "other tests" do
310
- it "should raise a DataError if an invalid frame type is requested" do
311
- lambda {
312
- # Opcode 3 is not supported by this draft
313
- @f << "\x83\x05Hello"
314
- }.should raise_error(EventMachine::WebSocket::DataError, "Unknown opcode")
315
- end
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
- it "should accept a fragmented unmasked text message in 3 frames" do
318
- @f.should_receive(:message).with(:text, '', 'Hello world')
319
- @f << "\x01\x03Hel"
320
- @f << "\x00\x02lo"
321
- @f << "\x80\x06 world"
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