Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for transform option. #82

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ 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` option is set.

##### extensions

Expand All @@ -70,8 +72,10 @@ in preferred order.

##### lastModified

Enable or disable `Last-Modified` header, defaults to true. Uses the file
system's last modified value.
Enable or disable `Last-Modified` header. Uses the file system's last modified
value.

Defaults to `true`, unless the `transform` option is set.

##### maxAge

Expand All @@ -83,6 +87,30 @@ 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(...)
}
```

When a transform is specified, the `lastModified` and `etag` options default 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:
Expand Down
18 changes: 15 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,13 @@ function SendStream(req, path, options) {
this.path = path
this.req = req

this._transform = typeof opts.transform === 'function'
? options.transform
: undefined

this._etag = opts.etag !== undefined
? Boolean(opts.etag)
: true
: (this._transform !== undefined ? false : true)

this._dotfiles = opts.dotfiles !== undefined
? opts.dotfiles
Expand Down Expand Up @@ -120,7 +124,7 @@ function SendStream(req, path, options) {

this._lastModified = opts.lastModified !== undefined
? Boolean(opts.lastModified)
: true
: (this._transform !== undefined ? false : true)

this._maxage = opts.maxAge || opts.maxage
this._maxage = typeof this._maxage === 'string'
Expand Down Expand Up @@ -592,7 +596,12 @@ SendStream.prototype.send = function(path, stat){
opts.end = Math.max(offset, offset + len - 1)

// 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();
Expand Down Expand Up @@ -691,6 +700,9 @@ 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
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"after": "0.8.1",
"istanbul": "0.3.9",
"mocha": "2.2.5",
"readable-stream": "2.0.2",
"supertest": "1.0.1"
},
"files": [
Expand Down
62 changes: 61 additions & 1 deletion test/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ var fs = require('fs');
var http = require('http');
var path = require('path');
var request = require('supertest');
var send = require('..')
var send = require('..');
var Transform = require('readable-stream/transform');

// test server

Expand Down Expand Up @@ -1172,6 +1173,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){
Expand Down Expand Up @@ -1295,3 +1340,18 @@ function shouldNotHaveHeader(header) {
assert.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header)
}
}

// Trivial replaceStream implementation
function replaceStream(search, replace) {
var data = ''
var transform = new Transform()
transform._transform = function (buf, encoding, cb) {
data += buf
cb()
}
transform._flush = function (cb) {
this.push(data.replace(search, replace))
cb()
}
return transform
}