/*! * Connect - compress * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * 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){ return /json|text|javascript/.test(res.getHeader('Content-Type')); }; /** * 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){ * return /json|text|javascript/.test(res.getHeader('Content-Type')); * }; * * 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) { options = options || {}; var names = Object.keys(exports.methods) , filter = options.filter || exports.filter; return function compress(req, res, next){ var accept = req.headers['accept-encoding'] , vary = res.getHeader('Vary') , write = res.write , end = res.end , stream , method; // vary if (!vary) { res.setHeader('Vary', 'Accept-Encoding'); } else if (!~vary.indexOf('Accept-Encoding')) { res.setHeader('Vary', vary + ', Accept-Encoding'); } // see #724 req.on('close', function(){ res.write = res.end = function(){}; }); // proxy res.write = function(chunk, encoding){ if (!this.headerSent) this._implicitHeader(); return stream ? stream.write(new Buffer(chunk, encoding)) : write.call(res, chunk, encoding); }; res.end = function(chunk, encoding){ if (chunk) this.write(chunk, encoding); return stream ? stream.end() : end.call(res); }; res.on('header', function(){ var encoding = res.getHeader('Content-Encoding') || 'identity'; // already encoded if ('identity' != encoding) return; // default request filter if (!filter(req, res)) return; // SHOULD use identity if (!accept) return; // head if ('HEAD' == req.method) return; // default to gzip if ('*' == accept.trim()) method = 'gzip'; // compression method if (!method) { for (var i = 0, len = names.length; i < len; ++i) { if (~accept.indexOf(names[i])) { method = names[i]; break; } } } // compression method if (!method) return; // compression stream stream = exports.methods[method](options); // header fields res.setHeader('Content-Encoding', method); res.removeHeader('Content-Length'); // compression stream.on('data', function(chunk){ write.call(res, chunk); }); stream.on('end', function(){ end.call(res); }); stream.on('drain', function() { res.emit('drain'); }); }); next(); }; };