├── .gitignore ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── AUTHORS ├── History.md ├── LICENSE.txt ├── Makefile ├── README.md ├── index.js ├── logo.png ├── package.json └── test └── index.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.html 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | .DS_Store 17 | coverage/ 18 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .tmp/ 4 | .git/ 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 14 | "indent" : false, // {int} Number of spaces to use for indentation 15 | "latedef" : false, // true: Require variables/functions to be defined before being used 16 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 18 | "noempty" : true, // true: Prohibit use of empty blocks 19 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 20 | "plusplus" : false, // true: Prohibit use of `++` & `--` 21 | "quotmark" : false, // Quotation mark consistency: 22 | // false : do nothing (default) 23 | // true : ensure whatever is used is consistent 24 | // "single" : require single quotes 25 | // "double" : require double quotes 26 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 27 | "unused" : false, // true: Require all defined variables be used 28 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 29 | "trailing" : false, // true: Prohibit trailing whitespaces 30 | "maxparams" : false, // {int} Max number of formal params allowed per function 31 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 32 | "maxstatements" : false, // {int} Max number statements per function 33 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 34 | "maxlen" : false, // {int} Max number of characters per line 35 | 36 | // Relaxing 37 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 38 | "boss" : true, // true: Tolerate assignments where comparisons would be expected 39 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 40 | "eqnull" : false, // true: Tolerate use of `== null` 41 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 42 | "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) 43 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 44 | // (ex: `for each`, multiple try/catch, function expression…) 45 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 46 | "expr" : true, // true: Tolerate `ExpressionStatement` as Programs 47 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 48 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 49 | "iterator" : false, // true: Tolerate using the `__iterator__` property 50 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 51 | "laxbreak" : true, // true: Tolerate possibly unsafe line breakings 52 | "laxcomma" : false, // true: Tolerate comma-first style coding 53 | "loopfunc" : false, // true: Tolerate functions being defined in loops 54 | "multistr" : true, // true: Tolerate multi-line strings 55 | "proto" : false, // true: Tolerate using the `__proto__` property 56 | "scripturl" : false, // true: Tolerate script-targeted URLs 57 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 58 | "shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 59 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 60 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 61 | "validthis" : true, // true: Tolerate using this in a non-constructor function 62 | 63 | // Environments 64 | "browser" : true, // Web Browser (window, document, etc) 65 | "couch" : false, // CouchDB 66 | "devel" : true, // Development/debugging (alert, confirm, etc) 67 | "dojo" : false, // Dojo Toolkit 68 | "jquery" : false, // jQuery 69 | "mootools" : false, // MooTools 70 | "node" : true, // Node.js 71 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 72 | "prototypejs" : false, // Prototype and Scriptaculous 73 | "rhino" : false, // Rhino 74 | "worker" : false, // Web Workers 75 | "wsh" : false, // Windows Scripting Host 76 | "yui" : false, // Yahoo User Interface 77 | "noyield" : true, // allow generators without a yield 78 | 79 | // Legacy 80 | "nomen" : false, // true: Prohibit dangling `_` in variables 81 | "onevar" : false, // true: Allow only one `var` statement per function 82 | "passfail" : false, // true: Stop on first error 83 | "white" : false, // true: Check against strict whitespace and indentation rules 84 | 85 | // Custom Globals 86 | "globals" : { // additional predefined global variables 87 | // mocha 88 | "describe": true, 89 | "it": true, 90 | "before": true, 91 | "afterEach": true, 92 | "beforeEach": true, 93 | "after": true 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | coverage.html 3 | Makefile 4 | .travis.yml 5 | logo.png 6 | .DS_Store 7 | coverage/ 8 | .jshint* 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.11' 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Ordered by date of first contribution. 2 | # Auto-generated by 'contributors' on Mon, 28 Apr 2014 00:57:27 GMT. 3 | # https://github.com/xingrz/node-contributors 4 | 5 | fengmk2 (https://github.com/fengmk2) 6 | dead-horse (https://github.com/dead-horse) 7 | coderhaoxin (https://github.com/coderhaoxin) 8 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.1.0 / 2014-04-28 3 | ================== 4 | 5 | * fix links 6 | * Merge pull request #4 from coderhaoxin/remove-content-length 7 | * remove possible incorrect content-length 8 | 9 | 0.0.4 / 2014-03-23 10 | ================== 11 | 12 | * if already set encoding, do not gzip (@dead-horse) 13 | 14 | 0.0.3 / 2014-03-05 15 | ================== 16 | 17 | * use delegating yields http://jongleberry.com/delegating-yield.html 18 | * use istanbul harmony branch 19 | * add coverage check 20 | * use istanbul replace blanket. gotwarlost/istanbul#44 21 | 22 | 0.0.2 / 2014-03-03 23 | ================== 24 | 25 | * add content min length == 150 bytes 26 | * only test on 0.11 27 | 28 | 0.0.1 / 2014-03-03 29 | ================== 30 | 31 | * first commit 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright (C) 2014 fengmk2 and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.test.js 2 | REPORTER = spec 3 | TIMEOUT = 1000 4 | MOCHA_OPTS = 5 | 6 | install: 7 | @npm install --registry=http://r.cnpmjs.org 8 | 9 | jshint: install 10 | @./node_modules/.bin/jshint . 11 | 12 | test: install 13 | @node_modules/.bin/mocha \ 14 | --harmony \ 15 | --reporter $(REPORTER) \ 16 | --timeout $(TIMEOUT) \ 17 | $(MOCHA_OPTS) \ 18 | $(TESTS) \ 19 | 20 | test-cov cov: install 21 | @NODE_ENV=test node --harmony \ 22 | node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha \ 23 | -- -u exports \ 24 | --reporter $(REPORTER) \ 25 | --timeout $(TIMEOUT) \ 26 | $(MOCHA_OPTS) \ 27 | $(TESTS) 28 | @./node_modules/.bin/cov coverage 29 | 30 | autod: 31 | @./node_modules/.bin/autod -w 32 | @$(MAKE) install 33 | 34 | contributors: 35 | @./node_modules/.bin/contributors -f plain -o AUTHORS 36 | 37 | .PHONY: test 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | koa-gzip 2 | ======= 3 | 4 | ___[Deprecated] please use [koa-compress](https://github.com/koajs/compress) instead.___ 5 | 6 | `gzip` support for [koa](https://github.com/koajs/koa) responses. 7 | 8 | ## Install 9 | 10 | ```bash 11 | $ npm install koa-gzip 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```js 17 | var koa = require('koa'); 18 | var gzip = require('koa-gzip'); 19 | 20 | var app = koa(); 21 | app.use(gzip()); 22 | ``` 23 | 24 | ### gzip always be the last middleware 25 | 26 | If you using other response middlewares, like [etag](https://github.com/koajs/etag), just use `gzip` be the first. 27 | 28 | ```js 29 | app.use(gzip()); 30 | app.use(fresh()); 31 | app.use(etag()); 32 | 33 | client => request => gzip() => fresh() => etag() => logic codes 34 | || 35 | client <= gzip() <= fresh() <= etag() <= response 36 | ``` 37 | 38 | ## License 39 | 40 | (The MIT License) 41 | 42 | Copyright (c) 2014 fengmk2 <fengmk2@gmail.com> and other contributors 43 | 44 | Permission is hereby granted, free of charge, to any person obtaining 45 | a copy of this software and associated documentation files (the 46 | 'Software'), to deal in the Software without restriction, including 47 | without limitation the rights to use, copy, modify, merge, publish, 48 | distribute, sublicense, and/or sell copies of the Software, and to 49 | permit persons to whom the Software is furnished to do so, subject to 50 | the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be 53 | included in all copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 56 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 57 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 58 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 59 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 60 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 61 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 62 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * koa-gzip - index.js 3 | * 4 | * Copyright(c) 2014 5 | * MIT Licensed 6 | * 7 | * Authors: 8 | * fengmk2 (http://fengmk2.github.com) 9 | */ 10 | 11 | "use strict"; 12 | 13 | /** 14 | * Module dependencies. 15 | */ 16 | 17 | var zlib = require('zlib'); 18 | var thunkify = require('thunkify-wrap'); 19 | 20 | var _gzip = thunkify(zlib.gzip); 21 | 22 | module.exports = function (options) { 23 | options = options || {}; 24 | // https://developers.google.com/speed/docs/best-practices/payload#GzipCompression 25 | options.minLength = Number(options.minLength) || 150; 26 | 27 | return function *gzip(next) { 28 | yield *next; 29 | 30 | var body = this.body; 31 | 32 | if (200 !== this.status 33 | || !body 34 | || this.acceptsEncodings('gzip') !== 'gzip' 35 | || this.response.header['content-encoding']) { 36 | return; 37 | } 38 | 39 | // TODO: Stream body 40 | if ('function' === typeof body.pipe) { 41 | this.set('content-encoding', 'gzip'); 42 | this.remove('content-length'); 43 | this.body = body.pipe(zlib.createGzip()); 44 | return; 45 | } 46 | 47 | if (typeof body === 'string') { 48 | body = new Buffer(body); 49 | } else if (!Buffer.isBuffer(body)) { 50 | body = new Buffer(JSON.stringify(body)); 51 | } 52 | 53 | if (body.length < options.minLength) { 54 | return; 55 | } 56 | 57 | var buf = yield _gzip(body); 58 | if (buf.length > body.length) { 59 | return; 60 | } 61 | 62 | this.set('content-encoding', 'gzip'); 63 | this.body = buf; 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koajs/koa-gzip/ff9db30d2d3e339de77ed5c59103bfa2366301f5/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-gzip", 3 | "version": "0.1.0", 4 | "description": "gzip support for koa responses.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "make test" 8 | }, 9 | "config": { 10 | "cov": { 11 | "threshold": 100 12 | } 13 | }, 14 | "dependencies": { 15 | "thunkify-wrap": "0.1.1" 16 | }, 17 | "devDependencies": { 18 | "autod": "*", 19 | "contributors": "*", 20 | "cov": "*", 21 | "istanbul-harmony": "*", 22 | "jshint": "*", 23 | "koa": "0.5.5", 24 | "mm": "0.2.1", 25 | "mocha": "*", 26 | "should": "3.3.1", 27 | "supertest": "0.11.0" 28 | }, 29 | "homepage": "https://github.com/node-modules/koa-gzip", 30 | "repository": { 31 | "type": "git", 32 | "url": "git://github.com/node-modules/koa-gzip.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/node-modules/koa-gzip/issues", 36 | "email": "fengmk2@gmail.com" 37 | }, 38 | "keywords": [ 39 | "koa-gzip", "gzip", "koa" 40 | ], 41 | "engines": { 42 | "node": ">= 0.11.9" 43 | }, 44 | "author": "fengmk2 (http://fengmk2.github.com)", 45 | "license": "MIT" 46 | } 47 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * koa-gzip - test/index.test.js 3 | * 4 | * Copyright(c) 2014 5 | * MIT Licensed 6 | * 7 | * Authors: 8 | * fengmk2 (http://fengmk2.github.com) 9 | */ 10 | 11 | "use strict"; 12 | 13 | /** 14 | * Module dependencies. 15 | */ 16 | 17 | var fs = require('fs'); 18 | var should = require('should'); 19 | var request = require('supertest'); 20 | var koa = require('koa'); 21 | var mm = require('mm'); 22 | var gzip = require('../'); 23 | 24 | describe('index.test.js', function () { 25 | var options = {}; 26 | var BODY = 'foo bar string, foo bar string, foo bar string, foo bar string, \ 27 | foo bar string, foo bar string, foo bar string, foo bar string, foo bar string, foo bar string, \ 28 | foo bar string, foo bar string, foo bar string, foo bar string, foo bar string, foo bar string'; 29 | var app = koa(); 30 | // app.outputErrors = true; 31 | app.use(gzip(options)); 32 | app.use(gzip(options)); 33 | app.use(function *(next) { 34 | if (this.url === '/404') { 35 | return yield next; 36 | } 37 | if (this.url === '/small') { 38 | return this.body = 'foo bar string'; 39 | } 40 | if (this.url === '/string') { 41 | return this.body = BODY; 42 | } 43 | if (this.url === '/buffer') { 44 | return this.body = new Buffer(BODY); 45 | } 46 | if (this.url === '/object') { 47 | return this.body = {foo: BODY}; 48 | } 49 | if (this.url === '/number') { 50 | return this.body = 1984; 51 | } 52 | if (this.url === '/stream') { 53 | var stat = fs.statSync(__filename); 54 | this.set('content-length', stat.size); 55 | return this.body = fs.createReadStream(__filename); 56 | } 57 | if (this.url === '/exists-encoding') { 58 | this.set('content-encoding', 'gzip'); 59 | return this.body = new Buffer('gzip'); 60 | } 61 | if (this.url === '/error') { 62 | return this.throw(new Error('mock error')); 63 | } 64 | }); 65 | 66 | before(function (done) { 67 | app = app.listen(0, done); 68 | }); 69 | 70 | afterEach(mm.restore); 71 | 72 | describe('gzip()', function () { 73 | it('should work with no options', function () { 74 | gzip(); 75 | }); 76 | }); 77 | 78 | describe('when status 200 and request accept-encoding include gzip', function () { 79 | it('should return gzip string body', function (done) { 80 | request(app) 81 | .get('/string') 82 | .set('Accept-Encoding', 'gzip,deflate,sdch') 83 | .expect(200) 84 | .expect('content-encoding', 'gzip') 85 | .expect('content-length', '46') 86 | .expect(BODY, done); 87 | }); 88 | 89 | it('should return raw string body if body smaller than minLength', function (done) { 90 | request(app) 91 | .get('/small') 92 | .set('Accept-Encoding', 'gzip,deflate,sdch') 93 | .expect(200) 94 | .expect('content-length', '14') 95 | .expect('foo bar string', function (err, res) { 96 | should.not.exist(err); 97 | should.not.exist(res.headers['content-encoding']); 98 | done(); 99 | }); 100 | }); 101 | 102 | it('should return raw string body if gzip body bigger than raw body', function (done) { 103 | mm(options, 'minLength', 10); 104 | request(app) 105 | .get('/small') 106 | .set('Accept-Encoding', 'gzip,deflate,sdch') 107 | .expect(200) 108 | .expect('content-length', '14') 109 | .expect('foo bar string', function (err, res) { 110 | should.not.exist(err); 111 | should.not.exist(res.headers['content-encoding']); 112 | done(); 113 | }); 114 | }); 115 | 116 | it('should return gzip buffer body', function (done) { 117 | request(app) 118 | .get('/buffer') 119 | .set('Accept-Encoding', 'gzip,deflate,sdch') 120 | .expect(200) 121 | .expect('content-encoding', 'gzip') 122 | .expect('content-length', '46') 123 | .expect(BODY, done); 124 | }); 125 | 126 | it('should return gzip stream body', function (done) { 127 | request(app) 128 | .get('/stream') 129 | .set('Accept-Encoding', 'gzip,deflate,sdch') 130 | .expect(200) 131 | .expect('Content-Encoding', 'gzip') 132 | .expect(fs.readFileSync(__filename, 'utf8'), 133 | function (err, res) { 134 | should.not.exist(err); 135 | should.not.exist(res.headers['content-length']); 136 | done(); 137 | }); 138 | }); 139 | 140 | it('should return gzip json body', function (done) { 141 | request(app) 142 | .get('/object') 143 | .set('Accept-Encoding', 'gzip,deflate,sdch') 144 | .expect(200) 145 | .expect('Content-Encoding', 'gzip') 146 | .expect('content-length', '52') 147 | .expect({foo: BODY}, done); 148 | }); 149 | 150 | it('should return number body', function (done) { 151 | request(app) 152 | .get('/number') 153 | .set('Accept-Encoding', 'gzip,deflate,sdch') 154 | .expect('content-length', '4') 155 | .expect(200, function (err, res) { 156 | should.not.exist(err); 157 | res.body.should.equal(1984); 158 | done(); 159 | }); 160 | }); 161 | }); 162 | 163 | describe('when status 200 and request accept-encoding exclude gzip', function () { 164 | it('should return raw body', function (done) { 165 | request(app) 166 | .get('/string') 167 | .set('Accept-Encoding', 'deflate,sdch') 168 | .expect(200) 169 | .expect('content-length', '' + BODY.length) 170 | .expect(BODY, 171 | function (err, res) { 172 | should.not.exist(err); 173 | should.not.exist(res.headers['content-encoding']); 174 | done(); 175 | }); 176 | }); 177 | }); 178 | 179 | describe('when status non 200', function () { 180 | it('should return 404', function (done) { 181 | request(app) 182 | .get('/404') 183 | .set('Accept-Encoding', 'gzip,deflate,sdch') 184 | .expect(404) 185 | .expect('Not Found', done); 186 | }); 187 | 188 | it('should return 500', function (done) { 189 | request(app) 190 | .get('/error') 191 | .set('Accept-Encoding', 'gzip,deflate,sdch') 192 | .expect(500) 193 | .expect('Internal Server Error', done); 194 | }); 195 | }); 196 | }); 197 | --------------------------------------------------------------------------------