Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ to a single process.
to (`['polling', 'websocket']`)
- `allowUpgrades` (`Boolean`): whether to allow transport upgrades
(`true`)
- `perMessageDeflate` (`Object|Boolean`): parameters of the WebSocket permessage-deflate extension
(see [ws module](https://github.com/einaros/ws) api docs). Set to `false` to disable. (`true`)
- `httpCompression` (`Object|Boolean`): parameters of the http compression for the polling transports
(see [zlib](http://nodejs.org/api/zlib.html#zlib_options) api docs). Set to `false` to disable. (`true`)
- `threshold` (`Number`): data is compressed only if the byte size is above this value (`1024`)
- `cookie` (`String|Boolean`): name of the HTTP cookie that
contains the client sid to send as part of handshake response
headers. Set to `false` to not send one. (`io`)
Expand Down Expand Up @@ -297,7 +302,10 @@ A representation of a client. _Inherits from EventEmitter_.
sending binary data, which is sent as is.
- **Parameters**
- `String` | `Buffer` | `ArrayBuffer` | `ArrayBufferView`: a string or any object implementing `toString()`, with outgoing data, or a Buffer or ArrayBuffer with binary data. Also any ArrayBufferView can be sent as is.
- `Object`: optional, options object
- `Function`: optional, a callback executed when the message gets flushed out by the transport
- **Options**
- `compress` (`Boolean`): whether to compress sending data. This option might be ignored and forced to be `true` when using polling. (`true`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should split the packet buffer instead of ignoring?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm, I'm not sure which is better, but thought ignoring the option would be faster since we need a poll request per packet in the worst case if we don't do so.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, compression at the expense of latency is no good.

- **Returns** `Socket` for chaining
- `close`
- Disconnects the client
Expand Down
14 changes: 13 additions & 1 deletion lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,21 @@ function Server(opts){
this.allowUpgrades = false !== opts.allowUpgrades;
this.allowRequest = opts.allowRequest;
this.cookie = false !== opts.cookie ? (opts.cookie || 'io') : false;
this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || true) : false;
this.httpCompression = false !== opts.httpCompression ? (opts.httpCompression || {}) : false;

if (true === this.httpCompression) this.httpCompression = {};
if (this.httpCompression && null == this.httpCompression.threshold) {
this.httpCompression.threshold = 1024;
}

// initialize websocket server
if (~this.transports.indexOf('websocket')) {
this.ws = new WebSocketServer({ noServer: true, clientTracking: false });
this.ws = new WebSocketServer({
noServer: true,
clientTracking: false,
perMessageDeflate: this.perMessageDeflate
});
}
}

Expand Down Expand Up @@ -228,6 +239,7 @@ Server.prototype.handshake = function(transport, req){
var transport = new transports[transport](req);
if ('polling' == transportName) {
transport.maxHttpBufferSize = this.maxHttpBufferSize;
transport.httpCompression = this.httpCompression;
}

if (req._query && req._query.b64) {
Expand Down
25 changes: 19 additions & 6 deletions lib/socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Socket.prototype.maybeUpgrade = function (transport) {

function onPacket(packet){
if ('ping' == packet.type && 'probe' == packet.data) {
transport.send([{ type: 'pong', data: 'probe' }]);
transport.send([{ type: 'pong', data: 'probe', options: { compress: true } }]);
clearInterval(self.checkIntervalTimer);
self.checkIntervalTimer = setInterval(check, 100);
} else if ('upgrade' == packet.type && self.readyState != 'closed') {
Expand Down Expand Up @@ -202,7 +202,7 @@ Socket.prototype.maybeUpgrade = function (transport) {
function check(){
if ('polling' == self.transport.name && self.transport.writable) {
debug('writing a noop packet to polling for fast upgrade');
self.transport.send([{ type: 'noop' }]);
self.transport.send([{ type: 'noop', options: { compress: true } }]);
}
}

Expand Down Expand Up @@ -280,14 +280,15 @@ Socket.prototype.setupSendCallback = function () {
* Sends a message packet.
*
* @param {String} message
* @param {Object} options
* @param {Function} callback
* @return {Socket} for chaining
* @api public
*/

Socket.prototype.send =
Socket.prototype.write = function(data, callback){
this.sendPacket('message', data, callback);
Socket.prototype.write = function(data, options, callback){
this.sendPacket('message', data, options, callback);
return this;
};

Expand All @@ -296,14 +297,26 @@ Socket.prototype.write = function(data, callback){
*
* @param {String} packet type
* @param {String} optional, data
* @param {Object} options
* @api private
*/

Socket.prototype.sendPacket = function (type, data, callback) {
Socket.prototype.sendPacket = function (type, data, options, callback) {
if ('function' == typeof options) {
callback = options;
options = null;
}

options = options || {};
options.compress = false !== options.compress;

if ('closing' != this.readyState) {
debug('sending packet "%s" (%s)', type, data);

var packet = { type: type };
var packet = {
type: type,
options: options
};
if (data) packet.data = data;

// exports packetCreate event
Expand Down
18 changes: 2 additions & 16 deletions lib/transports/polling-jsonp.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ JSONP.prototype.onData = function (data) {
* @api private
*/

JSONP.prototype.doWrite = function (data) {
JSONP.prototype.doWrite = function (data, options, callback) {
// we must output valid javascript, not valid json
// see: http://timelessrepo.com/json-isnt-a-javascript-subset
var js = JSON.stringify(data)
Expand All @@ -70,21 +70,7 @@ JSONP.prototype.doWrite = function (data) {
// prepare response
data = this.head + js + this.foot;

// explicit UTF-8 is required for pages not served under utf
var headers = {
'Content-Type': 'text/javascript; charset=UTF-8',
'Content-Length': Buffer.byteLength(data)
};

// prevent XSS warnings on IE
// https://github.com/LearnBoost/socket.io/pull/1333
var ua = this.req.headers['user-agent'];
if (ua && (~ua.indexOf(';MSIE') || ~ua.indexOf('Trident/'))) {
headers['X-XSS-Protection'] = '0';
}

this.res.writeHead(200, this.headers(this.req, headers));
this.res.end(data);
Polling.prototype.doWrite.call(this, data, options, callback);
};

/**
Expand Down
30 changes: 0 additions & 30 deletions lib/transports/polling-xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,36 +48,6 @@ XHR.prototype.onRequest = function (req) {
}
};

/**
* Frames data prior to write.
*
* @api private
*/

XHR.prototype.doWrite = function(data){
// explicit UTF-8 is required for pages not served under utf
var isString = typeof data == 'string';
var contentType = isString
? 'text/plain; charset=UTF-8'
: 'application/octet-stream';
var contentLength = '' + (isString ? Buffer.byteLength(data) : data.length);

var headers = {
'Content-Type': contentType,
'Content-Length': contentLength
};

// prevent XSS warnings on IE
// https://github.com/LearnBoost/socket.io/pull/1333
var ua = this.req.headers['user-agent'];
if (ua && (~ua.indexOf(';MSIE') || ~ua.indexOf('Trident/'))) {
headers['X-XSS-Protection'] = '0';
}

this.res.writeHead(200, this.headers(this.req, headers));
this.res.end(data);
};

/**
* Returns headers for a response.
*
Expand Down
119 changes: 110 additions & 9 deletions lib/transports/polling.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@

var Transport = require('../transport')
, parser = require('engine.io-parser')
, zlib = require('zlib')
, accepts = require('accepts')
, debug = require('debug')('engine:polling');

var compressionMethods = {
gzip: zlib.createGzip,
deflate: zlib.createDeflate
};

/**
* Exports the constructor.
*/
Expand Down Expand Up @@ -99,7 +106,7 @@ Polling.prototype.onPollRequest = function (req, res) {
// if we're still writable but had a pending close, trigger an empty send
if (this.writable && this.shouldClose) {
debug('triggering empty send to append close packet');
this.send([{ type: 'noop' }]);
this.send([{ type: 'noop', options: { compress: true } }]);
}
};

Expand Down Expand Up @@ -214,7 +221,7 @@ Polling.prototype.onData = function (data) {
Polling.prototype.onClose = function () {
if (this.writable) {
// close pending poll request
this.send([{ type: 'noop' }]);
this.send([{ type: 'noop', options: { compress: true } }]);
}
Transport.prototype.onClose.call(this);
};
Expand All @@ -227,31 +234,125 @@ Polling.prototype.onClose = function () {
*/

Polling.prototype.send = function (packets) {
this.writable = false;

if (this.shouldClose) {
debug('appending close packet to payload');
packets.push({ type: 'close' });
packets.push({ type: 'close', options: { compress: true } });
this.shouldClose();
this.shouldClose = null;
}

var self = this;
parser.encodePayload(packets, this.supportsBinary, function(data) {
self.write(data);
var compress = packets.some(function(packet) {
return packet.options && packet.options.compress;
});
self.write(data, { compress: compress });
});
};

/**
* Writes data as response to poll request.
*
* @param {String} data
* @param {Object} options
* @api private
*/

Polling.prototype.write = function (data) {
Polling.prototype.write = function (data, options) {
debug('writing "%s"', data);
this.doWrite(data);
this.req.cleanup();
this.writable = false;
var self = this;
this.doWrite(data, options, function() {
self.req.cleanup();
});
};

/**
* Performs the write.
*
* @api private
*/

Polling.prototype.doWrite = function (data, options, callback) {
var self = this;

// explicit UTF-8 is required for pages not served under utf
var isString = typeof data == 'string';
var contentType = isString
? 'text/plain; charset=UTF-8'
: 'application/octet-stream';

var headers = {
'Content-Type': contentType
};

// prevent XSS warnings on IE
// https://github.com/LearnBoost/socket.io/pull/1333
var ua = this.req.headers['user-agent'];
if (ua && (~ua.indexOf(';MSIE') || ~ua.indexOf('Trident/'))) {
headers['X-XSS-Protection'] = '0';
}

if (!this.httpCompression || !options.compress) {
respond(data);
return;
}

var len = isString ? Buffer.byteLength(data) : data.length;
if (len < this.httpCompression.threshold) {
respond(data);
return;
}

var encoding = accepts(this.req).encodings(['gzip', 'deflate']);
if (!encoding) {
respond(data);
return;
}

this.compress(data, encoding, function(err, data) {
if (err) {
self.res.writeHead(500);
self.res.end();
callback(err);
return;
}

headers['Content-Encoding'] = encoding;
respond(data);
});

function respond(data) {
headers['Content-Length'] = 'string' == typeof data ? Buffer.byteLength(data) : data.length;
self.res.writeHead(200, self.headers(self.req, headers));
self.res.end(data);
callback();
}
};

/**
* Comparesses data.
*
* @api private
*/

Polling.prototype.compress = function (data, encoding, callback) {
debug('compressing');

var buffers = [];
var nread = 0;

compressionMethods[encoding](this.httpCompression)
.on('error', callback)
.on('data', function(chunk) {
buffers.push(chunk);
nread += chunk.length;
})
.on('end', function() {
callback(null, Buffer.concat(buffers, nread));
})
.end(data);
};

/**
Expand All @@ -270,7 +371,7 @@ Polling.prototype.doClose = function (fn) {

if (this.writable) {
debug('transport writable - closing right away');
this.send([{ type: 'close' }]);
this.send([{ type: 'close', options: { compress: true } }]);
fn();
} else {
debug('transport not writable - buffering orderly close');
Expand Down
5 changes: 3 additions & 2 deletions lib/transports/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,11 @@ WebSocket.prototype.onData = function (data) {
WebSocket.prototype.send = function (packets) {
var self = this;
for (var i = 0, l = packets.length; i < l; i++) {
parser.encodePacket(packets[i], this.supportsBinary, function(data) {
var packet = packets[i];
parser.encodePacket(packet, this.supportsBinary, function(data) {
debug('writing "%s"', data);
self.writable = false;
self.socket.send(data, function (err){
self.socket.send(data, packet.options, function (err){
if (err) return self.onError('write error', err.stack);
self.writable = true;
self.emit('drain');
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
],
"dependencies": {
"debug": "1.0.3",
"ws": "0.5.0",
"ws": "0.6.5",
"engine.io-parser": "1.2.1",
"base64id": "0.1.0"
"base64id": "0.1.0",
"accepts": "1.1.4"
},
"devDependencies": {
"mocha": "1.12.0",
Expand Down
Loading