mirror of
https://github.com/zebrajr/express.git
synced 2026-01-15 12:15:27 +00:00
@@ -1,6 +1,9 @@
|
||||
unreleased
|
||||
==========
|
||||
|
||||
* add `res.sendFile`
|
||||
- accepts a file system path instead of a URL
|
||||
- requires an absolute path or `root` option specified
|
||||
* deps: qs@1.0.2
|
||||
- Complete rewrite
|
||||
- Limits array length to 20
|
||||
|
||||
118
lib/response.js
118
lib/response.js
@@ -5,6 +5,7 @@
|
||||
var deprecate = require('depd')('express');
|
||||
var escapeHtml = require('escape-html');
|
||||
var http = require('http');
|
||||
var isAbsolute = require('./utils').isAbsolute;
|
||||
var path = require('path');
|
||||
var mixin = require('utils-merge');
|
||||
var sign = require('cookie-signature').sign;
|
||||
@@ -301,6 +302,123 @@ res.jsonp = function jsonp(obj) {
|
||||
return this.send(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transfer the file at the given `path`.
|
||||
*
|
||||
* Automatically sets the _Content-Type_ response header field.
|
||||
* The callback `fn(err)` is invoked when the transfer is complete
|
||||
* or when an error occurs. Be sure to check `res.sentHeader`
|
||||
* if you wish to attempt responding, as the header and some data
|
||||
* may have already been transferred.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
|
||||
* - `root` root directory for relative filenames
|
||||
* - `headers` object of headers to serve with file
|
||||
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
|
||||
*
|
||||
* Other options are passed along to `send`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* The following example illustrates how `res.sendFile()` may
|
||||
* be used as an alternative for the `static()` middleware for
|
||||
* dynamic situations. The code backing `res.sendFile()` is actually
|
||||
* the same code, so HTTP cache support etc is identical.
|
||||
*
|
||||
* app.get('/user/:uid/photos/:file', function(req, res){
|
||||
* var uid = req.params.uid
|
||||
* , file = req.params.file;
|
||||
*
|
||||
* req.user.mayViewFilesFrom(uid, function(yes){
|
||||
* if (yes) {
|
||||
* res.sendFile('/uploads/' + uid + '/' + file);
|
||||
* } else {
|
||||
* res.send(403, 'Sorry! you cant see that.');
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.sendFile = function sendFile(path, options, fn) {
|
||||
var done;
|
||||
var req = this.req;
|
||||
var next = req.next;
|
||||
|
||||
if (!path) {
|
||||
throw new TypeError('path argument is required to res.sendFile');
|
||||
}
|
||||
|
||||
// support function as second arg
|
||||
if (typeof options === 'function') {
|
||||
fn = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (!options.root && !isAbsolute(path)) {
|
||||
throw new TypeError('path must be absolute or specify root to res.sendFile');
|
||||
}
|
||||
|
||||
// socket errors
|
||||
req.socket.on('error', onerror);
|
||||
|
||||
// errors
|
||||
function onerror(err) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
|
||||
// clean up
|
||||
cleanup();
|
||||
|
||||
// callback available
|
||||
if (fn) return fn(err);
|
||||
|
||||
// delegate
|
||||
next(err);
|
||||
}
|
||||
|
||||
// streaming
|
||||
function onstream(stream) {
|
||||
if (done) return;
|
||||
cleanup();
|
||||
if (fn) stream.on('end', fn);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
function cleanup() {
|
||||
req.socket.removeListener('error', onerror);
|
||||
}
|
||||
|
||||
// transfer
|
||||
var pathname = encodeURI(path);
|
||||
var file = send(req, pathname, options);
|
||||
file.on('error', onerror);
|
||||
file.on('directory', next);
|
||||
file.on('stream', onstream);
|
||||
|
||||
if (options.headers) {
|
||||
// set headers on successful transfer
|
||||
file.on('headers', function headers(res) {
|
||||
var obj = options.headers;
|
||||
var keys = Object.keys(obj);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var k = keys[i];
|
||||
res.setHeader(k, obj[k]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// pipe
|
||||
file.pipe(this);
|
||||
this.on('finish', cleanup);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transfer the file at the given `path`.
|
||||
*
|
||||
|
||||
@@ -1,9 +1,158 @@
|
||||
|
||||
var after = require('after');
|
||||
var express = require('../')
|
||||
, request = require('supertest')
|
||||
, assert = require('assert');
|
||||
var path = require('path');
|
||||
var should = require('should');
|
||||
var fixtures = path.join(__dirname, 'fixtures');
|
||||
|
||||
describe('res', function(){
|
||||
describe('.sendFile(path)', function () {
|
||||
it('should transfer a file', function (done) {
|
||||
var app = createApp(path.resolve(fixtures, 'name.txt'));
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'tobi', done);
|
||||
});
|
||||
|
||||
it('should transfer a file with special characters in string', function (done) {
|
||||
var app = createApp(path.resolve(fixtures, '% of dogs.txt'));
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, '20%', done);
|
||||
});
|
||||
|
||||
it('should 404 when not found', function (done) {
|
||||
var app = createApp(path.resolve(fixtures, 'does-no-exist'));
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.statusCode = 200;
|
||||
res.send('no!');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, done);
|
||||
});
|
||||
|
||||
it('should not override manual content-types', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.contentType('application/x-bogus');
|
||||
res.sendFile(path.resolve(fixtures, 'name.txt'));
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/x-bogus')
|
||||
.end(done);
|
||||
})
|
||||
|
||||
describe('with "dotfiles" option', function () {
|
||||
it('should not serve dotfiles by default', function (done) {
|
||||
var app = createApp(path.resolve(__dirname, 'fixtures/.name'));
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, done);
|
||||
});
|
||||
|
||||
it('should accept dotfiles option', function(done){
|
||||
var app = createApp(path.resolve(__dirname, 'fixtures/.name'), { dotfiles: 'allow' });
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'tobi', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with "headers" option', function () {
|
||||
it('should accept headers option', function (done) {
|
||||
var headers = {
|
||||
'x-success': 'sent',
|
||||
'x-other': 'done'
|
||||
};
|
||||
var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), { headers: headers });
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('x-success', 'sent')
|
||||
.expect('x-other', 'done')
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('should ignore headers option on 404', function (done) {
|
||||
var headers = { 'x-success': 'sent' };
|
||||
var app = createApp(path.resolve(__dirname, 'fixtures/does-not-exist'), { headers: headers });
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, function (err, res) {
|
||||
if (err) return done(err);
|
||||
res.headers.should.not.have.property('x-success');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with "root" option', function () {
|
||||
it('should not transfer relative with without', function (done) {
|
||||
var app = createApp('test/fixtures/name.txt');
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(500, /must be absolute/, done);
|
||||
})
|
||||
|
||||
it('should serve relative to "root"', function (done) {
|
||||
var app = createApp('name.txt', {root: fixtures});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'tobi', done);
|
||||
})
|
||||
|
||||
it('should disallow requesting out of "root"', function (done) {
|
||||
var app = createApp('foo/../../user.html', {root: fixtures});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(403, done);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.sendFile(path, fn)', function () {
|
||||
it('should invoke the callback when complete', function (done) {
|
||||
var cb = after(2, done);
|
||||
var app = createApp(path.resolve(fixtures, 'name.txt'), cb);
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, cb);
|
||||
})
|
||||
|
||||
it('should invoke the callback on 404', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {
|
||||
should(err).be.ok;
|
||||
err.status.should.equal(404);
|
||||
res.send('got it');
|
||||
});
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'got it', done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('.sendfile(path, fn)', function(){
|
||||
it('should invoke the callback when complete', function(done){
|
||||
var app = express();
|
||||
@@ -331,3 +480,13 @@ describe('res', function(){
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function createApp(path, options, fn) {
|
||||
var app = express();
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.sendFile(path, options, fn);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
Reference in New Issue
Block a user