pax_global_header00006660000000000000000000000064124032203450014505gustar00rootroot0000000000000052 comment=3b2473dfc4d81116de2dfe84061b6a5962a96062 compression-1.1.0/000077500000000000000000000000001240322034500140455ustar00rootroot00000000000000compression-1.1.0/.gitignore000066400000000000000000000000441240322034500160330ustar00rootroot00000000000000coverage node_modules npm-debug.log compression-1.1.0/.travis.yml000066400000000000000000000003711240322034500161570ustar00rootroot00000000000000language: node_js node_js: - "0.8" - "0.10" - "0.11" matrix: allow_failures: - node_js: "0.11" fast_finish: true script: "npm run-script test-travis" after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" compression-1.1.0/HISTORY.md000066400000000000000000000027761240322034500155440ustar00rootroot000000000000001.1.0 / 2014-09-07 ================== * deps: accepts@~1.1.0 * deps: compressible@~2.0.0 * deps: debug@~2.0.0 1.0.11 / 2014-08-10 =================== * deps: on-headers@~1.0.0 * deps: vary@~1.0.0 1.0.10 / 2014-08-05 =================== * deps: compressible@~1.1.1 - Fix upper-case Content-Type characters prevent compression 1.0.9 / 2014-07-20 ================== * Add `debug` messages * deps: accepts@~1.0.7 - deps: negotiator@0.4.7 1.0.8 / 2014-06-20 ================== * deps: accepts@~1.0.5 - use `mime-types` 1.0.7 / 2014-06-11 ================== * use vary module for better `Vary` behavior * deps: accepts@1.0.3 * deps: compressible@1.1.0 1.0.6 / 2014-06-03 ================== * fix regression when negotiation fails 1.0.5 / 2014-06-03 ================== * fix listeners for delayed stream creation - fixes regression for certain `stream.pipe(res)` situations 1.0.4 / 2014-06-03 ================== * fix adding `Vary` when value stored as array * fix back-pressure behavior * fix length check for `res.end` 1.0.3 / 2014-05-29 ================== * use `accepts` for negotiation * use `on-headers` to handle header checking * deps: bytes@1.0.0 1.0.2 / 2014-04-29 ================== * only version compatible with node.js 0.8 * support headers given to `res.writeHead` * deps: bytes@0.3.0 * deps: negotiator@0.4.3 1.0.1 / 2014-03-08 ================== * bump negotiator * use compressible * use .headersSent (drops 0.8 support) * handle identity;q=0 case compression-1.1.0/LICENSE000066400000000000000000000022171240322034500150540ustar00rootroot00000000000000(The MIT License) Copyright (c) 2014 Jonathan Ong Copyright (c) 2014 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. compression-1.1.0/README.md000066400000000000000000000060331240322034500153260ustar00rootroot00000000000000# compression [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] [![Gratipay][gratipay-image]][gratipay-url] Node.js compression middleware. ## Install ```bash $ npm install compression ``` ## API ```js var compression = require('compression') ``` ### compression(options) Returns the compression middleware using the given `options`. ```js app.use(compression({ threshold: 512 })) ``` #### Options - `threshold` `<1kb>` - response is only compressed if the byte size is at or above this threshold. - `filter` - a filtering callback function. Uses [Compressible](https://github.com/expressjs/compressible) by default. In addition to these, [zlib](http://nodejs.org/api/zlib.html) options may be passed in to the options object. ### res.flush This module adds a `res.flush()` method to force the partially-compressed response to be flushed to the client. ## Examples ### express/connect When using this module with express or connect, simply `app.use` the module as high as you like. Requests that pass through the middleware will be compressed. ```js var compression = require('compression') var express = require('express') var app = express() // compress all requests app.use(compression()) // add alll routes ``` ### Server-Sent Events Because of the nature of compression this module does not work out of the box with server-sent events. To compress content, a window of the output needs to be buffered up in order to get good compression. Typically when using server-sent events, there are certain block of data that need to reach the client. You can achieve this by calling `res.flush()` when you need the data written to actually make it to the client. ```js var compression = require('compression') var express = require('express') var app = express() // compress responses app.use(compression()) // server-sent event stream app.get('/events', function (req, res) { res.setHeader('Content-Type', 'text/event-stream') res.setHeader('Cache-Control', 'no-cache') // send a ping approx eveny 2 seconds var timer = setInterval(function () { res.write('data: ping\n\n') // !!! this is the important part res.flush() }, 2000) res.on('close', function () { clearInterval(timer) }) }) ``` ## License [MIT](LICENSE) [npm-image]: https://img.shields.io/npm/v/compression.svg?style=flat [npm-url]: https://npmjs.org/package/compression [travis-image]: https://img.shields.io/travis/expressjs/compression.svg?style=flat [travis-url]: https://travis-ci.org/expressjs/compression [coveralls-image]: https://img.shields.io/coveralls/expressjs/compression.svg?style=flat [coveralls-url]: https://coveralls.io/r/expressjs/compression?branch=master [downloads-image]: http://img.shields.io/npm/dm/compression.svg?style=flat [downloads-url]: https://npmjs.org/package/compression [gratipay-image]: https://img.shields.io/gratipay/dougwilson.svg?style=flat [gratipay-url]: https://www.gratipay.com/dougwilson/ compression-1.1.0/index.js000066400000000000000000000114551240322034500155200ustar00rootroot00000000000000/*! * compression * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014 Jonathan Ong * Copyright(c) 2014 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. */ var zlib = require('zlib'); var accepts = require('accepts'); var bytes = require('bytes'); var debug = require('debug')('compression') var onHeaders = require('on-headers'); var compressible = require('compressible'); var vary = require('vary'); /** * Supported content-encoding methods. */ exports.methods = { gzip: zlib.createGzip , deflate: zlib.createDeflate }; /** * Default filter function. */ exports.filter = function filter(req, res) { var type = res.getHeader('Content-Type') if (type === undefined || !compressible(type)) { debug('%s not compressible', type) return false } return true }; /** * Compress response data with gzip / deflate. * * @param {Object} options * @return {Function} middleware * @api public */ module.exports = function compression(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 = bytes(options.threshold) } else { threshold = options.threshold || 1024 } return function compression(req, res, next){ var compress = true var listeners = [] var write = res.write var on = res.on var end = res.end var stream // see #8 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._header) { // if content-length is set and is lower // than the threshold, don't compress var len = Number(res.getHeader('Content-Length')) checkthreshold(len) this._implicitHeader(); } return stream ? stream.write(new Buffer(chunk, encoding)) : write.call(res, chunk, encoding); }; res.end = function(chunk, encoding){ var len if (chunk) { len = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk, encoding) } if (!this._header) { checkthreshold(len) } if (chunk) { this.write(chunk, encoding); } return stream ? stream.end() : end.call(res); }; res.on = function(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 checkthreshold(len) { if (compress && len < threshold) { debug('size below threshold') compress = false } } function nocompress(msg) { debug('no compression' + (msg ? ': ' + msg : '')) addListeners(res, on, listeners) listeners = null } onHeaders(res, function(){ // determine if request is filtered if (!filter(req, res)) { nocompress('filtered') return } // vary vary(res, 'Accept-Encoding') if (!compress) { nocompress() return } var encoding = res.getHeader('Content-Encoding') || 'identity'; // already encoded if ('identity' !== encoding) { nocompress('already encoded') return } // head if ('HEAD' === req.method) { nocompress('HEAD request') return } // compression method var accept = accepts(req); var method = accept.encodings(['gzip', 'deflate', 'identity']); // negotiation failed if (!method || method === 'identity') { nocompress('not acceptable') return } // compression stream debug('%s compression', method) stream = exports.methods[method](options); addListeners(stream, stream.on, listeners) // 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){ if (write.call(res, chunk) === false) { stream.pause() } }); stream.on('end', function(){ end.call(res); }); on.call(res, 'drain', function() { stream.resume() }); }); next(); }; }; /** * Add bufferred listeners to stream */ function addListeners(stream, on, listeners) { for (var i = 0; i < listeners.length; i++) { on.apply(stream, listeners[i]) } } function noop(){} compression-1.1.0/package.json000066400000000000000000000020151240322034500163310ustar00rootroot00000000000000{ "name": "compression", "description": "Compression middleware for connect and node.js", "version": "1.1.0", "author": "Jonathan Ong (http://jongleberry.com)", "contributors": [ "Douglas Christopher Wilson " ], "license": "MIT", "repository": "expressjs/compression", "dependencies": { "accepts": "~1.1.0", "bytes": "1.0.0", "compressible": "~2.0.0", "debug": "~2.0.0", "on-headers": "~1.0.0", "vary": "~1.0.0" }, "devDependencies": { "istanbul": "0.3.2", "mocha": "~1.21.3", "supertest": "~0.13.0", "should": "~4.0.1" }, "files": [ "LICENSE", "HISTORY.md", "index.js" ], "engines": { "node": ">= 0.8.0" }, "scripts": { "test": "mocha --check-leaks --reporter spec --bail", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --check-leaks --reporter dot", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --check-leaks --reporter spec" } } compression-1.1.0/test/000077500000000000000000000000001240322034500150245ustar00rootroot00000000000000compression-1.1.0/test/test.js000066400000000000000000000342241240322034500163460ustar00rootroot00000000000000var bytes = require('bytes'); var crypto = require('crypto'); var http = require('http'); var request = require('supertest'); var should = require('should'); var compress = require('..'); describe('compress()', function(){ it('should gzip files', function(done){ var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'gzip', done) }) it('should skip HEAD', function(done){ var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) request(server) .head('/') .set('Accept-Encoding', 'gzip') .end(function (err, res) { if (err) return done(err) res.headers.should.not.have.property('content-encoding') done() }) }) it('should skip unknown accept-encoding', function(done){ var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) request(server) .get('/') .set('Accept-Encoding', 'bogus') .end(function (err, res) { if (err) return done(err) res.headers.should.not.have.property('content-encoding') done() }) }) it('should skip if content-encoding already set', function(done){ var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Encoding', 'x-custom') res.end('hello, world') }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'x-custom') .expect(200, 'hello, world', done) }) it('should set Vary', function(done){ var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'gzip') .expect('Vary', 'Accept-Encoding', done) }) it('should set Vary even if Accept-Encoding is not set', function(done){ var server = createServer({ threshold: 1000 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) request(server) .get('/') .expect('Vary', 'Accept-Encoding') .end(function (err, res) { if (err) return done(err) res.headers.should.not.have.property('content-encoding') done() }) }) it('should not set Vary if Content-Type does not pass filter', function(done){ var server = createServer(null, function (req, res) { res.setHeader('Content-Type', 'image/jpeg') res.end() }) request(server) .get('/') .end(function (err, res) { if (err) return done(err) res.headers.should.not.have.property('vary') done() }) }) it('should set Vary for HEAD request', function(done){ var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) request(server) .head('/') .set('Accept-Encoding', 'gzip') .expect('Vary', 'Accept-Encoding', done) }) it('should transfer chunked', function(done){ var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Transfer-Encoding', 'chunked', done) }) it('should remove Content-Length for chunked', function(done){ var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('hello, world') }) request(server) .get('/') .expect('Content-Encoding', 'gzip') .end(function (err, res) { if (err) return done(err) res.headers.should.not.have.property('content-length') done() }) }) it('should allow writing after close', function(done){ // UGH var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.on('close', function () { res.write('hello, ') res.end('world') done() }) res.destroy() }) request(server) .get('/') .end(function(){}) }) it('should back-pressure when compressed', function(done){ var buf var client var drained = false var resp var server = createServer({ threshold: 0 }, function (req, res) { resp = res res.on('drain', function(){ drained = true }) res.setHeader('Content-Type', 'text/plain') res.write('start') pressure() }) var wait = 2 crypto.pseudoRandomBytes(1024 * 128, function(err, chunk){ buf = chunk pressure() }) function complete(){ if (--wait !== 0) return drained.should.be.true done() } function pressure(){ if (!buf || !resp || !client) return while (resp.write(buf) !== false) { resp.flush() } resp.on('drain', function(){ resp.write('end') resp.end() }) resp.on('finish', complete) client.resume() } request(server) .get('/') .request() .on('response', function (res) { client = res res.headers['content-encoding'].should.equal('gzip') res.pause() res.on('end', complete) pressure() }) .end() }) it('should back-pressure when uncompressed', function(done){ var buf var client var drained = false var resp var server = createServer({ filter: function(){ return false } }, function (req, res) { resp = res res.on('drain', function(){ drained = true }) res.setHeader('Content-Type', 'text/plain') res.write('start') pressure() }) var wait = 2 crypto.pseudoRandomBytes(1024 * 128, function(err, chunk){ buf = chunk pressure() }) function complete(){ if (--wait !== 0) return drained.should.be.true done() } function pressure(){ if (!buf || !resp || !client) return while (resp.write(buf) !== false) { resp.flush() } resp.on('drain', function(){ resp.write('end') resp.end() }) resp.on('finish', complete) client.resume() } request(server) .get('/') .request() .on('response', function (res) { client = res res.headers.should.not.have.property('content-encoding') res.pause() res.on('end', complete) pressure() }) .end() }) it('should transfer large bodies', function (done) { var len = bytes('1mb') var buf = new Buffer(len) var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end(buf) }) buf.fill('.') request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Transfer-Encoding', 'chunked') .expect('Content-Encoding', 'gzip', function (err, res) { if (err) return done(err) should(res.text).equal(buf.toString()) res.text.length.should.equal(len) done() }) }) it('should transfer large bodies with multiple writes', function (done) { var len = bytes('40kb') var buf = new Buffer(len) var server = createServer({ threshold: 0 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.write(buf) res.write(buf) res.write(buf) res.end(buf) }) buf.fill('.') request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Transfer-Encoding', 'chunked') .expect('Content-Encoding', 'gzip', function (err, res) { if (err) return done(err) res.text.length.should.equal(len * 4) done() }) }) describe('threshold', function(){ it('should not compress responses below the threshold size', function(done){ var server = createServer({ threshold: '1kb' }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Length', '12') res.end('hello, world') }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .end(function(err, res){ if (err) return done(err) res.headers.should.not.have.property('content-encoding') done() }) }) it('should compress responses above the threshold size', function(done){ var server = createServer({ threshold: '1kb' }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Length', '2048') res.end(new Buffer(2048)) }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'gzip', done) }) it('should compress when streaming without a content-length', function(done){ var server = createServer({ threshold: '1kb' }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.write('hello, ') setTimeout(function(){ res.end('world') }, 10) }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'gzip', done) }) it('should not compress when streaming and content-length is lower than threshold', function(done){ var server = createServer({ threshold: '1kb' }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Length', '12') res.write('hello, ') setTimeout(function(){ res.end('world') }, 10) }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .end(function(err, res){ if (err) return done(err) res.headers.should.not.have.property('content-encoding') done() }) }) it('should compress when streaming and content-length is larger than threshold', function(done){ var server = createServer({ threshold: '1kb' }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Length', '2048') res.write(new Buffer(1024)) setTimeout(function(){ res.end(new Buffer(1024)) }, 10) }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect('Content-Encoding', 'gzip', done) }) // res.end(str, encoding) broken in node.js 0.8 var run = /^v0\.8\./.test(process.version) ? it.skip : it run('should handle writing hex data', function(done){ var server = createServer({ threshold: 6 }, function (req, res) { res.setHeader('Content-Type', 'text/plain') res.end('2e2e2e2e', 'hex') }) request(server) .get('/') .set('Accept-Encoding', 'gzip') .expect(200, '....', function (err, res) { if (err) return done(err) res.headers.should.not.have.property('content-encoding') done() }) }) }) describe('res.flush()', function () { it('should always be present', function (done) { var server = createServer(null, function (req, res) { res.statusCode = typeof res.flush === 'function' ? 200 : 500 res.flush() res.end() }) request(server) .get('/') .expect(200, done) }) it('should flush the response', function (done) { var chunks = 0 var resp var server = createServer({ threshold: 0 }, function (req, res) { resp = res res.setHeader('Content-Type', 'text/plain') res.setHeader('Content-Length', '2048') write() }) function write() { chunks++ if (chunks === 2) return resp.end() if (chunks > 2) return chunks-- resp.write(new Buffer(1024)) resp.flush() } request(server) .get('/') .set('Accept-Encoding', 'gzip') .request() .on('response', function (res) { res.headers['content-encoding'].should.equal('gzip') res.on('data', write) res.on('end', function(){ chunks.should.equal(2) done() }) }) .end() }) it('should flush small chunks for gzip', function (done) { var chunks = 0 var resp var server = createServer({ threshold: 0 }, function (req, res) { resp = res res.setHeader('Content-Type', 'text/plain') write() }) function write() { chunks++ if (chunks === 20) return resp.end() if (chunks > 20) return chunks-- resp.write('..') resp.flush() } request(server) .get('/') .set('Accept-Encoding', 'gzip') .request() .on('response', function (res) { res.headers['content-encoding'].should.equal('gzip') res.on('data', write) res.on('end', function(){ chunks.should.equal(20) done() }) }) .end() }) it('should flush small chunks for deflate', function (done) { var chunks = 0 var resp var server = createServer({ threshold: 0 }, function (req, res) { resp = res res.setHeader('Content-Type', 'text/plain') write() }) function write() { chunks++ if (chunks === 20) return resp.end() if (chunks > 20) return chunks-- resp.write('..') resp.flush() } request(server) .get('/') .set('Accept-Encoding', 'deflate') .request() .on('response', function (res) { res.headers['content-encoding'].should.equal('deflate') res.on('data', write) res.on('end', function(){ chunks.should.equal(20) done() }) }) .end() }) }) }) function createServer(opts, fn) { var _compress = compress(opts) return http.createServer(function (req, res) { _compress(req, res, function (err) { if (err) { res.statusCode = err.status || 500 res.end(err.message) return; } fn(req, res) }) }) }