Skip to content

Commit

Permalink
Adding static path prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Serby committed Sep 18, 2011
1 parent cbdb1df commit 53ae9f0
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 29 deletions.
9 changes: 5 additions & 4 deletions README.md
Expand Up @@ -5,15 +5,15 @@ gzippo pronounced `g-zippo` is a gzip middleware for Connect / expressjs using n
gzippo currently only supports only gzipping static content files however a release is in progress to introduce streaming support.

## Installation

$ npm install gzippo

### Usage

In your express/connect server setup, use as follows:

var gzippo = require('gzippo');

//Replace the static provider with gzippo's
//app.use(express.static(__dirname + '/public'));
app.use(gzippo.staticGzip(__dirname + '/public'));
Expand All @@ -23,10 +23,11 @@ Options:
- `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/`.
- `maxAge` - cache-control max-age directive, defaulting to 1 day
- `clientMaxAge` - browser cache-control max-age directive, defaulting to 1 week
- `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

Currently the gzipped version is created and stored in memory. This is not final and was done to get a working version
up and about. A version which will gzip text/html after res.render() / res.end() is in progress.

[node-compress](https://github.com/waveto/node-compress) gzip library is used for gzipping.

Found gzippo helpful? Why don't you [tip us](http://tiptheweb.org/tip/?link=https%3A%2F%2Fgithub.com%2Ftomgallacher%2Fgzippo&title=Tip%20to%20Support%20gzippo) [![Flattr Button](http://api.flattr.com/button/flattr-badge-large.png "Flattr This!")](https://flattr.com/thing/282348/gzippo-node-js-gzip-module "gzippo - node.js gzip module")
Expand Down
59 changes: 34 additions & 25 deletions lib/staticGzip.js
@@ -1,6 +1,6 @@
/*!
* Tom Gallacher
*
*
* MIT Licensed
*/

Expand Down Expand Up @@ -44,7 +44,7 @@ var gzippoCache = {};
/**
* gzip file.
*/

var gzippo = function(filename, charset, callback) {
var gzip = new compress.Gzip();
gzip.init();
Expand All @@ -63,8 +63,9 @@ var gzippo = function(filename, charset, callback) {
*
* - `maxAge` cache-control max-age directive, defaulting to 1 day
* - `clientMaxAge` client cache-control max-age directive, defaulting to 1 week
* - `contentTypeMatch` - A regular expression tested against the Content-Type header to determine whether the response
* - `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/`.
* - `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
*
* Examples:
*
Expand All @@ -84,40 +85,42 @@ var gzippo = function(filename, charset, callback) {

exports = module.exports = function staticGzip(dirPath, options){
options = options || {};
var maxAge = options.maxAge || 86400000,
contentTypeMatch = options.contentTypeMatch || /text|javascript|json/,
clientMaxAge = options.clientMaxAge || 604800000;

var
maxAge = options.maxAge || 86400000,
contentTypeMatch = options.contentTypeMatch || /text|javascript|json/,
clientMaxAge = options.clientMaxAge || 604800000,
prefix = options.prefix || '';

if (!dirPath) throw new Error('You need to provide the directory to your static content.');
if (!contentTypeMatch.test) throw new Error('contentTypeMatch: must be a regular expression.');

return function staticGzip(req, res, next){
var url, filename, contentType, acceptEncoding, charset;

function pass(name) {
var o = Object.create(options);
o.path = name;
o.maxAge = maxAge;
staticSend(req, res, next, o);
}

function setHeaders(cacheObj) {
res.setHeader('Content-Type', contentType);
res.setHeader('Content-Encoding', 'gzip');
res.setHeader('Vary', 'Accept-Encoding');
res.setHeader('Vary', 'Accept-Encoding');
res.setHeader('Content-Length', cacheObj.content.length);
res.setHeader('Last-Modified', cacheObj.mtime.toUTCString());
res.setHeader('Date', new Date().toUTCString());
res.setHeader('Expires', new Date(Date.now() + clientMaxAge).toUTCString());
res.setHeader('Cache-Control', 'public, max-age=' + (clientMaxAge / 1000));
res.setHeader('ETag', '"' + cacheObj.content.length + '-' + Number(cacheObj.mtime) + '"');
}

function sendGzipped(cacheObj) {
setHeaders(cacheObj);
res.end(cacheObj.content, 'binary');
}

function gzipAndSend(filename, gzipName, mtime) {
gzippo(filename, charset, function(gzippedData) {
gzippoCache[gzipName] = {
Expand All @@ -126,17 +129,23 @@ exports = module.exports = function staticGzip(dirPath, options){
'content': gzippedData
};
sendGzipped(gzippoCache[gzipName]);
});
});
}


if (req.method !== 'GET') {
return next();
}

url = parse(req.url);
filename = path.join(dirPath, url.pathname);


// Allow a url path prefix
if (url.pathname.substring(0, prefix.length) !== prefix) {
return next();
}

filename = path.join(dirPath, url.pathname.substring(prefix.length));

contentType = mime.lookup(filename);
charset = mime.charsets.lookup(contentType);
contentType = contentType + (charset ? '; charset=' + charset : '');
Expand All @@ -145,23 +154,23 @@ exports = module.exports = function staticGzip(dirPath, options){
if (!contentTypeMatch.test(contentType)) {
return pass(filename);
}

if (!~acceptEncoding.indexOf('gzip')) {
return pass(filename);
}

//This is storing in memory for the moment, need to think what the best way to do this.
//Check file is not a directory

fs.stat(filename, function(err, stat) {
if (err || stat.isDirectory()) {
return pass(filename);
return pass(filename);
}

var base = path.basename(filename),
dir = path.dirname(filename),
gzipName = path.join(dir, base + '.gz');

if (req.headers['if-modified-since'] &&
gzippoCache[gzipName] &&
+gzippoCache[gzipName].mtime <= new Date(req.headers['if-modified-since']).getTime()) {
Expand All @@ -170,12 +179,12 @@ exports = module.exports = function staticGzip(dirPath, options){
res.statusCode = 304;
return res.end();
}
//TODO: check for pre-compressed file

//TODO: check for pre-compressed file
if (typeof gzippoCache[gzipName] === 'undefined') {
gzipAndSend(filename, gzipName, stat.mtime);
} else {
if ((gzippoCache[gzipName].mtime < stat.mtime) ||
if ((gzippoCache[gzipName].mtime < stat.mtime) ||
((gzippoCache[gzipName].ctime + maxAge) < Date.now())) {
gzipAndSend(filename, gzipName, stat.mtime);
} else {
Expand Down
88 changes: 88 additions & 0 deletions test/prefexTest.js
@@ -0,0 +1,88 @@

/**
* Module dependencies.
*/

var staticProvider,
assert = require('assert'),
should = require('should'),
http = require('http'),
gzippo = require('../');

try {
staticProvider = require('connect');
} catch (e) {
staticProvider = require('express');
}

/**
* Path to ./test/fixtures/
*/

var fixturesPath = __dirname + '/fixtures';

module.exports = {
'requesting without a prefix succeeds': function() {
var app = staticProvider.createServer(
gzippo.staticGzip(fixturesPath)
);

assert.response(app,
{
url: '/user.json',
headers: {
'Accept-Encoding':"gzip"
}
},
function(res){
var gzippedData = res.body;
res.statusCode.should.equal(200);
res.headers.should.have.property('content-type', 'application/json');
res.headers.should.have.property('content-length', '69');
res.headers.should.have.property('content-encoding', 'gzip');
}
);
},
'requesting with a prefix succeeds': function() {
var app = staticProvider.createServer(
gzippo.staticGzip(fixturesPath, { prefix: '/resource' })
);

assert.response(app,
{
url: '/resource/user.json',
headers: {
'Accept-Encoding':"gzip"
}
},
function(res){
var gzippedData = res.body;
res.statusCode.should.equal(200);
res.headers.should.have.property('content-type', 'application/json');
res.headers.should.have.property('content-length', '69');
res.headers.should.have.property('content-encoding', 'gzip');
}
);
},
'requesting with a / prefix succeeds': function() {
var app = staticProvider.createServer(
gzippo.staticGzip(fixturesPath, { prefix: '/'})
);

assert.response(app,
{
url: '/user.json',
headers: {
'Accept-Encoding':"gzip"
}
},
function(res){
var gzippedData = res.body;
res.statusCode.should.equal(200);
res.headers.should.have.property('content-type', 'application/json');
res.headers.should.have.property('content-length', '69');
res.headers.should.have.property('content-encoding', 'gzip');
}
);
}
};

0 comments on commit 53ae9f0

Please sign in to comment.