diff --git a/README.md b/README.md index 70eec00..6a9f6c9 100644 --- a/README.md +++ b/README.md @@ -145,10 +145,12 @@ With this method, you don't have to explicitly send the response back, in case o Sets the `Cache-Control` header. -example: `{ cache: 7200 }` +example: `{ cache: 7200 }` will set the max-age for all files to 7200 seconds +example: `{ cache: {'**/*.css': 300}}` will set the max-age for all CSS files to 5 minutes. Passing a number will set the cache duration to that number of seconds. Passing `false` will disable the `Cache-Control` header. +Passing a object with [minimatch glob pattern](https://github.com/isaacs/minimatch) keys and number values will set cache max-age for any matching paths. > Defaults to `3600` diff --git a/lib/node-static.js b/lib/node-static.js index 22984e2..3c95040 100644 --- a/lib/node-static.js +++ b/lib/node-static.js @@ -1,11 +1,12 @@ -var fs = require('fs') - , events = require('events') - , buffer = require('buffer') - , http = require('http') - , url = require('url') - , path = require('path') - , mime = require('mime') - , util = require('./node-static/util'); +var fs = require('fs') + , events = require('events') + , buffer = require('buffer') + , http = require('http') + , url = require('url') + , path = require('path') + , mime = require('mime') + , util = require('./node-static/util') + , minimatch = require('minimatch'); // Current version var version = [0, 7, 7]; @@ -16,7 +17,7 @@ var Server = function (root, options) { // resolve() doesn't normalize (to lowercase) drive letters on Windows this.root = path.normalize(path.resolve(root || '.')); this.options = options || {}; - this.cache = 3600; + this.cache = {'**': 3600}; this.defaultHeaders = {}; this.options.headers = this.options.headers || {}; @@ -25,9 +26,11 @@ var Server = function (root, options) { if ('cache' in this.options) { if (typeof(this.options.cache) === 'number') { + this.cache = {'**': this.options.cache}; + } else if (typeof(this.options.cache) === 'object') { this.cache = this.options.cache; } else if (! this.options.cache) { - this.cache = false; + this.cache = {}; } } @@ -39,10 +42,6 @@ var Server = function (root, options) { this.defaultHeaders['server'] = this.serverInfo; - if (this.cache !== false) { - this.defaultHeaders['cache-control'] = 'max-age=' + this.cache; - } - for (var k in this.defaultHeaders) { this.options.headers[k] = this.options.headers[k] || this.defaultHeaders[k]; @@ -347,6 +346,7 @@ Server.prototype.respond = function (pathname, status, _headers, files, stat, re var contentType = _headers['Content-Type'] || mime.lookup(files[0]) || 'application/octet-stream'; + _headers = this.setCacheHeaders(_headers, req); if(this.options.gzip) { this.respondGzip(pathname, status, contentType, _headers, files, stat, req, res, finish); @@ -388,6 +388,25 @@ Server.prototype.stream = function (pathname, files, length, startByte, res, cal })(files.slice(0), 0); }; +Server.prototype.setCacheHeaders = function(_headers, req) { + var maxAge = this.getMaxAge(req.url); + if (typeof(maxAge) === 'number') { + _headers['cache-control'] = 'max-age=' + maxAge; + } + return _headers; +}; + +Server.prototype.getMaxAge = function(requestUrl) { + if (this.cache) { + for(var pattern in this.cache) { + if (minimatch(requestUrl, pattern)) { + return this.cache[pattern]; + } + } + } + return false; +}; + // Exports exports.Server = Server; exports.version = version; diff --git a/package.json b/package.json index 047fb48..5b44054 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,10 @@ }, "license": "MIT", "dependencies": { - "optimist": ">=0.3.4", "colors": ">=0.6.0", - "mime": ">=1.2.9" + "mime": ">=1.2.9", + "minimatch": "^3.0.3", + "optimist": ">=0.3.4" }, "devDependencies": { "request": "latest", diff --git a/test/integration/node-static-test.js b/test/integration/node-static-test.js index 14c591d..6f81d23 100644 --- a/test/integration/node-static-test.js +++ b/test/integration/node-static-test.js @@ -229,6 +229,9 @@ suite.addBatch({ }, 'should respond with text/html': function(error, response, body){ assert.equal(response.headers['content-type'], 'text/html'); + }, + 'should respond with cache-control': function(error, response, body){ + assert.equal(response.headers['cache-control'], 'max-age=3600'); } } }).addBatch({ @@ -386,5 +389,63 @@ suite.addBatch({ assert.equal(body, 'hello world'); } } +}).addBatch({ + 'once an http server is listening with custom cache configuration': { + topic: function () { + server.close(); + + fileServer = new static.Server(__dirname + '/../fixtures', { + cache: { + '**/*.txt': 100, + '**/': 300 + } + }); + + server = require('http').createServer(function (request, response) { + fileServer.serve(request, response); + }).listen(TEST_PORT, this.callback) + }, + 'should be listening' : function(){ + /* This test is necessary to ensure the topic execution. + * A topic without tests will be not executed */ + assert.isTrue(true); + } + } +}).addBatch({ + 'requesting custom cache index file': { + topic : function(){ + request.get(TEST_SERVER + '/', this.callback); + }, + 'should respond with 200' : function(error, response, body){ + assert.equal(response.statusCode, 200); + }, + 'should respond with cache-control': function(error, response, body){ + assert.equal(response.headers['cache-control'], 'max-age=300'); + } + } +}).addBatch({ + 'requesting custom cache text file': { + topic : function(){ + request.get(TEST_SERVER + '/hello.txt', this.callback); + }, + 'should respond with 200' : function(error, response, body){ + assert.equal(response.statusCode, 200); + }, + 'should respond with cache-control': function(error, response, body){ + assert.equal(response.headers['cache-control'], 'max-age=100'); + } + } +}).addBatch({ + 'requesting custom cache un-cached file': { + topic : function(){ + request.get(TEST_SERVER + '/empty.css', this.callback); + }, + 'should respond with 200' : function(error, response, body){ + assert.equal(response.statusCode, 200); + }, + 'should not respond with cache-control': function(error, response, body){ + assert.equal(response.headers['cache-control'], undefined); + } + } }).export(module);