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