├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── lib ├── compress.js └── staticGzip.js ├── package.json └── test ├── fixtures ├── index_test │ └── index.html ├── js │ └── nestedTest.js ├── space the final frontier │ └── tomg.co.png ├── test.js ├── test.js.gzip ├── tomg.co.png ├── user.gzip ├── user.json ├── utf8.gz ├── utf8.txt └── utf8.txt.gz ├── prefexTest.js └── staticGzipTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .DS_STORE 3 | *.swp 4 | *.monitor 5 | nodemon-ignore 6 | .*.sw[a-z] 7 | *.un~i 8 | .DS_Store 9 | Icon? 10 | ._* 11 | .Spotlight-V100 12 | .Trashes 13 | bench/* 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated Please use https://github.com/expressjs/compression instead 2 | 3 | # gzippo [![Build Status](https://secure.travis-ci.org/tomgco/gzippo.png?branch=master)](https://secure.travis-ci.org/tomgco/gzippo) 4 | 5 | gzippo pronounced `g-zippo` is a gzip middleware for Connect / expressjs using node-compress for better performance, in node 0.6 and up will be using the new zlib api. 6 | 7 | gzippo currently only supports only gzipping static content files however a release is in progress to introduce streaming support. 8 | 9 | ## Notice 10 | 11 | Please note that gzippo@0.0.X branch will only be tested for nodejs 0.4, where as gzippo@0.1.X will work for node 0.6 12 | 13 | ## Installation 14 | 15 | $ npm install gzippo 16 | 17 | ### Usage 18 | #### Static Gzip 19 | 20 | In your express/connect server setup, use as follows: 21 | 22 | var gzippo = require('gzippo'); 23 | 24 | //Replace the static provider with gzippo's 25 | //app.use(express.static(__dirname + '/public')); 26 | app.use(gzippo.staticGzip(__dirname + '/public')); 27 | 28 | Options: 29 | 30 | - `contentTypeMatch` - A regular expression tested against the Content-Type header to determine whether the response should be gzipped or not. The default value is `/text|javascript|json/`. 31 | - `maxAge` - cache-control max-age directive, defaulting to 1 day 32 | - `clientMaxAge` - browser cache-control max-age directive, defaulting to 1 week 33 | - `prefix` - A url prefix. If you want all your static content in a root path such as /resource/. Any url paths not matching will be ignored 34 | 35 | Currently the gzipped version is created and stored in memory. This is not final and was done to get a working version 36 | up and about. 37 | 38 | Gzippo now uses the native Zlib support found in node >= 0.6 39 | 40 | #### Streaming Gzip 41 | 42 | Starting in Connect 2.X Expressjs has the ability to use a streaming gzip module provided natively by connect. As this 2.X branch is not currently stable I have back ported the compress.js component into gzippo. 43 | 44 | app.use(gzippo.staticGzip(__dirname + '/public')); 45 | app.use(gzippo.compress()); 46 | 47 | This has no caching and is currently unsupported as it will be included in a future connect 1.X release, up until then compress.js will be included in gzippo. 48 | 49 | ## License 50 | 51 | (The MIT License) 52 | 53 | Copyright (c) 2011 Tom Gallacher <> 54 | 55 | Permission is hereby granted, free of charge, to any person obtaining 56 | a copy of this software and associated documentation files (the 57 | 'Software'), to deal in the Software without restriction, including 58 | without limitation the rights to use, copy, modify, merge, publish, 59 | distribute, sublicense, and/or sell copies of the Software, and to 60 | permit persons to whom the Software is furnished to do so, subject to 61 | the following conditions: 62 | 63 | The above copyright notice and this permission notice shall be 64 | included in all copies or substantial portions of the Software. 65 | 66 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 67 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 68 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 69 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 70 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 71 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 72 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.staticGzip = require("./lib/staticGzip.js"); 2 | exports.compress = require("./lib/compress.js"); -------------------------------------------------------------------------------- /lib/compress.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - compress 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var zlib = require('zlib'); 14 | 15 | /** 16 | * Supported content-encoding methods. 17 | */ 18 | 19 | exports.methods = { 20 | gzip: zlib.createGzip, 21 | deflate: zlib.createDeflate 22 | }; 23 | 24 | /** 25 | * Default filter function. 26 | */ 27 | 28 | exports.filter = function(req, res){ 29 | var type = res.getHeader('Content-Type') || ''; 30 | return type.match(/json|text|javascript/); 31 | }; 32 | 33 | /** 34 | * Compress response data with gzip/deflate. 35 | * 36 | * Filter: 37 | * 38 | * A `filter` callback function may be passed to 39 | * replace the default logic of: 40 | * 41 | * exports.filter = function(req, res){ 42 | * var type = res.getHeader('Content-Type') || ''; 43 | * return type.match(/json|text|javascript/); 44 | * }; 45 | * 46 | * Options: 47 | * 48 | * All remaining options are passed to the gzip/deflate 49 | * creation functions. Consult node's docs for additional details. 50 | * 51 | * - `chunkSize` (default: 16*1024) 52 | * - `windowBits` 53 | * - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression 54 | * - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more 55 | * - `strategy`: compression strategy 56 | * 57 | * @param {Object} options 58 | * @return {Function} 59 | * @api public 60 | */ 61 | 62 | module.exports = function compress(options) { 63 | var options = options || {} 64 | , names = Object.keys(exports.methods) 65 | , filter = options.filter || exports.filter; 66 | 67 | return function(req, res, next){ 68 | var accept = req.headers['accept-encoding'] 69 | , write = res.write 70 | , end = res.end 71 | , stream 72 | , method; 73 | 74 | // vary 75 | res.setHeader('Vary', 'Accept-Encoding'); 76 | 77 | // proxy 78 | 79 | res.write = function(chunk, encoding){ 80 | if (!this.headerSent) this._implicitHeader(); 81 | return stream 82 | ? stream.write(chunk, encoding) 83 | : write.call(res, chunk, encoding); 84 | }; 85 | 86 | res.end = function(chunk, encoding){ 87 | if (chunk) this.write(chunk, encoding); 88 | return stream 89 | ? stream.end() 90 | : end.call(res); 91 | }; 92 | 93 | res.on('header', function(){ 94 | // default request filter 95 | if (!filter(req, res)) return; 96 | 97 | // SHOULD use identity 98 | if (!accept) return; 99 | 100 | // head 101 | if ('HEAD' == req.method) return; 102 | 103 | // default to gzip 104 | if ('*' == accept.trim()) method = 'gzip'; 105 | 106 | // compression method 107 | if (!method) { 108 | for (var i = 0, len = names.length; i < len; ++i) { 109 | if (~accept.indexOf(names[i])) { 110 | method = names[i]; 111 | break; 112 | } 113 | } 114 | } 115 | 116 | // compression method 117 | if (!method) return; 118 | 119 | // compression stream 120 | stream = exports.methods[method](options); 121 | 122 | // header fields 123 | res.setHeader('Content-Encoding', method); 124 | res.removeHeader('Content-Length'); 125 | 126 | // compression 127 | 128 | stream.on('data', function(chunk){ 129 | write.call(res, chunk); 130 | }); 131 | 132 | stream.on('end', function(){ 133 | end.call(res); 134 | }); 135 | }); 136 | 137 | next(); 138 | }; 139 | } -------------------------------------------------------------------------------- /lib/staticGzip.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Tom Gallacher 3 | * 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var fs = require('fs'), 12 | parse = require('url').parse, 13 | path = require('path'), 14 | mime = require('mime'), 15 | zlib = require('zlib'), 16 | staticSend; 17 | try { 18 | staticSend = require('connect').static.send; 19 | } catch (e) { 20 | staticSend = require('express').static.send; 21 | } 22 | 23 | /** 24 | * Strip `Content-*` headers from `res`. 25 | * 26 | * @param {ServerResponse} res 27 | * @api public 28 | */ 29 | 30 | var removeContentHeaders = function(res){ 31 | Object.keys(res._headers).forEach(function(field){ 32 | if (0 === field.indexOf('content')) { 33 | res.removeHeader(field); 34 | } 35 | }); 36 | }; 37 | 38 | /** 39 | * gzipped cache. 40 | */ 41 | 42 | var gzippoCache = {}; 43 | 44 | /** 45 | * gzip file. 46 | */ 47 | 48 | var gzippo = function(filename, charset, callback) { 49 | 50 | fs.readFile(decodeURI(filename), function (err, data) { 51 | if (err) throw err; 52 | zlib.gzip(data, function(err, result) { 53 | callback(result); 54 | }); 55 | }); 56 | 57 | 58 | }; 59 | 60 | /** 61 | * By default gzip's static's that match the given regular expression /text|javascript|json/ 62 | * and then serves them with Connects static provider, denoted by the given `dirPath`. 63 | * 64 | * Options: 65 | * 66 | * - `maxAge` how long gzippo should cache gziped assets, defaulting to 1 day 67 | * - `clientMaxAge` client cache-control max-age directive, defaulting to 1 week 68 | * - `contentTypeMatch` - A regular expression tested against the Content-Type header to determine whether the response 69 | * should be gzipped or not. The default value is `/text|javascript|json/`. 70 | * - `prefix` - A url prefix. If you want all your static content in a root path such as /resource/. Any url paths not matching will be ignored 71 | * 72 | * Examples: 73 | * 74 | * connect.createServer( 75 | * connect.staticGzip(__dirname + '/public/'); 76 | * ); 77 | * 78 | * connect.createServer( 79 | * connect.staticGzip(__dirname + '/public/', {maxAge: 86400000}); 80 | * ); 81 | * 82 | * @param {String} path 83 | * @param {Object} options 84 | * @return {Function} 85 | * @api public 86 | */ 87 | 88 | exports = module.exports = function staticGzip(dirPath, options){ 89 | options = options || {}; 90 | var 91 | maxAge = options.maxAge || 86400000, 92 | contentTypeMatch = options.contentTypeMatch || /text|javascript|json/, 93 | clientMaxAge = options.clientMaxAge || 604800000, 94 | prefix = options.prefix || ''; 95 | 96 | if (!dirPath) throw new Error('You need to provide the directory to your static content.'); 97 | if (!contentTypeMatch.test) throw new Error('contentTypeMatch: must be a regular expression.'); 98 | 99 | dirPath = path.normalize(dirPath); 100 | 101 | return function staticGzip(req, res, next){ 102 | var url, filename, contentType, acceptEncoding, charset; 103 | 104 | function pass(name) { 105 | var o = Object.create(options); 106 | o.path = name; 107 | o.maxAge = clientMaxAge; 108 | staticSend(req, res, next, o); 109 | } 110 | 111 | function setHeaders(cacheObj) { 112 | res.setHeader('Content-Type', contentType); 113 | res.setHeader('Content-Encoding', 'gzip'); 114 | res.setHeader('Vary', 'Accept-Encoding'); 115 | res.setHeader('Content-Length', cacheObj.content.length); 116 | res.setHeader('Last-Modified', cacheObj.mtime.toUTCString()); 117 | res.setHeader('Date', new Date().toUTCString()); 118 | res.setHeader('Expires', new Date(Date.now() + clientMaxAge).toUTCString()); 119 | res.setHeader('Cache-Control', 'public, max-age=' + (clientMaxAge / 1000)); 120 | res.setHeader('ETag', '"' + cacheObj.content.length + '-' + Number(cacheObj.mtime) + '"'); 121 | } 122 | 123 | function sendGzipped(cacheObj) { 124 | setHeaders(cacheObj); 125 | res.end(cacheObj.content, 'binary'); 126 | } 127 | 128 | function gzipAndSend(filename, gzipName, mtime) { 129 | gzippo(filename, charset, function(gzippedData) { 130 | gzippoCache[gzipName] = { 131 | 'ctime': Date.now(), 132 | 'mtime': mtime, 133 | 'content': gzippedData 134 | }; 135 | sendGzipped(gzippoCache[gzipName]); 136 | }); 137 | } 138 | 139 | function forbidden(res) { 140 | var body = 'Forbidden'; 141 | res.setHeader('Content-Type', 'text/plain'); 142 | res.setHeader('Content-Length', body.length); 143 | res.statusCode = 403; 144 | res.end(body); 145 | }; 146 | 147 | if (req.method !== 'GET' && req.method !== 'HEAD') { 148 | return next(); 149 | } 150 | 151 | url = parse(req.url); 152 | 153 | // Allow a url path prefix 154 | if (url.pathname.substring(0, prefix.length) !== prefix) { 155 | return next(); 156 | } 157 | 158 | filename = path.normalize(path.join(dirPath, url.pathname.substring(prefix.length))); 159 | // malicious path 160 | if (0 != filename.indexOf(dirPath)){ 161 | return forbidden(res); 162 | } 163 | 164 | // directory index file support 165 | if (filename.substr(-1) === '/') filename += 'index.html'; 166 | 167 | contentType = mime.lookup(filename); 168 | charset = mime.charsets.lookup(contentType, 'UTF-8'); 169 | contentType = contentType + (charset ? '; charset=' + charset : ''); 170 | acceptEncoding = req.headers['accept-encoding'] || ''; 171 | 172 | //This is storing in memory for the moment, need to think what the best way to do this. 173 | //Check file is not a directory 174 | 175 | fs.stat(decodeURI(filename), function(err, stat) { 176 | if (err) { 177 | return pass(filename); 178 | } 179 | 180 | if (stat.isDirectory()) { 181 | return pass(req.url); 182 | } 183 | 184 | if (!contentTypeMatch.test(contentType)) { 185 | return pass(filename); 186 | } 187 | 188 | if (!~acceptEncoding.indexOf('gzip')) { 189 | return pass(filename); 190 | } 191 | 192 | var base = path.basename(filename), 193 | dir = path.dirname(filename), 194 | gzipName = path.join(dir, base + '.gz'); 195 | 196 | if (req.headers['if-modified-since'] && 197 | gzippoCache[gzipName] && 198 | +stat.mtime <= new Date(req.headers['if-modified-since']).getTime()) { 199 | setHeaders(gzippoCache[gzipName]); 200 | removeContentHeaders(res); 201 | res.statusCode = 304; 202 | return res.end(); 203 | } 204 | 205 | //TODO: check for pre-compressed file 206 | if (typeof gzippoCache[gzipName] === 'undefined') { 207 | gzipAndSend(filename, gzipName, stat.mtime); 208 | } else { 209 | if ((gzippoCache[gzipName].mtime < stat.mtime) || 210 | ((gzippoCache[gzipName].ctime + maxAge) < Date.now())) { 211 | gzipAndSend(filename, gzipName, stat.mtime); 212 | } else { 213 | sendGzipped(gzippoCache[gzipName]); 214 | } 215 | } 216 | }); 217 | }; 218 | }; 219 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gzippo", 3 | "version": "0.1.7", 4 | "author": "Tom Gallacher", 5 | "description": "Gzip middleware for Connect using the native zlib library in node >= 0.6", 6 | "homepage": "http://www.tomg.co/gzippo", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/tomgco/gzippo.git" 10 | }, 11 | "keywords": [ 12 | "compression", 13 | "gzip", 14 | "compress" 15 | ], 16 | "engines": { 17 | "node": ">= 0.5 < 0.9" 18 | }, 19 | "scripts": { 20 | "test": "expresso" 21 | }, 22 | "main": "./index.js", 23 | "dependencies": { 24 | "mime": ">= 1.2" 25 | }, 26 | "devDependencies": { 27 | "expresso": ">= 0.9", 28 | "should": ">= 0.3", 29 | "connect": "~1" 30 | }, 31 | "optionalDependencies": {} 32 | } 33 | -------------------------------------------------------------------------------- /test/fixtures/index_test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Index Test 4 | 5 |

6 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 7 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 8 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 9 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 10 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 11 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 12 |

13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/js/nestedTest.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomgco/gzippo/aad2871a1f6adeaa891bd7dd41339a5f4f8165f0/test/fixtures/js/nestedTest.js -------------------------------------------------------------------------------- /test/fixtures/space the final frontier/tomg.co.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomgco/gzippo/aad2871a1f6adeaa891bd7dd41339a5f4f8165f0/test/fixtures/space the final frontier/tomg.co.png -------------------------------------------------------------------------------- /test/fixtures/test.js: -------------------------------------------------------------------------------- 1 | alert("hello"); -------------------------------------------------------------------------------- /test/fixtures/test.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomgco/gzippo/aad2871a1f6adeaa891bd7dd41339a5f4f8165f0/test/fixtures/test.js.gzip -------------------------------------------------------------------------------- /test/fixtures/tomg.co.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomgco/gzippo/aad2871a1f6adeaa891bd7dd41339a5f4f8165f0/test/fixtures/tomg.co.png -------------------------------------------------------------------------------- /test/fixtures/user.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomgco/gzippo/aad2871a1f6adeaa891bd7dd41339a5f4f8165f0/test/fixtures/user.gzip -------------------------------------------------------------------------------- /test/fixtures/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tomgallacher", 3 | "website": "www.tomgallacher.info" 4 | } -------------------------------------------------------------------------------- /test/fixtures/utf8.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomgco/gzippo/aad2871a1f6adeaa891bd7dd41339a5f4f8165f0/test/fixtures/utf8.gz -------------------------------------------------------------------------------- /test/fixtures/utf8.txt: -------------------------------------------------------------------------------- 1 | English: The quick brown fox jumps over the lazy dog. 2 | Jamaican: Chruu, a kwik di kwik brong fox a jomp huova di liezi daag de, yu no siit? 3 | Irish: "An ḃfuil do ċroí ag bualaḋ ó ḟaitíos an ġrá a ṁeall lena ṗóg éada ó ṡlí do leasa ṫú?" "D'ḟuascail Íosa Úrṁac na hÓiġe Beannaiṫe pór Éava agus Áḋaiṁ." 4 | Dutch: Pa's wijze lynx bezag vroom het fikse aquaduct. 5 | German: Falsches Üben von Xylophonmusik quält jeden größeren Zwerg. (1) 6 | German: Im finſteren Jagdſchloß am offenen Felsquellwaſſer patzte der affig-flatterhafte kauzig-höf‌liche Bäcker über ſeinem verſifften kniffligen C-Xylophon. (2) 7 | Norwegian: Blåbærsyltetøy ("blueberry jam", includes every extra letter used in Norwegian). 8 | Swedish: Flygande bäckasiner söka strax hwila på mjuka tuvor. 9 | Icelandic: Sævör grét áðan því úlpan var ónýt. 10 | Finnish: (5) Törkylempijävongahdus (This is a perfect pangram, every letter appears only once. Translating it is an art on its own, but I'll say "rude lover's yelp". :-D) 11 | Finnish: (5) Albert osti fagotin ja töräytti puhkuvan melodian. (Albert bought a bassoon and hooted an impressive melody.) 12 | Finnish: (5) On sangen hauskaa, että polkupyörä on maanteiden jokapäiväinen ilmiö. (It's pleasantly amusing, that the bicycle is an everyday sight on the roads.) 13 | Polish: Pchnąć w tę łódź jeża lub osiem skrzyń fig. 14 | Czech: Příliš žluťoučký kůň úpěl ďábelské kódy. 15 | Slovak: Starý kôň na hŕbe kníh žuje tíško povädnuté ruže, na stĺpe sa ďateľ učí kvákať novú ódu o živote. 16 | Greek (monotonic): ξεσκεπάζω την ψυχοφθόρα βδελυγμία 17 | Greek (polytonic): ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία 18 | Russian: Съешь же ещё этих мягких французских булок да выпей чаю. 19 | Russian: В чащах юга жил-был цитрус? Да, но фальшивый экземпляр! ёъ. 20 | Bulgarian: Жълтата дюля беше щастлива, че пухът, който цъфна, замръзна като гьон. 21 | Sami (Northern): Vuol Ruoŧa geđggiid leat máŋga luosa ja čuovžža. 22 | Hungarian: Árvíztűrő tükörfúrógép. 23 | Spanish: El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro. 24 | Portuguese: O próximo vôo à noite sobre o Atlântico, põe freqüentemente o único médico. (3) 25 | French: Les naïfs ægithales hâtifs pondant à Noël où il gèle sont sûrs d'être déçus en voyant leurs drôles d'œufs abîmés. 26 | Esperanto: Eĥoŝanĝo ĉiuĵaŭde. 27 | Hebrew: זה כיף סתם לשמוע איך תנצח קרפד עץ טוב בגן. 28 | Japanese (Hiragana): 29 | いろはにほへど ちりぬるを 30 | わがよたれぞ つねならむ 31 | うゐのおくやま けふこえて 32 | あさきゆめみじ ゑひもせず (4) -------------------------------------------------------------------------------- /test/fixtures/utf8.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomgco/gzippo/aad2871a1f6adeaa891bd7dd41339a5f4f8165f0/test/fixtures/utf8.txt.gz -------------------------------------------------------------------------------- /test/prefexTest.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var staticProvider, 7 | assert = require('assert'), 8 | should = require('should'), 9 | http = require('http'), 10 | gzippo = require('../'); 11 | 12 | try { 13 | staticProvider = require('connect'); 14 | } catch (e) { 15 | staticProvider = require('express'); 16 | } 17 | 18 | /** 19 | * Path to ./test/fixtures/ 20 | */ 21 | 22 | var fixturesPath = __dirname + '/fixtures'; 23 | 24 | module.exports = { 25 | 'requesting without a prefix succeeds': function() { 26 | var app = staticProvider.createServer( 27 | gzippo.staticGzip(fixturesPath) 28 | ); 29 | 30 | assert.response(app, 31 | { 32 | url: '/user.json', 33 | headers: { 34 | 'Accept-Encoding':"gzip" 35 | } 36 | }, 37 | function(res){ 38 | var gzippedData = res.body; 39 | res.statusCode.should.equal(200); 40 | res.headers.should.have.property('content-type', 'application/json; charset=UTF-8'); 41 | res.headers.should.have.property('content-length', '69'); 42 | res.headers.should.have.property('content-encoding', 'gzip'); 43 | } 44 | ); 45 | }, 46 | 'requesting with a prefix succeeds': function() { 47 | var app = staticProvider.createServer( 48 | gzippo.staticGzip(fixturesPath, { prefix: '/resource' }) 49 | ); 50 | 51 | assert.response(app, 52 | { 53 | url: '/resource/user.json', 54 | headers: { 55 | 'Accept-Encoding':"gzip" 56 | } 57 | }, 58 | function(res){ 59 | var gzippedData = res.body; 60 | res.statusCode.should.equal(200); 61 | res.headers.should.have.property('content-type', 'application/json; charset=UTF-8'); 62 | res.headers.should.have.property('content-length', '69'); 63 | res.headers.should.have.property('content-encoding', 'gzip'); 64 | } 65 | ); 66 | }, 67 | 'requesting with a / prefix succeeds': function() { 68 | var app = staticProvider.createServer( 69 | gzippo.staticGzip(fixturesPath, { prefix: '/'}) 70 | ); 71 | 72 | assert.response(app, 73 | { 74 | url: '/user.json', 75 | headers: { 76 | 'Accept-Encoding':"gzip" 77 | } 78 | }, 79 | function(res){ 80 | var gzippedData = res.body; 81 | res.statusCode.should.equal(200); 82 | res.headers.should.have.property('content-type', 'application/json; charset=UTF-8'); 83 | res.headers.should.have.property('content-length', '69'); 84 | res.headers.should.have.property('content-encoding', 'gzip'); 85 | } 86 | ); 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /test/staticGzipTest.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var staticProvider, 7 | assert = require('assert'), 8 | should = require('should'), 9 | http = require('http'), 10 | gzippo = require('../'), 11 | crypto = require('crypto'), 12 | fs = require('fs'), 13 | shasum = crypto.createHash('sha1'); 14 | 15 | try { 16 | staticProvider = require('connect'); 17 | } catch (e) { 18 | staticProvider = require('express'); 19 | } 20 | 21 | /** 22 | * Path to ./test/fixtures/ 23 | */ 24 | 25 | var fixturesPath = __dirname + '/fixtures'; 26 | 27 | function getApp() { 28 | return staticProvider.createServer(gzippo.staticGzip(fixturesPath)); 29 | } 30 | 31 | module.exports = { 32 | 'requesting gzipped json file succeeds': function() { 33 | assert.response(getApp(), 34 | { 35 | url: '/user.json', 36 | headers: { 37 | 'Accept-Encoding':"gzip" 38 | } 39 | }, 40 | function(res){ 41 | var gzippedData = res.body; 42 | assert.response(getApp(), { url: '/user.gzip' }, function(res) { 43 | assert.equal(gzippedData, res.body, "Data is not gzipped"); 44 | }); 45 | res.statusCode.should.equal(200); 46 | res.headers.should.have.property('content-type', 'application/json; charset=UTF-8'); 47 | res.headers.should.have.property('content-length', '69'); 48 | res.headers.should.have.property('content-encoding', 'gzip'); 49 | } 50 | ); 51 | }, 52 | 'requesting gzipped js file succeeds': function() { 53 | assert.response(getApp(), 54 | { 55 | url: '/test.js', 56 | headers: { 57 | 'Accept-Encoding':"gzip" 58 | } 59 | }, 60 | function(res){ 61 | var gzippedData = res.body; 62 | assert.response(getApp(), { url: '/test.js.gzip' }, function(res) { 63 | assert.equal(gzippedData, res.body, "Data is not gzipped"); 64 | }); 65 | 66 | res.statusCode.should.equal(200); 67 | res.headers.should.have.property('content-type', 'application/javascript; charset=UTF-8'); 68 | res.headers.should.have.property('content-length', '35'); 69 | res.headers.should.have.property('content-encoding', 'gzip'); 70 | } 71 | ); 72 | }, 73 | 'requesting js file without gzip succeeds': function() { 74 | assert.response(getApp(), 75 | { 76 | url: '/test.js' 77 | }, 78 | function(res){ 79 | var gzippedData = res.body; 80 | 81 | fs.readFile(fixturesPath + '/test.js', function (err, data) { 82 | if (err) throw err; 83 | assert.equal(gzippedData, data, "Data returned does not match file data on filesystem"); 84 | }); 85 | 86 | res.statusCode.should.equal(200); 87 | res.headers.should.have.property('content-length', '15'); 88 | } 89 | ); 90 | }, 91 | 'requesting gzipped utf-8 file succeeds': function() { 92 | assert.response(getApp(), 93 | { 94 | url: '/utf8.txt', 95 | headers: { 96 | 'Accept-Encoding':"gzip" 97 | } 98 | }, 99 | function(res){ 100 | var gzippedData = res.body; 101 | assert.response(getApp(), { url: '/utf8.txt.gz' }, function(res) { 102 | assert.equal(gzippedData, res.body, "Data is not gzipped"); 103 | }); 104 | 105 | res.statusCode.should.equal(200); 106 | res.headers.should.have.property('content-type', 'text/plain; charset=UTF-8'); 107 | res.headers.should.have.property('content-length', '2031'); 108 | res.headers.should.have.property('content-encoding', 'gzip'); 109 | } 110 | ); 111 | }, 112 | 'requesting gzipped utf-8 file returns 304': function() { 113 | assert.response(getApp(), 114 | { 115 | url: '/utf8.txt', 116 | headers: { 117 | 'Accept-Encoding': "gzip" 118 | } 119 | }, 120 | function(res) { 121 | res.statusCode.should.equal(200); 122 | assert.response(getApp(), 123 | { 124 | url: '/utf8.txt', 125 | headers: { 126 | 'Accept-Encoding': "gzip", 127 | 'If-Modified-Since': res.headers['last-modified'] 128 | } 129 | }, 130 | function(res2) { 131 | res2.statusCode.should.equal(304); 132 | } 133 | ); 134 | } 135 | ); 136 | }, 137 | 'requesting gzipped utf-8 file returns 200': function() { 138 | assert.response(getApp(), 139 | { 140 | url: '/utf8.txt', 141 | headers: { 142 | 'Accept-Encoding': "gzip" 143 | } 144 | }, 145 | function(res) { 146 | res.statusCode.should.equal(200); 147 | } 148 | ); 149 | }, 150 | 'requesting directory returns index.html if found': function() { 151 | assert.response(getApp(), 152 | { 153 | url: '/index_test/', 154 | headers: { 155 | 'Accept-Encoding': "gzip" 156 | } 157 | }, 158 | function(res) { 159 | res.statusCode.should.equal(200); 160 | res.headers.should.have.property('content-length', '366'); 161 | } 162 | ); 163 | }, 164 | 'ensuring max age is set on resources which are passed to the default static content provider': function() { 165 | assert.response(getApp(), 166 | { 167 | url: '/tomg.co.png' 168 | }, 169 | function(res) { 170 | assert.includes(res.headers['cache-control'], 'max-age=60480'); 171 | } 172 | ); 173 | }, 174 | 'Normal traversal should work': function() { 175 | assert.response(getApp(), 176 | { 177 | url: '/nom/../tomg.co.png' 178 | }, 179 | function(res) { 180 | res.statusCode.should.equal(200); 181 | } 182 | ); 183 | }, 184 | 'Ensuring that when viewing a directory a redirect works correctly': function() { 185 | assert.response(getApp(), 186 | { 187 | url: '/js' 188 | }, 189 | function(res) { 190 | res.statusCode.should.not.equal(301); 191 | } 192 | ); 193 | }, 194 | 'ensuring that gzippo works with a space in a static content path': function() { 195 | assert.response(getApp(), 196 | { 197 | url: '/space%20the%20final%20frontier/tomg.co.png' 198 | }, 199 | function(res) { 200 | res.statusCode.should.not.equal(404); 201 | } 202 | ); 203 | }, 204 | 'Ensuring req.url isnt passed to staticSend on error': function() { 205 | assert.response(getApp(), 206 | { 207 | url: '/etc/passwd' 208 | }, 209 | function(res) { 210 | res.statusCode.should.equal(404); 211 | } 212 | ); 213 | }, 214 | 'Ensuring you cannot traverse up the directory tree': function() { 215 | assert.response(getApp(), 216 | { 217 | url: '/../prefexTest.js' 218 | }, 219 | function(res) { 220 | res.statusCode.should.equal(403); 221 | } 222 | ); 223 | }, 224 | 'Ensuring you cannot traverse up the directory tree (urlencoded)': function() { 225 | assert.response(getApp(), 226 | { 227 | url: '/%2e%2e/prefexTest.js' 228 | }, 229 | function(res) { 230 | res.statusCode.should.equal(403); 231 | } 232 | ); 233 | } 234 | }; 235 | --------------------------------------------------------------------------------