UNPKG

qws

Version:

An HTML5 Web Sockets Server Module

226 lines (205 loc) 5.95 kB
// Generated by CoffeeScript 1.6.3 var EventEmitter, Frame, Message, crypto, inflate, os, unpack, urlParse, zlib, _ref, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; os = require('options-stream'); crypto = require('crypto'); zlib = require('zlib'); urlParse = require('url').parse; EventEmitter = require('events').EventEmitter; _ref = require('./frame'), unpack = _ref.unpack, inflate = _ref.inflate, Frame = _ref.Frame; Message = (function(_super) { __extends(Message, _super); function Message(req, socket, options) { var frame, msg, _this = this; this.req = req; this.socket = socket; this.options = os({ url: '/ws', deflate: true, min_deflate_length: 32, close_timeout: 100 }, options); this.deflated = false; if (true !== (msg = this.handShake())) { if ('url not match' === msg) { throw new Error("URLNOTMATCHED"); } this.socket.end('HTTP/1.1 400 Bad Request\r\n\r\n' + msg + "\r\n"); return; } if (true === this.socket.__QWS_USED) { return; } this.socket.__QWS_USED = true; frame = null; this.inflate = zlib.createInflateRaw({ chunkSize: 128 * 1024 }); socket.on('data', function(chunk) { var _ref1, _results; _results = []; while (chunk && chunk.length) { _ref1 = unpack(chunk, frame), frame = _ref1[0], chunk = _ref1[1]; if (frame.done) { inflate(frame, _this.inflate, function(err, f) { if (err) { return _this.emit('error', err); } return _this.onFrame(f); }); _results.push(frame = null); } else { _results.push(void 0); } } return _results; }); socket.on('error', function(err) { return _this.emit('error', err); }); socket.on('close', function() { return _this.emit('close'); }); } Message.prototype.write = function(data, opcode, mask, cb) { var frame, _this = this; if (typeof mask === 'function') { cb = mask; mask = null; } switch (typeof opcode) { case 'function': cb = opcode; opcode = null; break; case 'boolean': mask = opcode; opcode = null; } if (opcode == null) { opcode = 'text'; } if (mask == null) { mask = false; } frame = new Frame({ data: data, opcode: opcode, fin: true, mask: mask, minDeflateLength: this.options.min_deflate_length }); frame.pack(this.deflated, function(err, bin) { if (err) { if (cb) { cb(err); } return; } _this.socket.write(bin); if (cb) { return cb(null); } }); }; Message.prototype.ping = function(cb) { this.write('', 'ping', false, cb); }; Message.prototype.pong = function(cb) { this.write('', 'pong', false, cb); }; Message.prototype["continue"] = function(cb) { this.write('', 'continue', false, cb); }; Message.prototype.writeRaw = function(bin) { return this.socket.write(bin); }; Message.prototype.end = function(data, opcode, mask) { var _this = this; if (data != null) { this.write(data, opcode, mask, function() { return _this.close(); }); } else { this.close(); } }; Message.prototype.close = function() { var closed, _this = this; closed = false; return this.write('', 'close', false, function() { var timer; _this.socket.on('close', function(err) { clearTimeout(timer); closed = true; return _this.emit('closed', err); }); return timer = setTimeout(function() { if (closed) { return; } return _this.socket.end(); }, _this.options.close_timeout); }); }; Message.prototype.onFrame = function(frame) { switch (frame.opcode) { case 'text': this.emit('message', frame.data.toString(), this); break; case 'binary': this.emit('message', frame.data, this); break; case 'ping': this.emit('ping'); break; case 'pong': this.emit('pong'); break; case 'close': this.socket.end(); this.emit('close'); break; case 'continue': this.emit('continue'); } }; Message.prototype.handShake = function() { var head, key, path, req, sha1, sign, uinfo; req = this.req; path = (uinfo = urlParse(req.url)).path; if (uinfo.protocol && uinfo.protocol !== 'ws:') { return 'protocol not match'; } if (path !== this.options.url) { return 'url not match'; } if ('websocket' !== req.headers.upgrade) { return 'upgrade not match'; } if ('13' !== req.headers['sec-websocket-version']) { return 'version not match'; } if (!req.headers['sec-websocket-key']) { return 'key missed'; } key = req.headers['sec-websocket-key']; sha1 = crypto.createHash('sha1'); sha1.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); sign = sha1.digest('base64'); head = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + sign + "\r\n"; if (this.options.deflate && req.headers['sec-websocket-extensions'] === 'x-webkit-deflate-frame') { this.deflated = true; head += "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"; } head += "\r\n"; this.socket.write(head); return true; }; return Message; })(EventEmitter); exports.Message = Message;