2017-05-14 13:22:31 +02:00

193 lines
4.6 KiB
JavaScript

/*!
* Connect - compress
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var zlib = require('zlib');
var utils = require('../utils');
var Negotiator = require('negotiator');
/**
* Supported content-encoding methods.
*/
exports.methods = {
gzip: zlib.createGzip
, deflate: zlib.createDeflate
};
/**
* Default filter function.
*/
exports.filter = function(req, res){
return /json|text|javascript|dart|image\/svg\+xml|application\/x-font-ttf|application\/vnd\.ms-opentype|application\/vnd\.ms-fontobject/.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'));
* };
*
* Threshold:
*
* Only compress the response if the byte size is at or above a threshold.
* Always compress while streaming.
*
* - `threshold` - string representation of size or bytes as an integer.
*
* 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 filter = options.filter || exports.filter;
var threshold;
if (false === options.threshold || 0 === options.threshold) {
threshold = 0
} else if ('string' === typeof options.threshold) {
threshold = utils.parseBytes(options.threshold)
} else {
threshold = options.threshold || 1024
}
return function compress(req, res, next){
var accept = req.headers['accept-encoding']
, write = res.write
, end = res.end
, compress = true
, stream;
// see #724
req.on('close', function(){
res.write = res.end = function(){};
});
// flush is noop by default
res.flush = noop;
// proxy
res.write = function(chunk, encoding){
if (!this.headerSent) {
// if content-length is set and is lower
// than the threshold, don't compress
var length = res.getHeader('content-length');
if (!isNaN(length) && length < threshold) compress = false;
this._implicitHeader();
}
return stream
? stream.write(new Buffer(chunk, encoding))
: write.call(res, chunk, encoding);
};
res.end = function(chunk, encoding){
if (chunk) {
if (!this.headerSent && getSize(chunk) < threshold) compress = false;
this.write(chunk, encoding);
} else if (!this.headerSent) {
// response size === 0
compress = false;
}
return stream
? stream.end()
: end.call(res);
};
res.on('header', function(){
// default request filter
if (!filter(req, res)) return;
// vary
var vary = res.getHeader('Vary');
if (!vary) {
res.setHeader('Vary', 'Accept-Encoding');
} else if (!~vary.indexOf('Accept-Encoding')) {
res.setHeader('Vary', vary + ', Accept-Encoding');
}
if (!compress) return;
var encoding = res.getHeader('Content-Encoding') || 'identity';
// already encoded
if ('identity' != encoding) return;
// SHOULD use identity
if (!accept) return;
// head
if ('HEAD' == req.method) return;
// compression method
var method = new Negotiator(req).preferredEncoding(['gzip', 'deflate', 'identity']);
// negotiation failed
if (method === 'identity') return;
// compression stream
stream = exports.methods[method](options);
// overwrite the flush method
res.flush = function(){
stream.flush();
}
// 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();
};
};
function getSize(chunk) {
return Buffer.isBuffer(chunk)
? chunk.length
: Buffer.byteLength(chunk);
}
function noop(){}