# Protocol references:## * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75# * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76# * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17require'base64'require'digest/md5'require'digest/sha1'require'securerandom'require'set'require'stringio'require'uri'require'websocket/extensions'moduleWebSocketautoload:HTTP,File.expand_path('../http',__FILE__)classDriverroot=File.expand_path('../driver',__FILE__)begin# Load C native extensionrequire'websocket_mask'rescueLoadError# Fall back to pure-Ruby implementationrequire'websocket/mask'endifRUBY_PLATFORM=~/java/require'jruby'com.jcoglan.websocket.WebsocketMaskService.new.basicLoad(JRuby.runtime)endunlessMask.respond_to?(:mask)defMask.mask(payload,mask)@instance||=new@instance.mask(payload,mask)endendMAX_LENGTH=0x3ffffffSTATES=[:connecting,:open,:closing,:closed]BINARY='ASCII-8BIT'UNICODE='UTF-8'ConnectEvent=Struct.new(nil)OpenEvent=Struct.new(nil)MessageEvent=Struct.new(:data)PingEvent=Struct.new(:data)PongEvent=Struct.new(:data)CloseEvent=Struct.new(:code,:reason)ProtocolError=Class.new(StandardError)URIError=Class.new(ArgumentError)ConfigurationError=Class.new(ArgumentError)autoload:Client,root+'/client'autoload:Draft75,root+'/draft75'autoload:Draft76,root+'/draft76'autoload:EventEmitter,root+'/event_emitter'autoload:Headers,root+'/headers'autoload:Hybi,root+'/hybi'autoload:Proxy,root+'/proxy'autoload:Server,root+'/server'autoload:StreamReader,root+'/stream_reader'includeEventEmitterattr_reader:protocol,:ready_statedefinitialize(socket,options={})super()Driver.validate_options(options,[:max_length,:masking,:require_masking,:protocols])@socket=socket@reader=StreamReader.new@options=options@max_length=options[:max_length]||MAX_LENGTH@headers=Headers.new@queue=[]@ready_state=0enddefstatereturnnilunless@ready_state>=0STATES[@ready_state]enddefadd_extension(extension)falseenddefset_header(name,value)returnfalseunless@ready_state<=0@headers[name]=valuetrueenddefstartreturnfalseunless@ready_state==0unlessDriver.websocket?(@socket.env)returnfail_handshake(ProtocolError.new('Not a WebSocket request'))endbeginresponse=handshake_responserescue=>errorreturnfail_handshake(error)end@socket.write(response)openunless@stage==-1trueenddeftext(message)message=message.encode(UNICODE)unlessmessage.encoding.name==UNICODEframe(message,:text)enddefbinary(message)falseenddefping(*args)falseenddefpong(*args)falseenddefclose(reason=nil,code=nil)returnfalseunless@ready_state==1@ready_state=3emit(:close,CloseEvent.new(nil,nil))trueendprivatedeffail_handshake(error)headers=Headers.newheaders['Content-Type']='text/plain'headers['Content-Length']=error.message.bytesizeheaders=['HTTP/1.1 400 Bad Request',headers.to_s,error.message]@socket.write(headers.join("\r\n"))fail(:protocol_error,error.message)falseenddeffail(type,message)@ready_state=2emit(:error,ProtocolError.new(message))closeenddefopen@ready_state=1@queue.each{|message|frame(*message)}@queue=[]emit(:open,OpenEvent.new)enddefqueue(message)@queue<<messagetrueenddefself.client(socket,options={})Client.new(socket,options.merge(:masking=>true))enddefself.server(socket,options={})Server.new(socket,options.merge(:require_masking=>true))enddefself.rack(socket,options={})env=socket.envversion=env['HTTP_SEC_WEBSOCKET_VERSION']key=env['HTTP_SEC_WEBSOCKET_KEY']key1=env['HTTP_SEC_WEBSOCKET_KEY1']key2=env['HTTP_SEC_WEBSOCKET_KEY2']ifversionorkeyHybi.new(socket,options.merge(:require_masking=>true))elsifkey1orkey2Draft76.new(socket,options)elseDraft75.new(socket,options)endenddefself.encode(string,encoding=nil)casestringwhenArraythenstring=string.pack('C*')encoding||=BINARYwhenStringthenencoding||=UNICODEendunlessstring.encoding.name==encodingstring=string.dupifstring.frozen?string.force_encoding(encoding)endstring.valid_encoding??string:nilenddefself.validate_options(options,valid_keys)options.keys.eachdo|key|unlessvalid_keys.include?(key)raiseConfigurationError,"Unrecognized option: #{key.inspect}"endendenddefself.websocket?(env)connection=env['HTTP_CONNECTION']||''upgrade=env['HTTP_UPGRADE']||''env['REQUEST_METHOD']=='GET'andconnection.downcase.split(/ *, */).include?('upgrade')andupgrade.downcase=='websocket'endendend