sonixlabs-em-websocket 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG.rdoc +80 -0
  3. data/Gemfile +3 -0
  4. data/README.md +98 -0
  5. data/Rakefile +11 -0
  6. data/em-websocket.gemspec +27 -0
  7. data/examples/echo.rb +8 -0
  8. data/examples/flash_policy_file_server.rb +21 -0
  9. data/examples/js/FABridge.js +604 -0
  10. data/examples/js/WebSocketMain.swf +0 -0
  11. data/examples/js/swfobject.js +4 -0
  12. data/examples/js/web_socket.js +312 -0
  13. data/examples/multicast.rb +47 -0
  14. data/examples/test.html +30 -0
  15. data/lib/em-websocket/client_connection.rb +19 -0
  16. data/lib/em-websocket/close03.rb +11 -0
  17. data/lib/em-websocket/close05.rb +11 -0
  18. data/lib/em-websocket/close06.rb +16 -0
  19. data/lib/em-websocket/close75.rb +10 -0
  20. data/lib/em-websocket/connection.rb +184 -0
  21. data/lib/em-websocket/debugger.rb +17 -0
  22. data/lib/em-websocket/framing03.rb +167 -0
  23. data/lib/em-websocket/framing04.rb +15 -0
  24. data/lib/em-websocket/framing05.rb +168 -0
  25. data/lib/em-websocket/framing07.rb +180 -0
  26. data/lib/em-websocket/framing76.rb +114 -0
  27. data/lib/em-websocket/handler.rb +56 -0
  28. data/lib/em-websocket/handler03.rb +10 -0
  29. data/lib/em-websocket/handler05.rb +10 -0
  30. data/lib/em-websocket/handler06.rb +10 -0
  31. data/lib/em-websocket/handler07.rb +10 -0
  32. data/lib/em-websocket/handler08.rb +10 -0
  33. data/lib/em-websocket/handler13.rb +10 -0
  34. data/lib/em-websocket/handler75.rb +9 -0
  35. data/lib/em-websocket/handler76.rb +12 -0
  36. data/lib/em-websocket/handler_factory.rb +107 -0
  37. data/lib/em-websocket/handshake04.rb +75 -0
  38. data/lib/em-websocket/handshake75.rb +21 -0
  39. data/lib/em-websocket/handshake76.rb +71 -0
  40. data/lib/em-websocket/masking04.rb +63 -0
  41. data/lib/em-websocket/message_processor_03.rb +38 -0
  42. data/lib/em-websocket/message_processor_06.rb +52 -0
  43. data/lib/em-websocket/version.rb +5 -0
  44. data/lib/em-websocket/websocket.rb +45 -0
  45. data/lib/em-websocket.rb +23 -0
  46. data/lib/sonixlabs-em-websocket.rb +1 -0
  47. data/spec/helper.rb +146 -0
  48. data/spec/integration/client_examples.rb +48 -0
  49. data/spec/integration/common_spec.rb +118 -0
  50. data/spec/integration/draft03_spec.rb +270 -0
  51. data/spec/integration/draft05_spec.rb +48 -0
  52. data/spec/integration/draft06_spec.rb +88 -0
  53. data/spec/integration/draft13_spec.rb +75 -0
  54. data/spec/integration/draft75_spec.rb +117 -0
  55. data/spec/integration/draft76_spec.rb +230 -0
  56. data/spec/integration/shared_examples.rb +91 -0
  57. data/spec/unit/framing_spec.rb +325 -0
  58. data/spec/unit/handler_spec.rb +147 -0
  59. data/spec/unit/masking_spec.rb +27 -0
  60. data/spec/unit/message_processor_spec.rb +36 -0
  61. metadata +198 -0
