em-websocket 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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