blob: addc5c9a6b0b8b7cc9c33aba4014671366a4f337 [file] [log] [blame]
/*!
* Connect - compress
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* Copyright(c) 2012 Nebojsa Sabovic
* MIT Licensed
*/
/**
* Module dependencies.
*/
var zlib = require('zlib');
/**
* Supported content-encoding methods.
*/
exports.methods = {
gzip: zlib.createGzip
, deflate: zlib.createDeflate
};
/**
* Default filter function.
*/
exports.filter = function(req, res){
var type = res.getHeader('Content-Type') || '';
return type.match(/json|text|javascript/);
};
/**
* Compress:
*
* Compress response data with gzip/deflate.
*
* Filter:
*
* A `filter` callback function may be passed to
* replace the default logic of:
*
* exports.filter = function(req, res){
* var type = res.getHeader('Content-Type') || '';
* return type.match(/json|text|javascript/);
* };
*
* Options:
*
* All remaining options are passed to the gzip/deflate
* creation functions. Consult node's docs for additional details.
*
* - `chunkSize` (default: 16*1024)
* - `windowBits`
* - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression
* - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more
* - `strategy`: compression strategy
*
* @param {Object} options
* @return {Function}
* @api public
*/
module.exports = function compress(options) {
var options = options || {}
, names = Object.keys(exports.methods)
, filter = options.filter || exports.filter;
return function(req, res, next){
var accept = req.headers['accept-encoding']
, write = res.write
, writeHead = res.writeHead
, end = res.end
, stream
, method;
// vary
res.setHeader('Vary', 'Accept-Encoding');
// proxy
res.write = function(chunk, encoding){
if (!this._header) this._implicitHeader();
return stream
? stream.write(chunk, encoding)
: write.call(this, chunk, encoding);
};
res.end = function(chunk, encoding){
if (!this._header) {
this._implicitHeader();
}
if (chunk) this.write(chunk, encoding);
return stream
? stream.end()
: end.call(this);
};
res.writeHead = function(){
res.writeHead = writeHead;
// default request filter, SHOULD use identity, head
if (filter(req, res) && accept && 'HEAD' != req.method) {
// default to gzip
if ('*' == accept.trim()) {
method = 'gzip';
} else {
for (var i = 0, len = names.length; i < len; ++i) {
if (~accept.indexOf(names[i])) {
method = names[i];
break;
}
}
}
// compression method
if (method) {
// compression stream
stream = exports.methods[method](options);
// header fields
res.setHeader('Content-Encoding', method);
res.removeHeader('Content-Length');
// WARNING: We do not strip content-length out of passed-in headers
// compression
stream.on('data', function(chunk){
write.call(res, chunk);
});
stream.on('end', function(){
end.call(res);
});
stream.on('drain', function() {
res.emit('drain');
});
}
}
return res.writeHead.apply(this, arguments);
};
next();
};
}