websocket 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ autobahn
3
+ pkg/*.gem
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ script: "bundle exec rake spec"
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
7
+ - jruby-18mode
8
+ - jruby-19mode
9
+ - rbx-18mode
10
+ - rbx-19mode
11
+ - ree
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 / 2012-11-18
4
+
5
+ - initial release
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # WebSocket Ruby
2
+
3
+ - Travis CI build: [![](http://travis-ci.org/imanel/websocket-ruby.png)](http://travis-ci.org/imanel/websocket-ruby)
4
+ - Autobahn tests: [server](http://imanel.github.com/websocket-ruby/autobahn/server/), client
5
+
6
+ Universal Ruby library to handle WebSocket protocol. It foucses on providing abstraction layer over [WebSocket API](http://dev.w3.org/html5/websockets/) instead of providing server or client functionality.
7
+
8
+ Currently WebSocket Ruby supports all existing drafts of WebSocket, which include:
9
+
10
+ - [hixie-75](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75)
11
+ - [hixie-76](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76)
12
+ - [all hybi drafts (00-17)](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17)
13
+ - [RFC 6455](http://datatracker.ietf.org/doc/rfc6455/)
14
+
15
+ ## Installation
16
+
17
+ WebSocket Ruby has no external dependencies, so it can be installed from source or directly from rubygems:
18
+
19
+ ```
20
+ gem install "websocket"
21
+ ```
22
+
23
+ or via Gemfile:
24
+
25
+ ```
26
+ gem "websocket"
27
+ ```
28
+
29
+ ## Server handshake
30
+
31
+ ``` ruby
32
+ @handshake = WebSocket::Handshake::Server.new
33
+
34
+ # Parse client request
35
+ @handshake << <<EOF
36
+ GET /demo HTTP/1.1
37
+ Upgrade: websocket
38
+ Connection: Upgrade
39
+ Host: example.com
40
+ Sec-WebSocket-Origin: http://example.com
41
+ Sec-WebSocket-Version: 17
42
+ Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
43
+
44
+ EOF
45
+
46
+ # All data received?
47
+ @handshake.finished?
48
+
49
+ # No parsing errors?
50
+ @handshake.valid?
51
+
52
+ # Create response
53
+ @handshake.to_s # HTTP/1.1 101 Switching Protocols
54
+ # Upgrade: websocket
55
+ # Connection: Upgrade
56
+ # Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
57
+ ```
58
+
59
+ ## Client handshake
60
+
61
+ ``` ruby
62
+ @handshake = WebSocket::Handshake::Client.new(:url => 'ws://example.com')
63
+
64
+ # Create request
65
+ @handshake.to_s # GET /demo HTTP/1.1
66
+ # Upgrade: websocket
67
+ # Connection: Upgrade
68
+ # Host: example.com
69
+ # Sec-WebSocket-Origin: http://example.com
70
+ # Sec-WebSocket-Version: 17
71
+ # Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
72
+
73
+ # Parse server response
74
+ @handshake << <<EOF
75
+ HTTP/1.1 101 Switching Protocols
76
+ Upgrade: websocket
77
+ Connection: Upgrade
78
+ Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
79
+
80
+ EOF
81
+
82
+ # All data received?
83
+ @handshake.finished?
84
+
85
+ # No parsing errors?
86
+ @handshake.valid?
87
+ ```
88
+
89
+ ## Parsing and constructing frames
90
+
91
+ ``` ruby
92
+ # Prepare frame for sending
93
+ frame = WebSocket::Frame::Outgoing::Server.new(:version => @handshake.version, :data => "Hello", :type => :text)
94
+ frame.to_s # "\x81\x05\x48\x65\x6c\x6c\x6f"
95
+
96
+ # Parse incoming frames
97
+ frame = WebSocket::Frame::Incoming::Server.new(:version => @handshake.version)
98
+ frame << "\x81\x05\x48\x65\x6c\x6c\x6f\x81\x06\x77\x6f\x72\x6c\x64\x21"
99
+ frame.next # "Hello"
100
+ frame.next # "world!""
101
+ ```
102
+
103
+ ## Examples
104
+
105
+ For examples on how to use WebSocket Ruby see examples directory in the repository. Provided examples are fully working servers - they are passing full autobahn compatibility suit.
106
+
107
+ ## License
108
+
109
+ (The MIT License)
110
+
111
+ Copyright © 2012 Bernard Potocki
112
+
113
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
114
+
115
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
116
+
117
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.rspec_opts = ["-c", "-f progress"]
8
+ t.pattern = 'spec/**/*_spec.rb'
9
+ end
10
+
11
+ task :default => :spec
12
+
13
+ namespace :autobahn do
14
+ desc "Run autobahn tests for client"
15
+ task :client do
16
+ system('wstest --mode=fuzzingserver --spec=autobahn-client.json')
17
+ end
18
+
19
+ desc "Run autobahn tests for server"
20
+ task :server do
21
+ system('wstest --mode=fuzzingclient --spec=autobahn-server.json')
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ {
2
+ "url": "ws://127.0.0.1:9001",
3
+
4
+ "options": {"failByDrop": false},
5
+ "outdir": "./autobahn/client",
6
+ "webport": 8080,
7
+
8
+ "cases": ["1.1.1"],
9
+ "exclude-cases": [],
10
+ "exclude-agent-cases": {}
11
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "options": {"failByDrop": false},
3
+ "outdir": "./autobahn/server",
4
+
5
+ "servers": [{"agent": "WebSocket-Ruby v1.0.0", "url": "ws://localhost:9001", "options": {"version": 18}}],
6
+
7
+ "cases": ["*"],
8
+ "exclude-cases": [],
9
+ "exclude-agent-cases": {}
10
+ }
data/examples/base.rb ADDED
@@ -0,0 +1,162 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require "#{File.expand_path(File.dirname(__FILE__))}/../lib/websocket"
4
+
5
+ module WebSocket
6
+ module EventMachine
7
+ class Base < ::EventMachine::Connection
8
+
9
+ ###########
10
+ ### API ###
11
+ ###########
12
+
13
+ def onopen(&blk); @onopen = blk; end # Called when connection is opened
14
+ def onclose(&blk); @onclose = blk; end # Called when connection is closed
15
+ def onerror(&blk); @onerror = blk; end # Called when error occurs
16
+ def onmessage(&blk); @onmessage = blk; end # Called when message is received from server
17
+ def onping(&blk); @onping = blk; end # Called when ping message is received from server
18
+ def onpong(&blk); @onpong = blk; end # Called when pond message is received from server
19
+
20
+ # Send data to client
21
+ # @param data [String] Data to send
22
+ # @param args [Hash] Arguments for send
23
+ # @option args [String] :type Type of frame to send - available types are "text", "binary", "ping", "pong" and "close"
24
+ # @return [Boolean] true if data was send, otherwise call on_error if needed
25
+ def send(data, args = {})
26
+ type = args[:type] || :text
27
+ unless type == :plain
28
+ frame = outgoing_frame.new(:version => @handshake.version, :data => data, :type => type)
29
+ if !frame.supported?
30
+ trigger_onerror("Frame type '#{type}' is not supported in protocol version #{@handshake.version}")
31
+ return false
32
+ elsif !frame.require_sending?
33
+ return false
34
+ end
35
+ data = frame.to_s
36
+ end
37
+ # debug "Sending raw: ", data
38
+ send_data(data)
39
+ true
40
+ end
41
+
42
+ # Close connection
43
+ # @return [Boolean] true if connection is closed immediately, false if waiting for server to close connection
44
+ def close
45
+ if @state == :open
46
+ @state = :closing
47
+ return false if send('', :type => :close)
48
+ else
49
+ send('', :type => :close) if @state == :closing
50
+ @state = :closed
51
+ end
52
+ close_connection_after_writing
53
+ true
54
+ end
55
+
56
+ # Send ping message to client
57
+ # @return [Boolean] false if protocol version is not supporting ping requests
58
+ def ping(data = '')
59
+ send(data, :type => :ping)
60
+ end
61
+
62
+ # Send pong message to client
63
+ # @return [Boolean] false if protocol version is not supporting pong requests
64
+ def pong(data = '')
65
+ send(data, :type => :pong)
66
+ end
67
+
68
+ ############################
69
+ ### EventMachine methods ###
70
+ ############################
71
+
72
+ def receive_data(data)
73
+ # debug "Received raw: ", data
74
+ case @state
75
+ when :connecting then handle_connecting(data)
76
+ when :open then handle_open(data)
77
+ when :closing then handle_closing(data)
78
+ end
79
+ end
80
+
81
+ def unbind
82
+ unless @state == :closed
83
+ @state = :closed
84
+ close
85
+ trigger_onclose('')
86
+ end
87
+ end
88
+
89
+ #######################
90
+ ### Private methods ###
91
+ #######################
92
+
93
+ private
94
+
95
+ ['onopen'].each do |m|
96
+ define_method "trigger_#{m}" do
97
+ callback = instance_variable_get("@#{m}")
98
+ callback.call if callback
99
+ end
100
+ end
101
+
102
+ ['onerror', 'onping', 'onpong', 'onclose'].each do |m|
103
+ define_method "trigger_#{m}" do |data|
104
+ callback = instance_variable_get("@#{m}")
105
+ callback.call(data) if callback
106
+ end
107
+ end
108
+
109
+ def trigger_onmessage(data, type)
110
+ @onmessage.call(data, type) if @onmessage
111
+ end
112
+
113
+ def handle_connecting(data)
114
+ @handshake << data
115
+ return unless @handshake.finished?
116
+ if @handshake.valid?
117
+ send(@handshake.to_s, :type => :plain) if @handshake.should_respond?
118
+ @frame = incoming_frame.new(:version => @handshake.version)
119
+ @state = :open
120
+ trigger_onopen
121
+ handle_open(@handshake.leftovers) if @handshake.leftovers
122
+ else
123
+ trigger_onerror(@handshake.error)
124
+ close
125
+ end
126
+ end
127
+
128
+ def handle_open(data)
129
+ @frame << data
130
+ while frame = @frame.next
131
+ case frame.type
132
+ when :close
133
+ @state = :closing
134
+ close
135
+ trigger_onclose(frame.to_s)
136
+ when :ping
137
+ pong(frame.to_s)
138
+ trigger_onping(frame.to_s)
139
+ when :pong
140
+ trigger_onpong(frame.to_s)
141
+ when :text
142
+ trigger_onmessage(frame.to_s, :text)
143
+ when :binary
144
+ trigger_onmessage(frame.to_s, :binary)
145
+ end
146
+ end
147
+ unbind if @frame.error?
148
+ end
149
+
150
+ def handle_closing(data)
151
+ @state = :closed
152
+ close
153
+ trigger_onclose
154
+ end
155
+
156
+ def debug(description, data)
157
+ puts(description + data.bytes.to_a.collect{|b| '\x' + b.to_s(16).rjust(2, '0')}.join) unless @state == :connecting
158
+ end
159
+
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,70 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/base"
2
+ require 'uri'
3
+
4
+ # Example WebSocket Client (using EventMachine)
5
+ # @example Usage
6
+ # ws = WebSocket::EventMachine::Client.connect(:host => "0.0.0.0", :port => 8080)
7
+ # ws.onmessage { |msg| ws.send "Pong: #{msg}" }
8
+ # ws.send "data"
9
+ module WebSocket
10
+ module EventMachine
11
+ class Client < Base
12
+
13
+ # Connect to websocket server
14
+ # @param args [Hash] The request arguments
15
+ # @option args [String] :host The host IP/DNS name
16
+ # @option args [Integer] :port The port to connect too(default = 80)
17
+ # @option args [Integer] :version Version of protocol to use(default = 13)
18
+ def self.connect(args = {})
19
+ host = nil
20
+ port = nil
21
+ if args[:uri]
22
+ uri = URI.parse(args[:uri])
23
+ host = uri.host
24
+ port = uri.port
25
+ end
26
+ host = args[:host] if args[:host]
27
+ port = args[:port] if args[:port]
28
+ port ||= 80
29
+
30
+ ::EventMachine.connect host, port, self, args
31
+ end
32
+
33
+ # Initialize connection
34
+ # @param args [Hash] Arguments for connection
35
+ # @option args [String] :host The host IP/DNS name
36
+ # @option args [Integer] :port The port to connect too(default = 80)
37
+ # @option args [Integer] :version Version of protocol to use(default = 13)
38
+ def initialize(args)
39
+ @args = args
40
+ end
41
+
42
+ ############################
43
+ ### EventMachine methods ###
44
+ ############################
45
+
46
+ # Called after initialize of connection, but before connecting to server
47
+ def post_init
48
+ @state = :connecting
49
+ @handshake = WebSocket::Handshake::Client.new(@args)
50
+ end
51
+
52
+ # Called by EventMachine after connecting.
53
+ # Sends handshake to server
54
+ def connection_completed
55
+ send(@handshake.to_s, :type => :plain)
56
+ end
57
+
58
+ private
59
+
60
+ def incoming_frame
61
+ WebSocket::Frame::Incoming::Client
62
+ end
63
+
64
+ def outgoing_frame
65
+ WebSocket::Frame::Outgoing::Client
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,56 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/base"
2
+
3
+ # Example WebSocket Server (using EventMachine)
4
+ # @example Usage
5
+ # WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 8080) do |ws|
6
+ # ws.onopen { ws.send "Hello Client!"}
7
+ # ws.onmessage { |msg| ws.send "Pong: #{msg}" }
8
+ # ws.onclose { puts "WebSocket closed" }
9
+ # ws.onerror { |e| puts "Error: #{e}" }
10
+ # end
11
+ module WebSocket
12
+ module EventMachine
13
+ class Server < Base
14
+
15
+ # Start server
16
+ # @param options [Hash] The request arguments
17
+ # @option args [String] :host The host IP/DNS name
18
+ # @option args [Integer] :port The port to connect too(default = 80)
19
+ def self.start(options, &block)
20
+ ::EventMachine::start_server(options[:host], options[:port], self, options) do |c|
21
+ block.call(c)
22
+ end
23
+ end
24
+
25
+ # Initialize connection
26
+ # @param args [Hash] Arguments for server
27
+ # @option args [Boolean] :secure If true then server will run over SSL
28
+ # @option args [Hash] :tls_options Options for SSL if secure = true
29
+ def initialize(args)
30
+ @secure = args[:secure] || false
31
+ @tls_options = args[:tls_options] || {}
32
+ end
33
+
34
+ ############################
35
+ ### Eventmachine methods ###
36
+ ############################
37
+
38
+ def post_init
39
+ @state = :connecting
40
+ @handshake = WebSocket::Handshake::Server.new(:secure => @secure)
41
+ start_tls(@tls_options) if @secure
42
+ end
43
+
44
+ private
45
+
46
+ def incoming_frame
47
+ WebSocket::Frame::Incoming::Server
48
+ end
49
+
50
+ def outgoing_frame
51
+ WebSocket::Frame::Outgoing::Server
52
+ end
53
+
54
+ end
55
+ end
56
+ end