| /*! | 
|  * 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 | 
| } |