Skip to content

Commit

Permalink
Merge 7ae38be into de073ed
Browse files Browse the repository at this point in the history
  • Loading branch information
wesleytodd committed Jun 12, 2020
2 parents de073ed + 7ae38be commit b24a9e3
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 9 deletions.
19 changes: 12 additions & 7 deletions HISTORY.md
@@ -1,3 +1,8 @@
Unreleased
===================

* Add `transform` option

0.17.1 / 2019-05-10
===================

Expand Down Expand Up @@ -455,37 +460,37 @@

* update range-parser and fresh

0.1.4 / 2013-08-11
0.1.4 / 2013-08-11
==================

* update fresh

0.1.3 / 2013-07-08
0.1.3 / 2013-07-08
==================

* Revert "Fix fd leak"

0.1.2 / 2013-07-03
0.1.2 / 2013-07-03
==================

* Fix fd leak

0.1.0 / 2012-08-25
0.1.0 / 2012-08-25
==================

* add options parameter to send() that is passed to fs.createReadStream() [kanongil]

0.0.4 / 2012-08-16
0.0.4 / 2012-08-16
==================

* allow custom "Accept-Ranges" definition

0.0.3 / 2012-07-16
0.0.3 / 2012-07-16
==================

* fix normalization of the root directory. Closes #3

0.0.2 / 2012-07-09
0.0.2 / 2012-07-09
==================

* add passing of req explicitly for now (YUCK)
Expand Down
53 changes: 53 additions & 0 deletions README.md
Expand Up @@ -78,6 +78,8 @@ the 4th byte in the stream.

Enable or disable etag generation, defaults to true.

This will default to `false` if the [`transform` option is passed](#transform).

##### extensions

If a given file doesn't exist, try appending one of the given extensions,
Expand All @@ -104,6 +106,8 @@ in preferred order.
Enable or disable `Last-Modified` header, defaults to true. Uses the file
system's last modified value.

This will default to `false` if the [`transform` option is passed](#transform).

##### maxAge

Provide a max-age in milliseconds for http caching, defaults to 0.
Expand All @@ -119,6 +123,55 @@ Serve files relative to `path`.
Byte offset at which the stream starts, defaults to 0. The start is inclusive,
meaning `start: 2` will include the 3rd byte in the stream.

##### transform

A function which returns a stream which can be used to transform the data.
If this option is passed it will change the defaults of `etag` and `lastModified`
to `false`. If the transform stream changes the content length, it is also
responsible for updating the `Content-Length` header or changing to
`Transfer-Encoding: chunked`.

The transform function is given the readable stream from `fs.createReadStream`,
the `response` object and, the `options` passed to `send()`. It must return
a `ReadableStream`, you can even just return the `stream` passed to you.

If you expect the transform to always return the same results, you can re-enable
`etag` and `lastModified` and to use the underlying file as it normally would.

*Note on range requests:* When a range request is sent, the read stream will
only read part of the file. If your transform is preforming replacements, or
otherwise relying on always having complete content it might not work. You
can set `acceptRanges: false` to disallow range requests or you will have
to ensure that your transform logic can handle partial data. For an example
of a transform which can work on range requests, see the test labled
`should transform range requests`.

Example:

```javascript
var http = require('http')
var Readable = require('stream').Readable
var concatStream = require('concat-stream')
var send = require('send')

http.createServer(function (req, res) {
send(req, req.url, {
transform: function (stream, res, opts) {
var readStream = new Readable({
read: function () { }
})
stream.pipe(concatStream(function (d) {
var _d = Buffer.from(d.toString('utf8').replace('tobi', 'not tobi'))
res.setHeader('Content-Length', _d.length)
readStream.push(_d)
readStream.push(null)
}))
return readStream
}
}).pipe(res)
})
```

#### Events

The `SendStream` is an event emitter and will emit the following events:
Expand Down
16 changes: 14 additions & 2 deletions index.js
Expand Up @@ -102,6 +102,10 @@ function SendStream (req, path, options) {
this.path = path
this.req = req

this._transform = opts.transform !== undefined
? opts.transform
: undefined

this._acceptRanges = opts.acceptRanges !== undefined
? Boolean(opts.acceptRanges)
: true
Expand All @@ -112,7 +116,7 @@ function SendStream (req, path, options) {

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

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

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

this._maxage = opts.maxAge || opts.maxage
this._maxage = typeof this._maxage === 'string'
Expand Down Expand Up @@ -794,6 +798,14 @@ SendStream.prototype.stream = function stream (path, options) {

// pipe
var stream = fs.createReadStream(path, options)
if (this._transform) {
// When using a transform stream, the implementor is
// responsible for setting content length or any other
// headers for their stream type.
res.removeHeader('Content-Length')

stream = this._transform(stream, res, options)
}
this.emit('stream', stream)
stream.pipe(res)

Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -32,6 +32,7 @@
},
"devDependencies": {
"after": "0.8.2",
"concat-stream": "1.6.2",
"eslint": "5.16.0",
"eslint-config-standard": "12.0.0",
"eslint-plugin-import": "2.17.2",
Expand Down
55 changes: 55 additions & 0 deletions test/send.js
Expand Up @@ -6,7 +6,9 @@ var assert = require('assert')
var fs = require('fs')
var http = require('http')
var path = require('path')
var Readable = require('stream').Readable
var request = require('supertest')
var concatStream = require('concat-stream')
var send = require('..')

// test server
Expand Down Expand Up @@ -916,6 +918,59 @@ describe('send(file).pipe(res)', function () {
.expect(200, 'tobi', done)
})
})

describe('transforming file stream', function () {
it('should transform a file', function (done) {
var app = http.createServer(function (req, res) {
send(req, req.url, {
root: fixtures,
transform: function (stream, res, opts) {
var readStream = new Readable({
read: function () { }
})
stream.pipe(concatStream(function (d) {
var _d = typeof Buffer.from === 'function' ?
Buffer.from(d.toString('utf8').replace('tobi', 'not tobi'))
: new Buffer(d.toString('utf8').replace('tobi', 'not tobi'), 'utf8')
res.setHeader('Content-Length', _d.length)
readStream.push(_d)
readStream.push(null)
}))
return readStream
}
}).pipe(res)
})
request(app)
.get('/name.txt')
.expect(shouldNotHaveHeader('Last-Modified'))
.expect(shouldNotHaveHeader('ETag'))
.expect(200, 'not tobi', done)
})

it('should transform range requests', function (done) {
request(createServer({
root: fixtures,
transform: function (stream, res, opts) {
var readStream = new Readable({
read: function () { }
})
stream
.on('data', function (d) {
readStream.push(d.toString('utf8').split('').map(function (i) {
return ['a', 'b', 'c', 'd', 'e'][parseInt(i, 10) - 1]
}).join(''))
})
.on('end', function (d) {
readStream.push(null)
})
return readStream
}
}))
.get('/nums.txt')
.set('Range', 'bytes=0-4')
.expect(206, 'abcde', done)
})
})
})

describe('send(file, options)', function () {
Expand Down

0 comments on commit b24a9e3

Please sign in to comment.