You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							288 lines
						
					
					
						
							5.8 KiB
						
					
					
				
			
		
		
	
	
							288 lines
						
					
					
						
							5.8 KiB
						
					
					
				/*! | 
						|
 * compression | 
						|
 * Copyright(c) 2010 Sencha Inc. | 
						|
 * Copyright(c) 2011 TJ Holowaychuk | 
						|
 * Copyright(c) 2014 Jonathan Ong | 
						|
 * Copyright(c) 2014-2015 Douglas Christopher Wilson | 
						|
 * MIT Licensed | 
						|
 */ | 
						|
 | 
						|
'use strict' | 
						|
 | 
						|
/** | 
						|
 * Module dependencies. | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
var accepts = require('accepts') | 
						|
var Buffer = require('safe-buffer').Buffer | 
						|
var bytes = require('bytes') | 
						|
var compressible = require('compressible') | 
						|
var debug = require('debug')('compression') | 
						|
var onHeaders = require('on-headers') | 
						|
var vary = require('vary') | 
						|
var zlib = require('zlib') | 
						|
 | 
						|
/** | 
						|
 * Module exports. | 
						|
 */ | 
						|
 | 
						|
module.exports = compression | 
						|
module.exports.filter = shouldCompress | 
						|
 | 
						|
/** | 
						|
 * Module variables. | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/ | 
						|
 | 
						|
/** | 
						|
 * Compress response data with gzip / deflate. | 
						|
 * | 
						|
 * @param {Object} [options] | 
						|
 * @return {Function} middleware | 
						|
 * @public | 
						|
 */ | 
						|
 | 
						|
