qws
Version:
An HTML5 Web Sockets Server Module
178 lines (156 loc) • 4.68 kB
text/coffeescript
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 : (, , options)->
# http can only be use one time
= os {
url : '/ws'
deflate : true
min_deflate_length : 32
close_timeout : 100
}, options
= false
unless true is msg =
# show 400 page only when url matched, otherwise return
throw new Error "URLNOTMATCHED" if 'url not match' is msg
.end 'HTTP/1.1 400 Bad Request\r\n\r\n' + msg + "\r\n"
return
return if true is .__QWS_USED
# set used flag on socket
.__QWS_USED = true
frame = null
= 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, , (err, f) =>
return 'error', err if err
# emit event
f
# reset frame
frame = null
socket.on 'error', (err)=>
'error', err
socket.on 'close', =>
'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 : .min_deflate_length
# pack
frame.pack , (err, bin) =>
if err
cb err if cb
return
# write
.write bin
cb null if cb
return
ping : (cb)->
'', 'ping', false, cb
return
pong : (cb)->
'', 'pong', false, cb
return
continue : (cb)->
'', 'continue', false, cb
return
writeRaw : (bin) ->
.write bin
end : (data, opcode, mask)->
if data?
data, opcode, mask, =>
else
return
close : ->
closed = false
'', 'close', false, =>
.on 'close', (err)=>
clearTimeout timer
closed = true
'closed', err
timer = setTimeout =>
return if closed
.end()
, .close_timeout
onFrame : (frame) ->
switch frame.opcode
when 'text' then 'message', frame.data.toString(), @
when 'binary' then 'message', frame.data, @
when 'ping' then 'ping'
when 'pong' then 'pong'
when 'close'
.end()
'close'
when 'continue' then 'continue'
# else 'control', frame.opcode
return
handShake : ->
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 .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 .deflate and req.headers['sec-websocket-extensions'] is 'x-webkit-deflate-frame'
= true
head += "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
head += "\r\n"
# send response
.write head
true
exports.Message = Message