diff --git a/README.md b/README.md index c21b3df..8a5092a 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ with a dot, for backward-compatibility. ##### etag -Enable or disable etag generation, defaults to true. +Enable or disable etag generation, defaults to `true` unless the transform options is set. ##### extensions @@ -70,9 +70,11 @@ in preferred order. ##### lastModified -Enable or disable `Last-Modified` header, defaults to true. Uses the file +Enable or disable `Last-Modified` header, defaults to `true`. Uses the file system's last modified value. +If the `transform` option is set then the default is `false`. + ##### maxAge Provide a max-age in milliseconds for http caching, defaults to 0. @@ -83,6 +85,26 @@ This can also be a string accepted by the Serve files relative to `path`. +##### Transform + +A function that consumes the file stream and produces a new (transformed) stream: +```javascript +function(stream) {return stream.pipe(replaceStream('tobi', 'peter'))} +``` + +Multiple transformations are possible: +```javascript +function(stream) { + return stream + .pipe(replaceStream('tobi', 'peter')) + .pipe(replaceStream('peter', 'hans')) + .pipe(...) +} +``` + +With transform the last-modified and etag defaults to `false` but can be overridden when +a transform on the file's stream is expected to always generate the same result. + ### Events The `SendStream` is an event emitter and will emit the following events: diff --git a/index.js b/index.js index f63081d..c5cb732 100644 --- a/index.js +++ b/index.js @@ -81,9 +81,13 @@ function SendStream(req, path, options) { this.path = path; this.options = options; + this._transform = typeof options.transform === 'function' + ? options.transform + : undefined + this._etag = options.etag !== undefined ? Boolean(options.etag) - : true + : (this._transform !== undefined ? false : true) this._dotfiles = options.dotfiles !== undefined ? options.dotfiles @@ -114,7 +118,7 @@ function SendStream(req, path, options) { this._lastModified = options.lastModified !== undefined ? Boolean(options.lastModified) - : true + : (this._transform !== undefined ? false : true) this._maxage = options.maxAge || options.maxage this._maxage = typeof this._maxage === 'string' @@ -553,7 +557,12 @@ SendStream.prototype.send = function(path, stat){ } // content-length - res.setHeader('Content-Length', len); + if(this._transform === undefined){ + res.setHeader('Content-Length', len); + }else{ + //we don't know the content-length of the transformed data beforehand + res.setHeader('Transfer-Encoding', 'chunked'); + } // HEAD support if ('HEAD' == req.method) return res.end(); @@ -651,9 +660,15 @@ SendStream.prototype.stream = function(path, options){ // pipe var stream = fs.createReadStream(path, options); + this.emit('stream', stream); + + if(this._transform !== undefined){ + stream = this._transform(stream); + } + stream.pipe(res); - + // response finished, done with the fd onFinished(res, function onfinished(){ finished = true; diff --git a/package.json b/package.json index e863a55..efda1e4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "after": "0.8.1", "istanbul": "0.3.5", "mocha": "~2.1.0", - "supertest": "~0.15.0" + "supertest": "~0.15.0", + "replacestream": "~1.0.2" }, "files": [ "HISTORY.md", diff --git a/test/send.js b/test/send.js index 0ed4682..119192f 100644 --- a/test/send.js +++ b/test/send.js @@ -8,6 +8,7 @@ var http = require('http'); var path = require('path'); var request = require('supertest'); var send = require('..') +var replaceStream = require('replacestream') // test server @@ -1103,6 +1104,50 @@ describe('send(file, options)', function(){ }) }) + describe('transform', function(){ + it('should transform the file contents', function(done){ + var app = http.createServer(function(req, res){ + send(req, 'test/fixtures/name.txt', {transform: function(stream) {return stream.pipe(replaceStream('tobi', 'peter'))}}) + .pipe(res) + }); + + request(app) + .get('/name.txt') + .expect(shouldNotHaveHeader('Last-Modified')) + .expect(shouldNotHaveHeader('ETag')) + .expect(200, "peter", done) + }) + + it('should be possible to do mulitple transformations', function(done){ + var transformFunc = function(stream) { + return stream + .pipe(replaceStream('tobi', 'peter')) + .pipe(replaceStream('peter', 'hans')) + } + + var app = http.createServer(function(req, res){ + send(req, 'test/fixtures/name.txt', {transform: transformFunc}) + .pipe(res) + }); + + request(app) + .get('/name.txt') + .expect(200, "hans", done) + }) + + it('should be able to override last modified', function(done){ + var app = http.createServer(function(req, res){ + send(req, 'test/fixtures/name.txt', {lastModified: true, transform: function(stream) {return stream.pipe(replaceStream('tobi', 'peter'))}}) + .pipe(res) + }); + + request(app) + .get('/name.txt') + .expect('last-modified', dateRegExp) + .expect(200, "peter", done) + }) + }) + describe('root', function(){ describe('when given', function(){ it('should join root', function(done){