function compression (options) { | 
						|
  var opts = options || {} | 
						|
 | 
						|
  // options | 
						|
  var filter = opts.filter || shouldCompress | 
						|
  var threshold = bytes.parse(opts.threshold) | 
						|
 | 
						|
  if (threshold == null) { | 
						|
    threshold = 1024 | 
						|
  } | 
						|
 | 
						|
  return function compression (req, res, next) { | 
						|
    var ended = false | 
						|
    var length | 
						|
    var listeners = [] | 
						|
    var stream | 
						|
 | 
						|
    var _end = res.end | 
						|
    var _on = res.on | 
						|
    var _write = res.write | 
						|
 | 
						|
    // flush | 
						|
    res.flush = function flush () { | 
						|
      if (stream) { | 
						|
        stream.flush() | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    // proxy | 
						|
 | 
						|
    res.write = function write (chunk, encoding) { | 
						|
      if (ended) { | 
						|
        return false | 
						|
      } | 
						|
 | 
						|
      if (!this._header) { | 
						|
        this._implicitHeader() | 
						|
      } | 
						|
 | 
						|
      return stream | 
						|
        ? stream.write(toBuffer(chunk, encoding)) | 
						|
        : _write.call(this, chunk, encoding) | 
						|
    } | 
						|
 | 
						|
    res.end = function end (chunk, encoding) { | 
						|
      if (ended) { | 
						|
        return false | 
						|
      } | 
						|
 | 
						|
      if (!this._header) { | 
						|
        // estimate the length | 
						|
        if (!this.getHeader('Content-Length')) { | 
						|
          length = chunkLength(chunk, encoding) | 
						|
        } | 
						|
 | 
						|
        this._implicitHeader() | 
						|
      } | 
						|
 | 
						|
      if (!stream) { | 
						|
        return _end.call(this, chunk, encoding) | 
						|
      } | 
						|
 | 
						|
      // mark ended | 
						|
      ended = true | 
						|
 | 
						|
      // write Buffer for Node.js 0.8 | 
						|
      return chunk | 
						|
        ? stream.end(toBuffer(chunk, encoding)) | 
						|
        : stream.end() | 
						|
    } | 
						|
 | 
						|
    res.on = function on (type, listener) { | 
						|
      if (!listeners || type !== 'drain') { | 
						|
        return _on.call(this, type, listener) | 
						|
      } | 
						|
 | 
						|
      if (stream) { | 
						|
        return stream.on(type, listener) | 
						|
      } | 
						|
 | 
						|
      // buffer listeners for future stream | 
						|
      listeners.push([type, listener]) | 
						|
 | 
						|
      return this | 
						|
    } | 
						|
 | 
						|
    function nocompress (msg) { | 
						|
      debug('no compression: %s', msg) | 
						|
      addListeners(res, _on, listeners) | 
						|
      listeners = null | 
						|
    } | 
						|
 | 
						|
    onHeaders(res, function onResponseHeaders () { | 
						|
      // determine if request is filtered | 
						|
      if (!filter(req, res)) { | 
						|
        nocompress('filtered') | 
						|
        return | 
						|
      } | 
						|
 | 
						|
      // determine if the entity should be transformed | 
						|
      if (!shouldTransform(req, res)) { | 
						|
        nocompress('no transform') | 
						|
        return | 
						|
      } | 
						|
 | 
						|
      // vary | 
						|
      vary(res, 'Accept-Encoding') | 
						|
 | 
						|
      // content-length below threshold | 
						|
      if (Number(res.getHeader('Content-Length')) < threshold || length < threshold) { | 
						|
        nocompress('size below threshold') | 
						|
        return | 
						|
      } | 
						|
 | 
						|
      var encoding = res.getHeader('Content-Encoding') || 'identity' | 
						|
 | 
						|
      // already encoded | 
						|
      if (encoding !== 'identity') { | 
						|
        nocompress('already encoded') | 
						|
        return | 
						|
      } | 
						|
 | 
						|
      // head | 
						|
      if (req.method === 'HEAD') { | 
						|
        nocompress('HEAD request') | 
						|
        return | 
						|
      } | 
						|
 | 
						|
      // compression method | 
						|
      var accept = accepts(req) | 
						|
      var method = accept.encoding(['gzip', 'deflate', 'identity']) | 
						|
 | 
						|
      // we really don't prefer deflate | 
						|
      if (method === 'deflate' && accept.encoding(['gzip'])) { | 
						|
        method = accept.encoding(['gzip', 'identity']) | 
						|
      } | 
						|
 | 
						|
      // negotiation failed | 
						|
      if (!method || method === 'identity') { | 
						|
        nocompress('not acceptable') | 
						|
        return | 
						|
      } | 
						|
 | 
						|
      // compression stream | 
						|
      debug('%s compression', method) | 
						|
      stream = method === 'gzip' | 
						|
        ? zlib.createGzip(opts) | 
						|
        : zlib.createDeflate(opts) | 
						|
 | 
						|
      // add buffered listeners to stream | 
						|
      addListeners(stream, stream.on, listeners) | 
						|
 | 
						|
      // header fields | 
						|
      res.setHeader('Content-Encoding', method) | 
						|
      res.removeHeader('Content-Length') | 
						|
 | 
						|
      // compression | 
						|
      stream.on('data', function onStreamData (chunk) { | 
						|
        if (_write.call(res, chunk) === false) { | 
						|
          stream.pause() | 
						|
        } | 
						|
      }) | 
						|
 | 
						|
      stream.on('end', function onStreamEnd () { | 
						|
        _end.call(res) | 
						|
      }) | 
						|
 | 
						|
      _on.call(res, 'drain', function onResponseDrain () { | 
						|
        stream.resume() | 
						|
      }) | 
						|
    }) | 
						|
 | 
						|
    next() | 
						|
  } | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Add bufferred listeners to stream | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function addListeners (stream, on, listeners) { | 
						|
  for (var i = 0; i < listeners.length; i++) { | 
						|
    on.apply(stream, listeners[i]) | 
						|
  } | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Get the length of a given chunk | 
						|
 */ | 
						|
 | 
						|
function chunkLength (chunk, encoding) { | 
						|
  if (!chunk) { | 
						|
    return 0 | 
						|
  } | 
						|
 | 
						|
  return !Buffer.isBuffer(chunk) | 
						|
    ? Buffer.byteLength(chunk, encoding) | 
						|
    : chunk.length | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Default filter function. | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function shouldCompress (req, res) { | 
						|
  var type = res.getHeader('Content-Type') | 
						|
 | 
						|
  if (type === undefined || !compressible(type)) { | 
						|
    debug('%s not compressible', type) | 
						|
    return false | 
						|
  } | 
						|
 | 
						|
  return true | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Determine if the entity should be transformed. | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function shouldTransform (req, res) { | 
						|
  var cacheControl = res.getHeader('Cache-Control') | 
						|
 | 
						|
  // Don't compress for Cache-Control: no-transform | 
						|
  // https://tools.ietf.org/html/rfc7234#section-5.2.2.4 | 
						|
  return !cacheControl || | 
						|
    !cacheControlNoTransformRegExp.test(cacheControl) | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Coerce arguments to Buffer | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function toBuffer (chunk, encoding) { | 
						|
  return !Buffer.isBuffer(chunk) | 
						|
    ? Buffer.from(chunk, encoding) | 
						|
    : chunk | 
						|
}
 | 
						|
 |