websocket 1.0.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 (73) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +11 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +5 -0
  5. data/README.md +117 -0
  6. data/Rakefile +23 -0
  7. data/autobahn-client.json +11 -0
  8. data/autobahn-server.json +10 -0
  9. data/examples/base.rb +162 -0
  10. data/examples/client.rb +70 -0
  11. data/examples/server.rb +56 -0
  12. data/examples/tests/autobahn_client.rb +52 -0
  13. data/examples/tests/echo_client.rb +42 -0
  14. data/examples/tests/echo_server.rb +45 -0
  15. data/lib/websocket.rb +16 -0
  16. data/lib/websocket/frame.rb +11 -0
  17. data/lib/websocket/frame/base.rb +45 -0
  18. data/lib/websocket/frame/data.rb +52 -0
  19. data/lib/websocket/frame/handler.rb +15 -0
  20. data/lib/websocket/frame/handler/base.rb +41 -0
  21. data/lib/websocket/frame/handler/handler03.rb +162 -0
  22. data/lib/websocket/frame/handler/handler04.rb +19 -0
  23. data/lib/websocket/frame/handler/handler05.rb +17 -0
  24. data/lib/websocket/frame/handler/handler07.rb +34 -0
  25. data/lib/websocket/frame/handler/handler75.rb +79 -0
  26. data/lib/websocket/frame/incoming.rb +43 -0
  27. data/lib/websocket/frame/incoming/client.rb +9 -0
  28. data/lib/websocket/frame/incoming/server.rb +9 -0
  29. data/lib/websocket/frame/outgoing.rb +28 -0
  30. data/lib/websocket/frame/outgoing/client.rb +9 -0
  31. data/lib/websocket/frame/outgoing/server.rb +9 -0
  32. data/lib/websocket/handshake.rb +10 -0
  33. data/lib/websocket/handshake/base.rb +67 -0
  34. data/lib/websocket/handshake/client.rb +80 -0
  35. data/lib/websocket/handshake/handler.rb +20 -0
  36. data/lib/websocket/handshake/handler/base.rb +25 -0
  37. data/lib/websocket/handshake/handler/client.rb +19 -0
  38. data/lib/websocket/handshake/handler/client01.rb +19 -0
  39. data/lib/websocket/handshake/handler/client04.rb +47 -0
  40. data/lib/websocket/handshake/handler/client75.rb +25 -0
  41. data/lib/websocket/handshake/handler/client76.rb +85 -0
  42. data/lib/websocket/handshake/handler/server.rb +30 -0
  43. data/lib/websocket/handshake/handler/server04.rb +38 -0
  44. data/lib/websocket/handshake/handler/server75.rb +26 -0
  45. data/lib/websocket/handshake/handler/server76.rb +71 -0
  46. data/lib/websocket/handshake/server.rb +56 -0
  47. data/lib/websocket/version.rb +3 -0
  48. data/spec/frame/incoming_03_spec.rb +117 -0
  49. data/spec/frame/incoming_04_spec.rb +117 -0
  50. data/spec/frame/incoming_05_spec.rb +133 -0
  51. data/spec/frame/incoming_07_spec.rb +133 -0
  52. data/spec/frame/incoming_75_spec.rb +59 -0
  53. data/spec/frame/incoming_common_spec.rb +22 -0
  54. data/spec/frame/outgoing_03_spec.rb +77 -0
  55. data/spec/frame/outgoing_04_spec.rb +77 -0
  56. data/spec/frame/outgoing_05_spec.rb +77 -0
  57. data/spec/frame/outgoing_07_spec.rb +77 -0
  58. data/spec/frame/outgoing_75_spec.rb +41 -0
  59. data/spec/frame/outgoing_common_spec.rb +15 -0
  60. data/spec/handshake/client_04_spec.rb +20 -0
  61. data/spec/handshake/client_75_spec.rb +11 -0
  62. data/spec/handshake/client_76_spec.rb +20 -0
  63. data/spec/handshake/server_04_spec.rb +18 -0
  64. data/spec/handshake/server_75_spec.rb +11 -0
  65. data/spec/handshake/server_76_spec.rb +46 -0
  66. data/spec/spec_helper.rb +4 -0
  67. data/spec/support/all_client_drafts.rb +77 -0
  68. data/spec/support/all_server_drafts.rb +86 -0
  69. data/spec/support/handshake_requests.rb +72 -0
  70. data/spec/support/incoming_frames.rb +30 -0
  71. data/spec/support/outgoing_frames.rb +14 -0
  72. data/websocket.gemspec +21 -0
  73. metadata +163 -0
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Client draft 4 handshake' do
4
+ let(:handshake) { WebSocket::Handshake::Client.new({ :uri => 'ws://example.com/demo', :origin => 'http://example.com', :version => version }.merge(@request_params || {})) }
5
+
6
+ let(:version) { 4 }
7
+ let(:client_request) { client_handshake_04({ :key => handshake.send(:key), :version => version }.merge(@request_params || {})) }
8
+ let(:server_response) { server_handshake_04({ :accept => handshake.send(:accept) }.merge(@request_params || {})) }
9
+
10
+ it_should_behave_like 'all client drafts'
11
+
12
+ it "should disallow client with invalid challenge" do
13
+ @request_params = { :accept => "invalid" }
14
+ handshake << server_response
15
+
16
+ handshake.should be_finished
17
+ handshake.should_not be_valid
18
+ handshake.error.should eql(:invalid_accept)
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Client draft 75 handshake' do
4
+ let(:handshake) { WebSocket::Handshake::Client.new({ :uri => 'ws://example.com/demo', :origin => 'http://example.com', :version => version }.merge(@request_params || {})) }
5
+
6
+ let(:version) { 75 }
7
+ let(:client_request) { client_handshake_75(@request_params || {}) }
8
+ let(:server_response) { server_handshake_75(@request_params || {}) }
9
+
10
+ it_should_behave_like 'all client drafts'
11
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Client draft 76 handshake' do
4
+ let(:handshake) { WebSocket::Handshake::Client.new({ :uri => 'ws://example.com/demo', :origin => 'http://example.com', :version => version }.merge(@request_params || {})) }
5
+
6
+ let(:version) { 76 }
7
+ let(:client_request) { client_handshake_76({ :key1 => handshake.key1, :key2 => handshake.key2, :key3 => handshake.key3 }.merge(@request_params || {})) }
8
+ let(:server_response) { server_handshake_76({ :challenge => handshake.send(:challenge) }.merge(@request_params || {})) }
9
+
10
+ it_should_behave_like 'all client drafts'
11
+
12
+ it "should disallow client with invalid challenge" do
13
+ @request_params = { :challenge => "invalid" }
14
+ handshake << server_response
15
+
16
+ handshake.should be_finished
17
+ handshake.should_not be_valid
18
+ handshake.error.should eql(:invalid_challenge)
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Server draft 04 handshake' do
4
+ let(:handshake) { WebSocket::Handshake::Server.new }
5
+ let(:version) { 04 }
6
+ let(:client_request) { client_handshake_04(@request_params || {}) }
7
+ let(:server_response) { server_handshake_04(@request_params || {}) }
8
+
9
+ it_should_behave_like 'all server drafts'
10
+
11
+ it "should disallow request without Sec-WebSocket-Key" do
12
+ handshake << client_request.gsub(/^Sec-WebSocket-Key:.*\n/, '')
13
+
14
+ handshake.should be_finished
15
+ handshake.should_not be_valid
16
+ handshake.error.should eql(:invalid_handshake_authentication)
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Server draft 75 handshake' do
4
+ let(:handshake) { WebSocket::Handshake::Server.new }
5
+
6
+ let(:version) { 75 }
7
+ let(:client_request) { client_handshake_75(@request_params || {}) }
8
+ let(:server_response) { server_handshake_75(@request_params || {}) }
9
+
10
+ it_should_behave_like 'all server drafts'
11
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Server draft 76 handshake' do
4
+ let(:handshake) { WebSocket::Handshake::Server.new }
5
+ let(:version) { 76 }
6
+ let(:client_request) { client_handshake_76(@request_params || {}) }
7
+ let(:server_response) { server_handshake_76(@request_params || {}) }
8
+
9
+ it_should_behave_like 'all server drafts'
10
+
11
+ it "should disallow request without spaces in key 1" do
12
+ @request_params = { :key1 => "4@146546xW%0l15" }
13
+ handshake << client_request
14
+
15
+ handshake.should be_finished
16
+ handshake.should_not be_valid
17
+ handshake.error.should eql(:invalid_handshake_authentication)
18
+ end
19
+
20
+ it "should disallow request without spaces in key 2" do
21
+ @request_params = { :key2 => "129985Y31.P00" }
22
+ handshake << client_request
23
+
24
+ handshake.should be_finished
25
+ handshake.should_not be_valid
26
+ handshake.error.should eql(:invalid_handshake_authentication)
27
+ end
28
+
29
+ it "should disallow request with invalid number of spaces or numbers in key 1" do
30
+ @request_params = { :key1 => "4 @1 46546xW%0l 1 5" }
31
+ handshake << client_request
32
+
33
+ handshake.should be_finished
34
+ handshake.should_not be_valid
35
+ handshake.error.should eql(:invalid_handshake_authentication)
36
+ end
37
+
38
+ it "should disallow request with invalid number of spaces or numbers in key 2" do
39
+ @request_params = { :key2 => "12998 5 Y3 1 .P00" }
40
+ handshake << client_request
41
+
42
+ handshake.should be_finished
43
+ handshake.should_not be_valid
44
+ handshake.error.should eql(:invalid_handshake_authentication)
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ require 'rspec'
2
+
3
+ require 'websocket'
4
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
@@ -0,0 +1,77 @@
1
+ shared_examples_for 'all client drafts' do
2
+ def validate_request
3
+ handshake.to_s.should eql(client_request)
4
+
5
+ handshake << server_response
6
+
7
+ handshake.error.should be_nil
8
+ handshake.should be_finished
9
+ handshake.should be_valid
10
+ end
11
+
12
+ it "should be valid" do
13
+ handshake << server_response
14
+
15
+ handshake.error.should be_nil
16
+ handshake.should be_finished
17
+ handshake.should be_valid
18
+ end
19
+
20
+ it "should return valid version" do
21
+ handshake.version.should eql(version)
22
+ end
23
+
24
+ it "should return valid host" do
25
+ @request_params = { :host => "www.test.cc" }
26
+ handshake.host.should eql('www.test.cc')
27
+ end
28
+
29
+ it "should return valid path" do
30
+ @request_params = { :path => "/custom" }
31
+ handshake.path.should eql('/custom')
32
+ end
33
+
34
+ it "should return valid query" do
35
+ @request_params = { :query => "aaa=bbb" }
36
+ handshake.query.should eql("aaa=bbb")
37
+ end
38
+
39
+ it "should return valid port" do
40
+ @request_params = { :port => 123 }
41
+ handshake.port.should eql(123)
42
+ end
43
+
44
+ it "should return valid response" do
45
+ validate_request
46
+ end
47
+
48
+ it "should allow custom path" do
49
+ @request_params = { :path => "/custom" }
50
+ validate_request
51
+ end
52
+
53
+ it "should allow query in path" do
54
+ @request_params = { :query => "test=true" }
55
+ validate_request
56
+ end
57
+
58
+ it "should allow custom port" do
59
+ @request_params = { :port => 123 }
60
+ validate_request
61
+ end
62
+
63
+ it "should recognize unfinished requests" do
64
+ handshake << server_response[0..-20]
65
+
66
+ handshake.should_not be_finished
67
+ handshake.should_not be_valid
68
+ end
69
+
70
+ it "should disallow requests with invalid request method" do
71
+ handshake << server_response.gsub('101', '404')
72
+
73
+ handshake.should be_finished
74
+ handshake.should_not be_valid
75
+ handshake.error.should eql(:invalid_status_code)
76
+ end
77
+ end
@@ -0,0 +1,86 @@
1
+ shared_examples_for 'all server drafts' do
2
+ def validate_request
3
+ handshake << client_request
4
+
5
+ handshake.error.should be_nil
6
+ handshake.should be_finished
7
+ handshake.should be_valid
8
+ handshake.to_s.should eql(server_response)
9
+ end
10
+
11
+ it "should be valid" do
12
+ handshake << client_request
13
+
14
+ handshake.error.should be_nil
15
+ handshake.should be_finished
16
+ handshake.should be_valid
17
+ end
18
+
19
+ it "should return valid version" do
20
+ handshake << client_request
21
+
22
+ handshake.version.should eql(version)
23
+ end
24
+
25
+ it "should return valid host" do
26
+ @request_params = { :host => "www.test.cc" }
27
+ handshake << client_request
28
+
29
+ handshake.host.should eql('www.test.cc')
30
+ end
31
+
32
+ it "should return valid path" do
33
+ @request_params = { :path => "/custom" }
34
+ handshake << client_request
35
+
36
+ handshake.path.should eql('/custom')
37
+ end
38
+
39
+ it "should return valid query" do
40
+ @request_params = { :path => "/custom?aaa=bbb" }
41
+ handshake << client_request
42
+
43
+ handshake.query.should eql('aaa=bbb')
44
+ end
45
+
46
+ it "should return valid port" do
47
+ @request_params = { :port => 123 }
48
+ handshake << client_request
49
+
50
+ handshake.port.should eql("123")
51
+ end
52
+
53
+ it "should return valid response" do
54
+ validate_request
55
+ end
56
+
57
+ it "should allow custom path" do
58
+ @request_params = { :path => "/custom" }
59
+ validate_request
60
+ end
61
+
62
+ it "should allow query in path" do
63
+ @request_params = { :path => "/custom?test=true" }
64
+ validate_request
65
+ end
66
+
67
+ it "should allow custom port" do
68
+ @request_params = { :port => 123 }
69
+ validate_request
70
+ end
71
+
72
+ it "should recognize unfinished requests" do
73
+ handshake << client_request[0..-10]
74
+
75
+ handshake.should_not be_finished
76
+ handshake.should_not be_valid
77
+ end
78
+
79
+ it "should disallow requests with invalid request method" do
80
+ handshake << client_request.gsub('GET', 'POST')
81
+
82
+ handshake.should be_finished
83
+ handshake.should_not be_valid
84
+ handshake.error.should eql(:get_request_required)
85
+ end
86
+ end
@@ -0,0 +1,72 @@
1
+ def client_handshake_75(args = {})
2
+ <<-EOF
3
+ GET #{args[:path] || "/demo"}#{"?#{args[:query]}" if args[:query]} HTTP/1.1\r
4
+ Upgrade: WebSocket\r
5
+ Connection: Upgrade\r
6
+ Host: #{args[:host] || "example.com"}#{":#{args[:port]}" if args[:port]}\r
7
+ Origin: http://example.com\r
8
+ \r
9
+ EOF
10
+ end
11
+
12
+ def server_handshake_75(args = {})
13
+ <<-EOF
14
+ HTTP/1.1 101 Web Socket Protocol Handshake\r
15
+ Upgrade: WebSocket\r
16
+ Connection: Upgrade\r
17
+ WebSocket-Origin: http://example.com\r
18
+ WebSocket-Location: ws#{args[:secure] ? "s" : ""}://#{args[:host] || "example.com"}#{":#{args[:port]}" if args[:port]}#{args[:path] || "/demo"}\r
19
+ \r
20
+ EOF
21
+ end
22
+
23
+ def client_handshake_76(args = {})
24
+ request = <<-EOF
25
+ GET #{args[:path] || "/demo"}#{"?#{args[:query]}" if args[:query]} HTTP/1.1\r
26
+ Upgrade: WebSocket\r
27
+ Connection: Upgrade\r
28
+ Host: #{args[:host] || "example.com"}#{":#{args[:port]}" if args[:port]}\r
29
+ Origin: http://example.com\r
30
+ Sec-WebSocket-Key1: #{args[:key1] || "4 @1 46546xW%0l 1 5"}\r
31
+ Sec-WebSocket-Key2: #{args[:key2] || "12998 5 Y3 1 .P00"}\r
32
+ \r
33
+ #{args[:key3] || "^n:ds[4U"}
34
+ EOF
35
+ request[0..-2]
36
+ end
37
+
38
+ def server_handshake_76(args = {})
39
+ request = <<-EOF
40
+ HTTP/1.1 101 WebSocket Protocol Handshake\r
41
+ Upgrade: WebSocket\r
42
+ Connection: Upgrade\r
43
+ Sec-WebSocket-Origin: http://example.com\r
44
+ Sec-WebSocket-Location: ws#{args[:secure] ? "s" : ""}://#{args[:host] || "example.com"}#{":#{args[:port]}" if args[:port]}#{args[:path] || "/demo"}\r
45
+ \r
46
+ #{args[:challenge] || "8jKS'y:G*Co,Wxa-"}
47
+ EOF
48
+ request[0..-2]
49
+ end
50
+
51
+ def client_handshake_04(args = {})
52
+ <<-EOF
53
+ GET #{args[:path] || "/demo"}#{"?#{args[:query]}" if args[:query]} HTTP/1.1\r
54
+ Upgrade: websocket\r
55
+ Connection: Upgrade\r
56
+ Host: #{args[:host] || "example.com"}#{":#{args[:port]}" if args[:port]}\r
57
+ Sec-WebSocket-Origin: http://example.com\r
58
+ Sec-WebSocket-Version: #{args[:version] || "4"}\r
59
+ Sec-WebSocket-Key: #{args[:key] || "dGhlIHNhbXBsZSBub25jZQ=="}\r
60
+ \r
61
+ EOF
62
+ end
63
+
64
+ def server_handshake_04(args = {})
65
+ <<-EOF
66
+ HTTP/1.1 101 Switching Protocols\r
67
+ Upgrade: websocket\r
68
+ Connection: Upgrade\r
69
+ Sec-WebSocket-Accept: #{args[:accept] || "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}\r
70
+ \r
71
+ EOF
72
+ end
@@ -0,0 +1,30 @@
1
+ shared_examples_for 'valid_incoming_frame' do
2
+ let(:decoded_text_array) { decoded_text == "" ? [""] : Array(decoded_text) } # Bug in Ruby 1.8 -> Array("") => [] instead of [""]
3
+ let(:frame_type_array) { Array(frame_type) }
4
+
5
+ its(:class) { should eql(WebSocket::Frame::Incoming) }
6
+ its(:data) { should eql(encoded_text || "") }
7
+ its(:version) { should eql(version) }
8
+ its(:type) { should be_nil }
9
+ its(:decoded?) { should be_false }
10
+ its(:to_s) { should eql(encoded_text || "") }
11
+
12
+ it "should have specified number of returned frames" do
13
+ decoded_text_array.each_with_index do |da, index|
14
+ n = subject.next
15
+ n.should_not be_nil, "Should return frame for #{da}, #{frame_type_array[index]}"
16
+ n.class.should eql(WebSocket::Frame::Incoming), "Should be WebSocket::Frame::Incoming, #{n} received instead"
17
+ end
18
+ subject.next.should be_nil
19
+ subject.error.should eql(error)
20
+ end
21
+
22
+ it "should return valid decoded frame for each specified decoded texts" do
23
+ decoded_text_array.each_with_index do |da, index|
24
+ f = subject.next
25
+ f.decoded?.should be_true
26
+ f.type.should eql(frame_type_array[index])
27
+ f.to_s.should eql(da)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ shared_examples_for 'valid_outgoing_frame' do
2
+ its(:class) { should eql(WebSocket::Frame::Outgoing) }
3
+ its(:version) { should eql(version) }
4
+ its(:type) { should eql(frame_type) }
5
+ its(:data) { should eql(decoded_text) }
6
+ its(:to_s) { should eql(encoded_text) }
7
+
8
+ context "after parsing" do
9
+ before { subject.to_s }
10
+ its(:error) { should eql(error) }
11
+ its(:require_sending?) { should eql(require_sending) }
12
+ end
13
+
14
+ end
data/websocket.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "websocket/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "websocket"
7
+ s.version = WebSocket::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Bernard Potocki"]
10
+ s.email = ["bernard.potocki@imanel.org"]
11
+ s.homepage = "http://github.com/imanel/websocket-ruby"
12
+ s.summary = %q{Universal Ruby library to handle WebSocket protocol}
13
+ s.description = %q{Universal Ruby library to handle WebSocket protocol}
14
+
15
+ s.add_development_dependency 'rspec', '~> 2.11'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end