Permalink
Browse files

first commit

  • Loading branch information...
0 parents commit 07684526b6197cbd187a9938ca264e17469b029e @frsyuki committed Jul 12, 2010
Showing with 423 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +68 −0 README.rdoc
  3. +21 −0 Rakefile
  4. +1 −0 VERSION
  5. +1 −0 lib/rev-websocket.rb
  6. +223 −0 lib/rev/websocket.rb
  7. +108 −0 lib/rev/websocket/spec.rb
@@ -0,0 +1 @@
+*.gemspec
@@ -0,0 +1,68 @@
+= Rev-WebSocket
+
+Rev-WebSocket is WebSocket server implementation based on Rev.
+- http://github.com/frsyuki/rev-websocket
+
+This library conforms to WebSocket draft-75 and draft-76.
+
+== Simple example
+
+ require 'rubygems'
+ require 'rev/websocket'
+
+ class MyConnection < Rev::WebSocket
+ def on_open
+ puts "WebSocket opened"
+ send_message("Hello, world!")
+ end
+
+ def on_message(data)
+ puts "WebSocket data received: '#{data}'"
+ send_message("echo: #{data}")
+ end
+
+ def on_close
+ puts "WebSocket closed"
+ end
+ end
+
+ host = '0.0.0.0'
+ port = '8081'
+
+ server = Rev::WebSocketServer.new(host, port, MyConnection)
+ server.attach(Rev::Loop.default)
+
+ Rev::Loop.run
+
+
+== Learn more
+
+- [Rev API reference] http://rev.rubyforge.org/rdoc/
+- [JSON for object serialization] http://github.com/brianmario/yajl-ruby
+- [MessagePack for object serialization] http://msgpack.org/
+- [The WebSocket protocol draft-76] http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
+
+
+== License
+
+Copyright (c) 2010 FURUHASHI Sadayuki
+
+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:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+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.
+
@@ -0,0 +1,21 @@
+require 'rake'
+
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gemspec|
+ gemspec.name = "rev-websocket"
+ gemspec.summary = "WebSocket server based on Rev"
+ gemspec.description = gemspec.summary
+ gemspec.email = "frsyuki@users.sourceforge.jp"
+ gemspec.homepage = "http://github.com/frsyuki/rev-websocket"
+ gemspec.authors = ["FURUHASHI Sadayuki"]
+ gemspec.add_dependency("rev", ">= 0.3.2")
+ gemspec.add_dependency("thin", '>= 1.2.7')
+ end
+ Jeweler::GemcutterTasks.new
+rescue LoadError
+ puts "Jeweler not available. Install it with: gem install jeweler"
+end
+
+task :default => :build
+
@@ -0,0 +1 @@
+0.1.0
@@ -0,0 +1 @@
+require File.dirname(__FILE__)+'/rev/websocket'
@@ -0,0 +1,223 @@
+require 'rev'
+require File.dirname(__FILE__)+'/websocket/spec'
+require 'thin_parser'
+
+module Rev
+ class WebSocketServer < TCPServer
+ def initialize(host, port = nil, klass = WebSocket, *args, &block)
+ super
+ end
+ end
+
+ # WebSocket spec:
+ # http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
+
+ class WebSocket < TCPSocket
+ def on_open
+ end
+
+ def on_message(data)
+ end
+
+ def on_error(reason)
+ end
+
+ attr_reader :request
+
+ def send_message(data)
+ if HAVE_ENCODING
+ frame = FRAME_START + data.force_encoding('UTF-8') + FRAME_END
+ else
+ frame = FRAME_START + data + FRAME_END
+ end
+ write frame
+ end
+
+ if "".respond_to?(:force_encoding)
+ HAVE_ENCODING = true
+ FRAME_START = "\x00".force_encoding('UTF-8')
+ FRAME_END = "\xFF".force_encoding('UTF-8')
+ else
+ HAVE_ENCODING = false
+ FRAME_START = "\x00"
+ FRAME_END = "\xFF"
+ end
+
+ #HTTP11_PRASER = Mongrel::HttpParser
+ HTTP11_PRASER = Thin::HttpParser
+
+ # Thin::HttpParser tries to call request['rack.input'].write(body)
+ class DummyIO
+ KEY = 'rack.input'
+ def write(data) end
+ end
+
+ def initialize(socket)
+ super
+ @state = :process_handshake
+ @data = ::IO::Buffer.new
+ @http11 = HTTP11_PRASER.new
+ @http11_nbytes = 0
+ @request = {DummyIO::KEY => DummyIO.new}
+ end
+
+ def on_read(data)
+ @data << data
+ dispatch
+ end
+
+ protected
+
+ def dispatch
+ while __send__(@state)
+ end
+ end
+
+ def process_handshake
+ return false if @data.empty?
+
+ data = @data.to_str
+ begin
+ @http11_nbytes = @http11.execute(@request, data, @http11_nbytes)
+ rescue
+ on_error "invalid HTTP header, parsing fails"
+ @state = :invalid_state
+ close
+ end
+
+ return false unless @http11.finished?
+
+ @data.read(@http11_nbytes)
+ remove_instance_variable(:@http11)
+ remove_instance_variable(:@http11_nbytes)
+
+ @request.delete(DummyIO::KEY)
+
+ unless @request["REQUEST_METHOD"] == "GET"
+ raise RuntimeError, "Request method must be GET"
+ end
+
+ unless @request['HTTP_CONNECTION'] == 'Upgrade' and @request['HTTP_UPGRADE'] == 'WebSocket'
+ raise RequestError, "Connection and Upgrade headers required"
+ end
+
+ @state = :process_frame_header
+
+ version = @request['HTTP_SEC_WEBSOCKET_KEY1'] ? 76 : 75
+ begin
+ case version
+ when 75
+ extend Spec75
+ when 76
+ extend Spec76
+ end
+
+ if handshake
+ on_open
+ end
+
+ rescue
+ on_bad_request
+ end
+ end
+
+ def on_bad_request
+ write "HTTP/1.1 400 Bad request\r\n\r\n"
+ close
+ end
+
+ def process_frame_header
+ return false if @data.empty?
+
+ @frame_type = @data.read(1).to_i
+ if (@frame_type & 0x80) == 0x80
+ @binary_length = 0
+ @state = :process_binary_frame_header
+ else
+ @state = :process_text_frame
+ end
+
+ return true
+ end
+
+ def process_binary_frame_header
+ until @data.empty?
+
+ b = @data.read(1).to_i
+ b_v = b & 0x7f
+ @binary_length = (@binary_length<<7) | b_v
+
+ if (b & 0x80) == 0x80
+ if @binary_length == 0
+ # If the /frame type/ is 0xFF and the /length/ was 0
+ write "\xff\x00"
+ @state = :invalid_state
+ close
+ return false
+ end
+
+ @state = :process_binary_frame
+ return true
+ end
+
+ end
+ return false
+ end
+
+ def process_binary_frame
+ return false if @data.size < @binary_length
+
+ # Just discard the read bytes.
+ @data.read(@binary_length)
+
+ @state = :process_frame_header
+ return true
+ end
+
+ def process_text_frame
+ return false if @data.empty?
+
+ pos = @data.to_str.index("\xff")
+ if pos.nil?
+ return false
+ end
+
+ msg = @data.read(pos)
+ @data.read(1) # read 0xff byte
+
+ @state = :process_frame_header
+
+ if @frame_type != 0x00
+ # discard the data
+ return true
+ end
+
+ msg.force_encoding('UTF-8') if HAVE_ENCODING
+ on_message(msg)
+
+ return true
+ end
+
+ def ssl?
+ false
+ end
+
+ private
+
+ def invalid_state
+ raise RuntimeError, "invalid state"
+ end
+ end
+
+ class SSLWebSocket < WebSocket
+ def on_connect
+ extend SSL
+ ssl_server_start
+ end
+
+ def ssl?
+ true
+ end
+ end
+end
+
Oops, something went wrong.

0 comments on commit 0768452

Please sign in to comment.