@@ -0,0 +1,88 @@
1
+ require 'helper'
2
+ require 'integration/client_examples'
3
+
4
+ describe "draft06" do
5
+ include EM::SpecHelper
6
+ default_timeout 1
7
+
8
+ before :each do
9
+ @request = {
10
+ :port => 80,
11
+ :method => "GET",
12
+ :path => "/demo",
13
+ :headers => {
14
+ 'Host' => 'example.com',
15
+ 'Upgrade' => 'websocket',
16
+ 'Connection' => 'Upgrade',
17
+ 'Sec-WebSocket-Key' => 'dGhlIHNhbXBsZSBub25jZQ==',
18
+ 'Sec-WebSocket-Protocol' => 'sample',
19
+ 'Sec-WebSocket-Origin' => 'http://example.com',
20
+ 'Sec-WebSocket-Version' => '6'
21
+ }
22
+ }
23
+
24
+ @response = {
25
+ :protocol => "HTTP/1.1 101 Switching Protocols\r\n",
26
+ :headers => {
27
+ "Upgrade" => "websocket",
28
+ "Connection" => "Upgrade",
29
+ "Sec-WebSocket-Accept" => "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
30
+ }
31
+ }
32
+ end
33
+
34
+ def start_server
35
+ EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
36
+ yield ws
37
+ }
38
+ end
39
+
40
+ def start_client
41
+ client = EM.connect('0.0.0.0', 12345, Draft03FakeWebSocketClient)
42
+ client.send_data(format_request(@request))
43
+ yield client if block_given?
44
+ end
45
+
46
+ it "should open connection" do
47
+ em {
48
+ start_server { |server|
49
+ server.onopen {
50
+ server.instance_variable_get(:@handler).class.should == EventMachine::WebSocket::Handler06
51
+ }
52
+ }
53
+
54
+ start_client { |client|
55
+ client.onopen {
56
+ client.handshake_response.lines.sort.
57
+ should == format_response(@response).lines.sort
58
+ done
59
+ }
60
+ }
61
+ }
62
+ end
63
+
64
+ it "should accept a single-frame text message (masked)" do
65
+ em {
66
+ start_server { |server|
67
+ server.onmessage { |msg|
68
+ msg.should == 'Hello'
69
+ if msg.respond_to?(:encoding)
70
+ msg.encoding.should == Encoding.find("UTF-8")
71
+ end
72
+ done
73
+ }
74
+ server.onerror {
75
+ fail
76
+ }
77
+ }
78
+
79
+ start_client { |client|
80
+ client.onopen {
81
+ client.send_data("\x00\x00\x01\x00\x84\x05Ielln")
82
+ }
83
+ }
84
+ }
85
+ end
86
+
87
+ it_should_behave_like "a websocket client"
88
+ end
@@ -0,0 +1,75 @@
1
+ require 'helper'
2
+ require 'integration/shared_examples'
3
+ require 'integration/client_examples'
4
+
5
+ describe "draft13" do
6
+ include EM::SpecHelper
7
+ default_timeout 1
8
+
9
+ before :each do
10
+ @request = {
11
+ :port => 80,
12
+ :method => "GET",
13
+ :path => "/demo",
14
+ :headers => {
15
+ 'Host' => 'example.com',
16
+ 'Upgrade' => 'websocket',
17
+ 'Connection' => 'Upgrade',
18
+ 'Sec-WebSocket-Key' => 'dGhlIHNhbXBsZSBub25jZQ==',
19
+ 'Sec-WebSocket-Protocol' => 'sample',
20
+ 'Sec-WebSocket-Origin' => 'http://example.com',
21
+ 'Sec-WebSocket-Version' => '13'
22
+ }
23
+ }
24
+
25
+ @response = {
26
+ :protocol => "HTTP/1.1 101 Switching Protocols\r\n",
27
+ :headers => {
28
+ "Upgrade" => "websocket",
29
+ "Connection" => "Upgrade",
30
+ "Sec-WebSocket-Accept" => "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
31
+ }
32
+ }
33
+ end
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, Draft07FakeWebSocketClient)
44
+ client.send_data(format_request(@request))
45
+ yield client if block_given?
46
+ end
47
+ end
48
+
49
+ it "should send back the correct handshake response" do
50
+ em {
51
+ EM.add_timer(0.1) do
52
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
53
+
54
+ # Create a fake client which sends draft 07 handshake
55
+ connection = EM.connect('0.0.0.0', 12345, Draft07FakeWebSocketClient)
56
+ connection.send_data(format_request(@request))
57
+
58
+ connection.onopen {
59
+ connection.handshake_response.lines.sort.
60
+ should == format_response(@response).lines.sort
61
+ done
62
+ }
63
+ end
64
+ }
65
+ end
66
+
67
+ it_should_behave_like "a websocket client" do
68
+ def start_server
69
+ EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
70
+ yield ws
71
+ }
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,117 @@
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
+ include EM::SpecHelper
10
+ default_timeout 1
11
+
12
+ it_behaves_like "a websocket server" do
13
+ def start_server
14
+ EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
15
+ yield ws
16
+ }
17
+ end
18
+
19
+ def start_client
20
+ client = Draft75WebSocketClient.new
21
+ yield client if block_given?
22
+ end
23
+ end
24
+
25
+ it "should automatically complete WebSocket handshake" do
26
+ em {
27
+ MSG = "Hello World!"
28
+ EventMachine.add_timer(0.1) do
29
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
30
+ http.errback { fail }
31
+ http.callback { http.response_header.status.should == 101 }
32
+
33
+ http.stream { |msg|
34
+ msg.should == MSG
35
+ EventMachine.stop
36
+ }
37
+ end
38
+
39
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
40
+ ws.onopen {
41
+ ws.send MSG
42
+ }
43
+ end
44
+ }
45
+ end
46
+
47
+ it "should split multiple messages into separate callbacks" do
48
+ em {
49
+ messages = %w[1 2]
50
+ received = []
51
+
52
+ EventMachine.add_timer(0.1) do
53
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
54
+ http.errback { fail }
55
+ http.stream {|msg|}
56
+ http.callback {
57
+ http.response_header.status.should == 101
58
+ http.send messages[0]
59
+ http.send messages[1]
60
+ }
61
+ end
62
+
63
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
64
+ ws.onopen {}
65
+ ws.onclose {}
66
+ ws.onmessage {|msg|
67
+ msg.should == messages[received.size]
68
+ received.push msg
69
+
70
+ EventMachine.stop if received.size == messages.size
71
+ }
72
+ end
73
+ }
74
+ end
75
+
76
+ it "should call onclose callback when client closes connection" do
77
+ em {
78
+ EventMachine.add_timer(0.1) do
79
+ http = EventMachine::HttpRequest.new('ws://127.0.0.1:12345/').get :timeout => 0
80
+ http.errback { fail }
81
+ http.callback {
82
+ http.response_header.status.should == 101
83
+ http.close_connection
84
+ }
85
+ http.stream{|msg|}
86
+ end
87
+
88
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
89
+ ws.onopen {}
90
+ ws.onclose {
91
+ ws.state.should == :closed
92
+ EventMachine.stop
93
+ }
94
+ end
95
+ }
96
+ end
97
+
98
+ it "should call onerror callback with raised exception and close connection on bad handshake" do
99
+ em {
100
+ EventMachine.add_timer(0.1) do
101
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:12345/').get :timeout => 0
102
+ http.errback { http.response_header.status.should == 0 }
103
+ http.callback { fail }
104
+ end
105
+
106
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) do |ws|
107
+ ws.onopen { fail }
108
+ ws.onclose { EventMachine.stop }
109
+ ws.onerror {|e|
110
+ e.should be_an_instance_of EventMachine::WebSocket::HandshakeError
111
+ e.message.should match('Connection and Upgrade headers required')
112
+ EventMachine.stop
113
+ }
114
+ end
115
+ }
116
+ end
117
+ end
@@ -0,0 +1,230 @@
1
+ require 'helper'
2
+ require 'integration/shared_examples'
3
+
4
+ describe "WebSocket server draft76" do
5
+ include EM::SpecHelper
6
+ default_timeout 1
7
+
8
+ before :each do
9
+ @request = {
10
+ :port => 80,
11
+ :method => "GET",
12
+ :path => "/demo",
13
+ :headers => {
14
+ 'Host' => 'example.com',
15
+ 'Connection' => 'Upgrade',
16
+ 'Sec-WebSocket-Key2' => '12998 5 Y3 1 .P00',
17
+ 'Sec-WebSocket-Protocol' => 'sample',
18
+ 'Upgrade' => 'WebSocket',
19
+ 'Sec-WebSocket-Key1' => '4 @1 46546xW%0l 1 5',
20
+ 'Origin' => 'http://example.com'
21
+ },
22
+ :body => '^n:ds[4U'
23
+ }
24
+
25
+ @response = {
26
+ :headers => {
27
+ "Upgrade" => "WebSocket",
28
+ "Connection" => "Upgrade",
29
+ "Sec-WebSocket-Location" => "ws://example.com/demo",
30
+ "Sec-WebSocket-Origin" => "http://example.com",
31
+ "Sec-WebSocket-Protocol" => "sample"
32
+ },
33
+ :body => "8jKS\'y:G*Co,Wxa-"
34
+ }
35
+ end
36
+
37
+ it_behaves_like "a websocket server" do
38
+ def start_server
39
+ EM::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
40
+ yield ws
41
+ }
42
+ end
43
+
44
+ def start_client
45
+ client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
46
+ client.send_data(format_request(@request))
47
+ yield client if block_given?
48
+ end
49
+ end
50
+
51
+ it "should send back the correct handshake response" do
52
+ em {
53
+ EM.add_timer(0.1) do
54
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
55
+
56
+ # Create a fake client which sends draft 76 handshake
57
+ connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
58
+ connection.send_data(format_request(@request))
59
+
60
+ connection.onopen {
61
+ connection.handshake_response.lines.sort.
62
+ should == format_response(@response).lines.sort
63
+ done
64
+ }
65
+ end
66
+ }
67
+ end
68
+
69
+ it "should send closing frame back and close the connection after recieving closing frame" do
70
+ em {
71
+ EM.add_timer(0.1) do
72
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
73
+
74
+ # Create a fake client which sends draft 76 handshake
75
+ connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
76
+ connection.send_data(format_request(@request))
77
+
78
+ # Send closing frame after handshake complete
79
+ connection.onopen {
80
+ connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
81
+ }
82
+
83
+ # Check that this causes a termination string to be returned and the
84
+ # connection close
85
+ connection.onclose {
86
+ connection.packets[0].should ==
87
+ EM::WebSocket::Handler76::TERMINATE_STRING
88
+ done
89
+ }
90
+ end
91
+ }
92
+ end
93
+
94
+ it "should ignore any data received after the closing frame" do
95
+ em {
96
+ EM.add_timer(0.1) do
97
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
98
+ # Fail if foobar message is received
99
+ ws.onmessage { |msg|
100
+ fail
101
+ }
102
+ }
103
+
104
+ # Create a fake client which sends draft 76 handshake
105
+ connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
106
+ connection.send_data(format_request(@request))
107
+
108
+ # Send closing frame after handshake complete, followed by another msg
109
+ connection.onopen {
110
+ connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
111
+ connection.send('foobar')
112
+ }
113
+
114
+ connection.onclose {
115
+ done
116
+ }
117
+ end
118
+ }
119
+ end
120
+
121
+ it "should accept null bytes within the frame after a line return" do
122
+ em {
123
+ EM.add_timer(0.1) do
124
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |ws|
125
+ ws.onmessage { |msg|
126
+ msg.should == "\n\000"
127
+ }
128
+ }
129
+
130
+ # Create a fake client which sends draft 76 handshake
131
+ connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
132
+ connection.send_data(format_request(@request))
133
+
134
+ # Send closing frame after handshake complete
135
+ connection.onopen {
136
+ connection.send_data("\000\n\000\377")
137
+ connection.send_data(EM::WebSocket::Handler76::TERMINATE_STRING)
138
+ }
139
+
140
+ connection.onclose {
141
+ done
142
+ }
143
+ end
144
+ }
145
+ end
146
+
147
+ it "should handle unreasonable frame lengths by calling onerror callback" do
148
+ em {
149
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
150
+ server.onerror { |error|
151
+ error.should be_an_instance_of EM::WebSocket::DataError
152
+ error.message.should == "Frame length too long (1180591620717411303296 bytes)"
153
+ done
154
+ }
155
+ }
156
+
157
+ # Create a fake client which sends draft 76 handshake
158
+ client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
159
+ client.send_data(format_request(@request))
160
+
161
+ # This particular frame indicates a message length of
162
+ # 1180591620717411303296 bytes. Such a message would previously cause
163
+ # a "bignum too big to convert into `long'" error.
164
+ # However it is clearly unreasonable and should be rejected.
165
+ client.onopen {
166
+ client.send_data("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
167
+ }
168
+ }
169
+ end
170
+
171
+ it "should handle impossible frames by calling onerror callback" do
172
+ em {
173
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
174
+ server.onerror { |error|
175
+ error.should be_an_instance_of EM::WebSocket::DataError
176
+ error.message.should == "Invalid frame received"
177
+ done
178
+ }
179
+ }
180
+
181
+ # Create a fake client which sends draft 76 handshake
182
+ client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
183
+ client.send_data(format_request(@request))
184
+
185
+ client.onopen {
186
+ client.send_data("foobar") # Does not start with \x00 or \xff
187
+ }
188
+ }
189
+ end
190
+
191
+ it "should handle invalid http requests by raising HandshakeError passed to onerror callback" do
192
+ em {
193
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { |server|
194
+ server.onerror { |error|
195
+ error.should be_an_instance_of EM::WebSocket::HandshakeError
196
+ error.message.should == "Invalid HTTP header"
197
+ done
198
+ }
199
+ }
200
+
201
+ client = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
202
+ client.send_data("This is not a HTTP header\r\n\r\n")
203
+ }
204
+ end
205
+
206
+ it "should handle handshake request split into two TCP packets" do
207
+ em {
208
+ EM.add_timer(0.1) do
209
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 12345) { }
210
+
211
+ # Create a fake client which sends draft 76 handshake
212
+ connection = EM.connect('0.0.0.0', 12345, FakeWebSocketClient)
213
+ data = format_request(@request)
214
+ # Sends first half of the request
215
+ connection.send_data(data[0...(data.length / 2)])
216
+
217
+ connection.onopen {
218
+ connection.handshake_response.lines.sort.
219
+ should == format_response(@response).lines.sort
220
+ done
221
+ }
222
+
223
+ EM.add_timer(0.1) do
224
+ # Sends second half of the request
225
+ connection.send_data(data[(data.length / 2)..-1])
226
+ end
227
+ end
228
+ }
229
+ end
230
+ end
@@ -0,0 +1,91 @@
1
+ # encoding: UTF-8
2
+
3
+ shared_examples_for "a websocket server" do
4
+ it "should call onerror if an application error raised in onopen" do
5
+ em {
6
+ start_server { |ws|
7
+ ws.onopen {
8
+ raise "application error"
9
+ }
10
+
11
+ ws.onerror { |e|
12
+ e.message.should == "application error"
13
+ done
14
+ }
15
+ }
16
+
17
+ start_client
18
+ }
19
+ end
20
+
21
+ it "should call onerror if an application error raised in onmessage" do
22
+ em {
23
+ start_server { |server|
24
+ server.onmessage {
25
+ raise "application error"
26
+ }
27
+
28
+ server.onerror { |e|
29
+ e.message.should == "application error"
30
+ done
31
+ }
32
+ }
33
+
34
+ start_client { |client|
35
+ client.onopen {
36
+ client.send('a message')
37
+ }
38
+ }
39
+ }
40
+ end
41
+
42
+ it "should call onerror in an application error raised in onclose" do
43
+ em {
44
+ start_server { |server|
45
+ server.onclose {
46
+ raise "application error"
47
+ }
48
+
49
+ server.onerror { |e|
50
+ e.message.should == "application error"
51
+ done
52
+ }
53
+ }
54
+
55
+ start_client { |client|
56
+ client.onopen {
57
+ EM.add_timer(0.1) {
58
+ client.close_connection
59
+ }
60
+ }
61
+ }
62
+ }
63
+ end
64
+
65
+ # Only run these tests on ruby 1.9
66
+ if "a".respond_to?(:force_encoding)
67
+ it "should raise error if you try to send non utf8 text data to ws" do
68
+ em {
69
+ start_server { |server|
70
+ server.onopen {
71
+ # Create a string which claims to be UTF-8 but which is not
72
+ s = "ê" # utf-8 string
73
+ s.encode!("ISO-8859-1")
74
+ s.force_encoding("UTF-8")
75
+ s.valid_encoding?.should == false # now invalid utf8
76
+
77
+ # Send non utf8 encoded data
78
+ server.send(s)
79
+ }
80
+ server.onerror { |error|
81
+ error.class.should == EventMachine::WebSocket::WebSocketError
82
+ error.message.should == "Data sent to WebSocket must be valid UTF-8 but was UTF-8 (valid: false)"
83
+ done
84
+ }
85
+ }
86
+
87
+ start_client { }
88
+ }
89
+ end
90
+ end
91
+ end