qws
Version:
An HTML5 Web Sockets Server Module
226 lines (205 loc) • 5.95 kB
JavaScript
// 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;