sonixlabs-em-websocket 0.3.7

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 (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