UNPKG

qws

Version:

An HTML5 Web Sockets Server Module

178 lines (156 loc) 4.68 kB
os = require 'options-stream' crypto = require 'crypto' zlib = require 'zlib' {parse: urlParse} = require 'url' {EventEmitter} = require 'events' {unpack, inflate, Frame} = require './frame' class Message extends EventEmitter constructor : (@req, @socket, options)-> # http can only be use one time @options = os { url : '/ws' deflate : true min_deflate_length : 32 close_timeout : 100 }, options @deflated = false unless true is msg = @handShake() # show 400 page only when url matched, otherwise return throw new Error "URLNOTMATCHED" if 'url not match' is msg @socket.end 'HTTP/1.1 400 Bad Request\r\n\r\n' + msg + "\r\n" return return if true is @socket.__QWS_USED # set used flag on socket @socket.__QWS_USED = true frame = null @inflate = zlib.createInflateRaw chunkSize : 128 * 1024 socket.on 'data', (chunk)=> # first chunk # if frame is null while chunk and chunk.length [frame, chunk] = unpack chunk, frame # 2+ chunks # else # [frame, buf] = unpack chunk, frame # if done if frame.done # decompress inflate frame, @inflate, (err, f) => return @emit 'error', err if err # emit event @onFrame f # reset frame frame = null socket.on 'error', (err)=> @emit 'error', err socket.on 'close', => @emit 'close' write : (data, opcode, mask, cb) -> # mask if typeof mask is 'function' cb = mask mask = null # opcode switch typeof opcode when 'function' cb = opcode opcode = null when 'boolean' mask = opcode opcode = null # text frame as defalut opcode ?= 'text' # no mask as defalut mask ?= false # data is frame # if data instanceof Frame # frame = data # else frame = new Frame data : data opcode : opcode fin : true mask : mask minDeflateLength : @options.min_deflate_length # pack frame.pack @deflated, (err, bin) => if err cb err if cb return # write @socket.write bin cb null if cb return ping : (cb)-> @write '', 'ping', false, cb return pong : (cb)-> @write '', 'pong', false, cb return continue : (cb)-> @write '', 'continue', false, cb return writeRaw : (bin) -> @socket.write bin end : (data, opcode, mask)-> if data? @write data, opcode, mask, => @close() else @close() return close : -> closed = false @write '', 'close', false, => @socket.on 'close', (err)=> clearTimeout timer closed = true @emit 'closed', err timer = setTimeout => return if closed @socket.end() , @options.close_timeout onFrame : (frame) -> switch frame.opcode when 'text' then @emit 'message', frame.data.toString(), @ when 'binary' then @emit 'message', frame.data, @ when 'ping' then @emit 'ping' when 'pong' then @emit 'pong' when 'close' @socket.end() @emit 'close' when 'continue' then @emit 'continue' # else @emit 'control', frame.opcode return handShake : -> req = @req {path} = uinfo = urlParse req.url return 'protocol not match' if uinfo.protocol and uinfo.protocol isnt 'ws:' # request check return 'url not match' unless path is @options.url return 'upgrade not match' unless 'websocket' is req.headers.upgrade return 'version not match' unless '13' is req.headers['sec-websocket-version'] return 'key missed' unless req.headers['sec-websocket-key'] # sign key = req.headers['sec-websocket-key'] sha1 = crypto.createHash 'sha1' sha1.update key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' sign = sha1.digest 'base64' # response header head = """ HTTP/1.1 101 Switching Protocols\r Upgrade: websocket\r Connection: Upgrade\r Sec-WebSocket-Accept: #{sign}\r\n """ # handOrigin # self.client.send("Sec-WebSocket-Origin: " + headers["Origin"] + "\r\n") # use deflate if @options.deflate and req.headers['sec-websocket-extensions'] is 'x-webkit-deflate-frame' @deflated = true head += "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n" head += "\r\n" # send response @socket.write head true exports.Message = Message