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.
Files changed (39) hide show
  1. data/CHANGELOG.rdoc +10 -0
  2. data/README.md +16 -0
  3. data/examples/test.html +11 -8
  4. data/lib/em-websocket.rb +6 -3
  5. data/lib/em-websocket/close03.rb +11 -0
  6. data/lib/em-websocket/close05.rb +11 -0
  7. data/lib/em-websocket/close06.rb +16 -0
  8. data/lib/em-websocket/close75.rb +10 -0
  9. data/lib/em-websocket/connection.rb +58 -32
  10. data/lib/em-websocket/framing03.rb +9 -30
  11. data/lib/em-websocket/framing04.rb +15 -0
  12. data/lib/em-websocket/framing05.rb +157 -0
  13. data/lib/em-websocket/framing76.rb +5 -6
  14. data/lib/em-websocket/handler.rb +2 -4
  15. data/lib/em-websocket/handler03.rb +2 -6
  16. data/lib/em-websocket/handler05.rb +10 -0
  17. data/lib/em-websocket/handler06.rb +10 -0
  18. data/lib/em-websocket/handler75.rb +1 -0
  19. data/lib/em-websocket/handler76.rb +1 -0
  20. data/lib/em-websocket/handler_factory.rb +41 -22
  21. data/lib/em-websocket/handshake04.rb +35 -0
  22. data/lib/em-websocket/handshake75.rb +4 -4
  23. data/lib/em-websocket/handshake76.rb +8 -8
  24. data/lib/em-websocket/masking04.rb +27 -0
  25. data/lib/em-websocket/message_processor_03.rb +33 -0
  26. data/lib/em-websocket/message_processor_06.rb +46 -0
  27. data/lib/em-websocket/version.rb +1 -1
  28. data/spec/helper.rb +54 -2
  29. data/spec/integration/common_spec.rb +115 -0
  30. data/spec/integration/draft03_spec.rb +26 -11
  31. data/spec/integration/draft05_spec.rb +45 -0
  32. data/spec/integration/draft06_spec.rb +79 -0
  33. data/spec/integration/draft75_spec.rb +115 -0
  34. data/spec/integration/draft76_spec.rb +25 -10
  35. data/spec/integration/shared_examples.rb +62 -0
  36. data/spec/unit/framing_spec.rb +55 -0
  37. data/spec/unit/masking_spec.rb +18 -0
  38. metadata +29 -33
  39. 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 = lambda {
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 = lambda {
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 = lambda {
102
+ connection.onopen {
88
103
  connection.send_data("\x02\x05Hello")
89
104
  }
90
105
 
91
- connection.onmessage = lambda { |frame|
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 = lambda {
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 = lambda {
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 = lambda {
169
+ connection.onopen {
155
170
  connection.send_data("\x01\x00")
156
171
  }
157
172
 
158
173
  # Check that close ack received
159
- connection.onmessage = lambda { |frame|
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 = lambda { |frame|
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 = lambda {
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 = lambda { |frame|
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