rev-websocket 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gemspec
data/README.rdoc ADDED
@@ -0,0 +1,76 @@
1
+ = Rev-WebSocket
2
+
3
+ Rev-WebSocket is a WebSocket server implementation based on Rev,
4
+ a high performance event-driven I/O library for Ruby.
5
+
6
+ This library conforms to WebSocket draft-75 and draft-76.
7
+
8
+ - http://github.com/frsyuki/rev-websocket
9
+
10
+
11
+ == Simple example
12
+
13
+ gem install rev-websocket
14
+
15
+
16
+ == Simple example
17
+
18
+ require 'rubygems'
19
+ require 'rev/websocket'
20
+
21
+ class MyConnection < Rev::WebSocket
22
+ def on_open
23
+ puts "WebSocket opened"
24
+ send_message("Hello, world!")
25
+ end
26
+
27
+ def on_message(data)
28
+ puts "WebSocket data received: '#{data}'"
29
+ send_message("echo: #{data}")
30
+ end
31
+
32
+ def on_close
33
+ puts "WebSocket closed"
34
+ end
35
+ end
36
+
37
+ host = '0.0.0.0'
38
+ port = '8081'
39
+
40
+ server = Rev::WebSocketServer.new(host, port, MyConnection)
41
+ server.attach(Rev::Loop.default)
42
+
43
+ Rev::Loop.run
44
+
45
+
46
+ == Learn more
47
+
48
+ - [Rev API reference] http://rev.rubyforge.org/rdoc/
49
+ - [JSON for object serialization] http://github.com/brianmario/yajl-ruby
50
+ - [MessagePack for object serialization] http://msgpack.org/
51
+ - [The WebSocket protocol draft-76] http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
52
+
53
+
54
+ == License
55
+
56
+ Copyright (c) 2010 FURUHASHI Sadayuki
57
+
58
+ Permission is hereby granted, free of charge, to any person obtaining
59
+ a copy of this software and associated documentation files (the
60
+ 'Software'), to deal in the Software without restriction, including
61
+ without limitation the rights to use, copy, modify, merge, publish,
62
+ distribute, sublicense, and/or sell copies of the Software, and to
63
+ permit persons to whom the Software is furnished to do so, subject to
64
+ the following conditions:
65
+
66
+ The above copyright notice and this permission notice shall be
67
+ included in all copies or substantial portions of the Software.
68
+
69
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
70
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
71
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
72
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
73
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
74
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
75
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
76
+
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "rev-websocket"
7
+ gemspec.summary = "WebSocket server based on Rev"
8
+ gemspec.description = "Rev-WebSocket is a WebSocket server implementation based on Rev, a high performance event-driven I/O library for Ruby. This library conforms to WebSocket draft-75 and draft-76."
9
+ gemspec.email = "frsyuki@users.sourceforge.jp"
10
+ gemspec.homepage = "http://github.com/frsyuki/rev-websocket"
11
+ gemspec.authors = ["FURUHASHI Sadayuki"]
12
+ gemspec.add_dependency("rev", ">= 0.3.2")
13
+ gemspec.add_dependency("thin", '>= 1.2.7')
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: gem install jeweler"
18
+ end
19
+
20
+ task :default => :build
21
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__)+'/rev/websocket'
@@ -0,0 +1,223 @@
1
+ require 'rev'
2
+ require File.dirname(__FILE__)+'/websocket/spec'
3
+ require 'thin_parser'
4
+
5
+ module Rev
6
+ class WebSocketServer < TCPServer
7
+ def initialize(host, port = nil, klass = WebSocket, *args, &block)
8
+ super
9
+ end
10
+ end
11
+
12
+ # WebSocket spec:
13
+ # http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
14
+
15
+ class WebSocket < TCPSocket
16
+ def on_open
17
+ end
18
+
19
+ def on_message(data)
20
+ end
21
+
22
+ def on_error(reason)
23
+ end
24
+
25
+ attr_reader :request
26
+
27
+ def send_message(data)
28
+ if HAVE_ENCODING
29
+ frame = FRAME_START + data.force_encoding('UTF-8') + FRAME_END
30
+ else
31
+ frame = FRAME_START + data + FRAME_END
32
+ end
33
+ write frame
34
+ end
35
+
36
+ if "".respond_to?(:force_encoding)
37
+ HAVE_ENCODING = true
38
+ FRAME_START = "\x00".force_encoding('UTF-8')
39
+ FRAME_END = "\xFF".force_encoding('UTF-8')
40
+ else
41
+ HAVE_ENCODING = false
42
+ FRAME_START = "\x00"
43
+ FRAME_END = "\xFF"
44
+ end
45
+
46
+ #HTTP11_PRASER = Mongrel::HttpParser
47
+ HTTP11_PRASER = Thin::HttpParser
48
+
49
+ # Thin::HttpParser tries to call request['rack.input'].write(body)
50
+ class DummyIO
51
+ KEY = 'rack.input'
52
+ def write(data) end
53
+ end
54
+
55
+ def initialize(socket)
56
+ super
57
+ @state = :process_handshake
58
+ @data = ::IO::Buffer.new
59
+ @http11 = HTTP11_PRASER.new
60
+ @http11_nbytes = 0
61
+ @request = {DummyIO::KEY => DummyIO.new}
62
+ end
63
+
64
+ def on_read(data)
65
+ @data << data
66
+ dispatch
67
+ end
68
+
69
+ protected
70
+
71
+ def dispatch
72
+ while __send__(@state)
73
+ end
74
+ end
75
+
76
+ def process_handshake
77
+ return false if @data.empty?
78
+
79
+ data = @data.to_str
80
+ begin
81
+ @http11_nbytes = @http11.execute(@request, data, @http11_nbytes)
82
+ rescue
83
+ on_error "invalid HTTP header, parsing fails"
84
+ @state = :invalid_state
85
+ close
86
+ end
87
+
88
+ return false unless @http11.finished?
89
+
90
+ @data.read(@http11_nbytes)
91
+ remove_instance_variable(:@http11)
92
+ remove_instance_variable(:@http11_nbytes)
93
+
94
+ @request.delete(DummyIO::KEY)
95
+
96
+ unless @request["REQUEST_METHOD"] == "GET"
97
+ raise RuntimeError, "Request method must be GET"
98
+ end
99
+
100
+ unless @request['HTTP_CONNECTION'] == 'Upgrade' and @request['HTTP_UPGRADE'] == 'WebSocket'
101
+ raise RequestError, "Connection and Upgrade headers required"
102
+ end
103
+
104
+ @state = :process_frame_header
105
+
106
+ version = @request['HTTP_SEC_WEBSOCKET_KEY1'] ? 76 : 75
107
+ begin
108
+ case version
109
+ when 75
110
+ extend Spec75
111
+ when 76
112
+ extend Spec76
113
+ end
114
+
115
+ if handshake
116
+ on_open
117
+ end
118
+
119
+ rescue
120
+ on_bad_request
121
+ end
122
+ end
123
+
124
+ def on_bad_request
125
+ write "HTTP/1.1 400 Bad request\r\n\r\n"
126
+ close
127
+ end
128
+
129
+ def process_frame_header
130
+ return false if @data.empty?
131
+
132
+ @frame_type = @data.read(1).to_i
133
+ if (@frame_type & 0x80) == 0x80
134
+ @binary_length = 0
135
+ @state = :process_binary_frame_header
136
+ else
137
+ @state = :process_text_frame
138
+ end
139
+
140
+ return true
141
+ end
142
+
143
+ def process_binary_frame_header
144
+ until @data.empty?
145
+
146
+ b = @data.read(1).to_i
147
+ b_v = b & 0x7f
148
+ @binary_length = (@binary_length<<7) | b_v
149
+
150
+ if (b & 0x80) == 0x80
151
+ if @binary_length == 0
152
+ # If the /frame type/ is 0xFF and the /length/ was 0
153
+ write "\xff\x00"
154
+ @state = :invalid_state
155
+ close
156
+ return false
157
+ end
158
+
159
+ @state = :process_binary_frame
160
+ return true
161
+ end
162
+
163
+ end
164
+ return false
165
+ end
166
+
167
+ def process_binary_frame
168
+ return false if @data.size < @binary_length
169
+
170
+ # Just discard the read bytes.
171
+ @data.read(@binary_length)
172
+
173
+ @state = :process_frame_header
174
+ return true
175
+ end
176
+
177
+ def process_text_frame
178
+ return false if @data.empty?
179
+
180
+ pos = @data.to_str.index("\xff")
181
+ if pos.nil?
182
+ return false
183
+ end
184
+
185
+ msg = @data.read(pos)
186
+ @data.read(1) # read 0xff byte
187
+
188
+ @state = :process_frame_header
189
+
190
+ if @frame_type != 0x00
191
+ # discard the data
192
+ return true
193
+ end
194
+
195
+ msg.force_encoding('UTF-8') if HAVE_ENCODING
196
+ on_message(msg)
197
+
198
+ return true
199
+ end
200
+
201
+ def ssl?
202
+ false
203
+ end
204
+
205
+ private
206
+
207
+ def invalid_state
208
+ raise RuntimeError, "invalid state"
209
+ end
210
+ end
211
+
212
+ class SSLWebSocket < WebSocket
213
+ def on_connect
214
+ extend SSL
215
+ ssl_server_start
216
+ end
217
+
218
+ def ssl?
219
+ true
220
+ end
221
+ end
222
+ end
223
+
@@ -0,0 +1,108 @@
1
+ require 'digest/md5'
2
+
3
+ module Rev
4
+ class WebSocket < TCPSocket
5
+
6
+ module Spec75
7
+ protected
8
+
9
+ def handshake
10
+ schema = (ssl? ? 'wss' : 'ws')
11
+ location = "#{schema}://#{@request['HTTP_HOST']}#{@request['REQUEST_URI']}"
12
+
13
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
14
+ upgrade << "Upgrade: WebSocket\r\n"
15
+ upgrade << "Connection: Upgrade\r\n"
16
+ upgrade << "WebSocket-Origin: #{@request['HTTP_ORIGIN']}\r\n"
17
+ upgrade << "WebSocket-Location: #{location}\r\n\r\n"
18
+
19
+ write upgrade
20
+
21
+ return true # opened
22
+ end
23
+ end
24
+
25
+ module Spec76
26
+ protected
27
+
28
+ def handshake
29
+ @state_save = @state
30
+ @state = :process_challenge_response
31
+
32
+ return false # not yet opened
33
+ end
34
+
35
+ def process_challenge_response
36
+ return false if @data.size < 10
37
+
38
+ crln = @data.read(2) # read \r\n bytes
39
+ if crln != "\r\n"
40
+ raise RuntimeError, "invalid request"
41
+ end
42
+
43
+ key_3 = @data.read(8)
44
+
45
+ @state = remove_instance_variable(:@state_save)
46
+
47
+ challenge_response = do_challenge_response(
48
+ @request['HTTP_SEC_WEBSOCKET_KEY1'],
49
+ @request['HTTP_SEC_WEBSOCKET_KEY2'],
50
+ key_3)
51
+
52
+ schema = (ssl? ? 'wss' : 'ws')
53
+ location = "#{schema}://#{@request['HTTP_HOST']}#{@request['REQUEST_URI']}"
54
+
55
+ upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
56
+ upgrade << "Upgrade: WebSocket\r\n"
57
+ upgrade << "Connection: Upgrade\r\n"
58
+ upgrade << "Sec-WebSocket-Location: #{location}\r\n"
59
+ upgrade << "Sec-WebSocket-Origin: #{@request['HTTP_ORIGIN']}\r\n"
60
+ if proto = @request['HTTP_SEC_WEBSOCKET_PROTOCOL']
61
+ # FIXME server is requed to confirm the subprotocol of the connection
62
+ upgrade << "Sec-WebSocket-Protocol: #{proto}\r\n"
63
+ end
64
+ upgrade << "\r\n"
65
+ upgrade << challenge_response
66
+
67
+ write upgrade
68
+
69
+ on_open
70
+
71
+ return true
72
+
73
+ rescue
74
+ on_bad_request
75
+ return false
76
+ end
77
+
78
+ private
79
+ def do_challenge_response(key_1, key_2, key_3)
80
+ key_number_1 = key_1.scan(/[0-9]/).join.to_i
81
+ key_number_2 = key_2.scan(/[0-9]/).join.to_i
82
+ spaces_1 = key_1.count(' ')
83
+ spaces_2 = key_2.count(' ')
84
+
85
+ if spaces_1 == 0 || spaces_2 == 0
86
+ raise RuntimeError, "invalid challenge key"
87
+ end
88
+
89
+ if key_number_1 % spaces_1 != 0 || key_number_2 % spaces_2 != 0
90
+ raise RuntimeError, "invalid challenge key"
91
+ end
92
+
93
+ part_1 = key_number_1 / spaces_1
94
+ part_2 = key_number_2 / spaces_2
95
+
96
+ challenge = [part_1, part_2].pack('NN') + key_3[0,8]
97
+ response = Digest::MD5.digest(challenge)
98
+
99
+ return response
100
+
101
+ rescue
102
+ on_bad_request
103
+ end
104
+ end
105
+
106
+ end
107
+ end
108
+
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rev-websocket
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - FURUHASHI Sadayuki
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-07-12 00:00:00 +09:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rev
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.3.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: thin
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.7
34
+ version:
35
+ description: Rev-WebSocket is a WebSocket server implementation based on Rev, a high performance event-driven I/O library for Ruby. This library conforms to WebSocket draft-75 and draft-76.
36
+ email: frsyuki@users.sourceforge.jp
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - .gitignore
45
+ - README.rdoc
46
+ - Rakefile
47
+ - VERSION
48
+ - lib/rev-websocket.rb
49
+ - lib/rev/websocket.rb
50
+ - lib/rev/websocket/spec.rb
51
+ has_rdoc: true
52
+ homepage: http://github.com/frsyuki/rev-websocket
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --charset=UTF-8
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ requirements: []
73
+
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.5
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: WebSocket server based on Rev
79
+ test_files: []
80
+