diff --git a/History.md b/History.md index 40a5ed7e4be..bfd7e58afa8 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,45 @@ +5.x +=== + +This incorporates all changes after 4.13.1 up to 4.14.0. + +5.0.0-alpha.2 / 2015-07-06 +========================== + +This is the second Express 5.0 alpha release, based off 4.13.1 and includes +changes from 5.0.0-alpha.1. + + * remove: + - `app.param(fn)` + - `req.param()` -- use `req.params`, `req.body`, or `req.query` instead + * change: + - `res.render` callback is always async, even for sync view engines + - The leading `:` character in `name` for `app.param(name, fn)` is no longer removed + - Use `router` module for routing + - Use `path-is-absolute` module for absolute path detection + +5.0.0-alpha.1 / 2014-11-06 +========================== + +This is the first Express 5.0 alpha release, based off 4.10.1. + + * remove: + - `app.del` - use `app.delete` + - `req.acceptsCharset` - use `req.acceptsCharsets` + - `req.acceptsEncoding` - use `req.acceptsEncodings` + - `req.acceptsLanguage` - use `req.acceptsLanguages` + - `res.json(obj, status)` signature - use `res.json(status, obj)` + - `res.jsonp(obj, status)` signature - use `res.jsonp(status, obj)` + - `res.send(body, status)` signature - use `res.send(status, body)` + - `res.send(status)` signature - use `res.sendStatus(status)` + - `res.sendfile` - use `res.sendFile` instead + - `express.query` middleware + * change: + - `req.host` now returns host (`hostname:port`) - use `req.hostname` for only hostname + - `req.query` is now a getter instead of a plain property + * add: + - `app.router` is a reference to the base router + 4.14.0 / 2016-06-16 =================== diff --git a/examples/search/index.js b/examples/search/index.js index d614ac24076..5775eb0ece1 100644 --- a/examples/search/index.js +++ b/examples/search/index.js @@ -38,10 +38,10 @@ app.get('/', function(req, res){ * GET search for :query. */ -app.get('/search/:query?', function(req, res){ +app.get('/search/:query?', function(req, res, next){ var query = req.params.query; db.smembers(query, function(err, vals){ - if (err) return res.send(500); + if (err) return next(err); res.send(vals); }); }); diff --git a/examples/static-files/index.js b/examples/static-files/index.js index c3b1659d861..3353cfa1e33 100644 --- a/examples/static-files/index.js +++ b/examples/static-files/index.js @@ -3,6 +3,7 @@ */ var express = require('../..'); +var path = require('path'); var logger = require('morgan'); var app = express(); @@ -16,7 +17,7 @@ app.use(logger('dev')); // that you pass it. In this case "GET /js/app.js" // will look for "./public/js/app.js". -app.use(express.static(__dirname + '/public')); +app.use(express.static(path.join(__dirname, 'public'))); // if you wanted to "prefix" you may use // the mounting feature of Connect, for example @@ -24,13 +25,13 @@ app.use(express.static(__dirname + '/public')); // The mount-path "/static" is simply removed before // passing control to the express.static() middleware, // thus it serves the file correctly by ignoring "/static" -app.use('/static', express.static(__dirname + '/public')); +app.use('/static', express.static(path.join(__dirname, 'public'))); // if for some reason you want to serve files from // several directories, you can use express.static() // multiple times! Here we're passing "./public/css", // this will allow "GET /style.css" instead of "GET /css/style.css": -app.use(express.static(__dirname + '/public/css')); +app.use(express.static(path.join(__dirname, 'public', 'css'))); app.listen(3000); console.log('listening on port 3000'); @@ -38,3 +39,4 @@ console.log('try:'); console.log(' GET /hello.txt'); console.log(' GET /js/app.js'); console.log(' GET /css/style.css'); + diff --git a/lib/application.js b/lib/application.js index 0ee4def3890..136e435baa2 100644 --- a/lib/application.js +++ b/lib/application.js @@ -14,20 +14,17 @@ */ var finalhandler = require('finalhandler'); -var Router = require('./router'); var methods = require('methods'); -var middleware = require('./middleware/init'); -var query = require('./middleware/query'); var debug = require('debug')('express:application'); var View = require('./view'); var http = require('http'); var compileETag = require('./utils').compileETag; var compileQueryParser = require('./utils').compileQueryParser; var compileTrust = require('./utils').compileTrust; -var deprecate = require('depd')('express'); var flatten = require('array-flatten'); var merge = require('utils-merge'); var resolve = require('path').resolve; +var Router = require('router'); var slice = Array.prototype.slice; /** @@ -54,11 +51,29 @@ var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default'; */ app.init = function init() { + var router = null; + this.cache = {}; this.engines = {}; this.settings = {}; this.defaultConfiguration(); + + // Setup getting to lazily add base router + Object.defineProperty(this, 'router', { + configurable: true, + enumerable: true, + get: function getrouter() { + if (router === null) { + router = new Router({ + caseSensitive: this.enabled('case sensitive routing'), + strict: this.enabled('strict routing') + }); + } + + return router; + } + }); }; /** @@ -117,32 +132,6 @@ app.defaultConfiguration = function defaultConfiguration() { if (env === 'production') { this.enable('view cache'); } - - Object.defineProperty(this, 'router', { - get: function() { - throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.'); - } - }); -}; - -/** - * lazily adds the base router if it has not yet been added. - * - * We cannot add the base router in the defaultConfiguration because - * it reads app settings which might be set after that has run. - * - * @private - */ -app.lazyrouter = function lazyrouter() { - if (!this._router) { - this._router = new Router({ - caseSensitive: this.enabled('case sensitive routing'), - strict: this.enabled('strict routing') - }); - - this._router.use(query(this.get('query parser fn'))); - this._router.use(middleware.init(this)); - } }; /** @@ -155,22 +144,31 @@ app.lazyrouter = function lazyrouter() { */ app.handle = function handle(req, res, callback) { - var router = this._router; - // final handler var done = callback || finalhandler(req, res, { env: this.get('env'), onerror: logerror.bind(this) }); - // no routes - if (!router) { - debug('no routes defined on app'); - done(); - return; + // set powered by header + if (this.enabled('x-powered-by')) { + res.setHeader('X-Powered-By', 'Express'); } - router.handle(req, res, done); + // set circular references + req.res = res; + res.req = req; + + // alter the prototypes + req.__proto__ = this.request; + res.__proto__ = this.response; + + // setup locals + if (!res.locals) { + res.locals = Object.create(null); + } + + this.router.handle(req, res, done); }; /** @@ -209,9 +207,8 @@ app.use = function use(fn) { throw new TypeError('app.use() requires middleware functions'); } - // setup router - this.lazyrouter(); - var router = this._router; + // get router + var router = this.router; fns.forEach(function (fn) { // non-express app @@ -251,8 +248,7 @@ app.use = function use(fn) { */ app.route = function route(path) { - this.lazyrouter(); - return this._router.route(path); + return this.router.route(path); }; /** @@ -318,8 +314,6 @@ app.engine = function engine(ext, fn) { */ app.param = function param(name, fn) { - this.lazyrouter(); - if (Array.isArray(name)) { for (var i = 0; i < name.length; i++) { this.param(name[i], fn); @@ -328,7 +322,7 @@ app.param = function param(name, fn) { return this; } - this._router.param(name, fn); + this.router.param(name, fn); return this; }; @@ -475,9 +469,7 @@ methods.forEach(function(method){ return this.set(path); } - this.lazyrouter(); - - var route = this._router.route(path); + var route = this.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }; @@ -494,9 +486,7 @@ methods.forEach(function(method){ */ app.all = function all(path) { - this.lazyrouter(); - - var route = this._router.route(path); + var route = this.route(path); var args = slice.call(arguments, 1); for (var i = 0; i < methods.length; i++) { @@ -506,10 +496,6 @@ app.all = function all(path) { return this; }; -// del -> delete alias - -app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead'); - /** * Render the given view `name` name with `options` * and a callback accepting an error and the diff --git a/lib/express.js b/lib/express.js index 540c8be6f41..ff62644c534 100644 --- a/lib/express.js +++ b/lib/express.js @@ -15,8 +15,7 @@ var EventEmitter = require('events').EventEmitter; var mixin = require('merge-descriptors'); var proto = require('./application'); -var Route = require('./router/route'); -var Router = require('./router'); +var Router = require('router'); var req = require('./request'); var res = require('./response'); @@ -59,14 +58,13 @@ exports.response = res; * Expose constructors. */ -exports.Route = Route; +exports.Route = Router.Route; exports.Router = Router; /** * Expose middleware */ -exports.query = require('./middleware/query'); exports.static = require('serve-static'); /** @@ -93,6 +91,7 @@ exports.static = require('serve-static'); 'limit', 'multipart', 'staticCache', + 'query', ].forEach(function (name) { Object.defineProperty(exports, name, { get: function () { diff --git a/lib/request.js b/lib/request.js index 557d050ffb5..9df73d35aca 100644 --- a/lib/request.js +++ b/lib/request.js @@ -14,7 +14,6 @@ */ var accepts = require('accepts'); -var deprecate = require('depd')('express'); var isIP = require('net').isIP; var typeis = require('type-is'); var http = require('http'); @@ -141,9 +140,6 @@ req.acceptsEncodings = function(){ return accept.encodings.apply(accept, arguments); }; -req.acceptsEncoding = deprecate.function(req.acceptsEncodings, - 'req.acceptsEncoding: Use acceptsEncodings instead'); - /** * Check if the given `charset`s are acceptable, * otherwise you should respond with 406 "Not Acceptable". @@ -158,9 +154,6 @@ req.acceptsCharsets = function(){ return accept.charsets.apply(accept, arguments); }; -req.acceptsCharset = deprecate.function(req.acceptsCharsets, - 'req.acceptsCharset: Use acceptsCharsets instead'); - /** * Check if the given `lang`s are acceptable, * otherwise you should respond with 406 "Not Acceptable". @@ -175,9 +168,6 @@ req.acceptsLanguages = function(){ return accept.languages.apply(accept, arguments); }; -req.acceptsLanguage = deprecate.function(req.acceptsLanguages, - 'req.acceptsLanguage: Use acceptsLanguages instead'); - /** * Parse Range header field, capping to the given `size`. * @@ -210,38 +200,27 @@ req.range = function range(size, options) { }; /** - * Return the value of param `name` when present or `defaultValue`. - * - * - Checks route placeholders, ex: _/user/:id_ - * - Checks body params, ex: id=12, {"id":12} - * - Checks query string params, ex: ?id=12 + * Parse the query string of `req.url`. * - * To utilize request bodies, `req.body` - * should be an object. This can be done by using - * the `bodyParser()` middleware. + * This uses the "query parser" setting to parse the raw + * string into an object. * - * @param {String} name - * @param {Mixed} [defaultValue] * @return {String} - * @public + * @api public */ -req.param = function param(name, defaultValue) { - var params = this.params || {}; - var body = this.body || {}; - var query = this.query || {}; +defineGetter(req, 'query', function query(){ + var queryparse = this.app.get('query parser fn'); - var args = arguments.length === 1 - ? 'name' - : 'name, default'; - deprecate('req.param(' + args + '): Use req.params, req.body, or req.query instead'); + if (!queryparse) { + // parsing is disabled + return Object.create(null); + } - if (null != params[name] && params.hasOwnProperty(name)) return params[name]; - if (null != body[name]) return body[name]; - if (null != query[name]) return query[name]; + var querystring = parse(this).query; - return defaultValue; -}; + return queryparse(querystring); +}); /** * Check if the incoming request contains the "Content-Type" @@ -399,7 +378,7 @@ defineGetter(req, 'path', function path() { }); /** - * Parse the "Host" header field to a hostname. + * Parse the "Host" header field to a host. * * When the "trust proxy" setting trusts the socket * address, the "X-Forwarded-Host" header field will @@ -409,14 +388,31 @@ defineGetter(req, 'path', function path() { * @public */ -defineGetter(req, 'hostname', function hostname(){ +defineGetter(req, 'host', function host(){ var trust = this.app.get('trust proxy fn'); - var host = this.get('X-Forwarded-Host'); + var val = this.get('X-Forwarded-Host'); - if (!host || !trust(this.connection.remoteAddress, 0)) { - host = this.get('Host'); + if (!val || !trust(this.connection.remoteAddress, 0)) { + val = this.get('Host'); } + return val || undefined; +}); + +/** + * Parse the "Host" header field to a hostname. + * + * When the "trust proxy" setting trusts the socket + * address, the "X-Forwarded-Host" header field will + * be trusted. + * + * @return {String} + * @api public + */ + +defineGetter(req, 'hostname', function hostname(){ + var host = this.host; + if (!host) return; // IPv6 literal support @@ -430,12 +426,6 @@ defineGetter(req, 'hostname', function hostname(){ : host; }); -// TODO: change req.host to return host in next major - -defineGetter(req, 'host', deprecate.function(function host(){ - return this.hostname; -}, 'req.host: Use req.hostname instead')); - /** * Check if the request is fresh, aka * Last-Modified and/or the ETag diff --git a/lib/response.js b/lib/response.js index 6128f450a94..6ab51223310 100644 --- a/lib/response.js +++ b/lib/response.js @@ -17,9 +17,9 @@ var deprecate = require('depd')('express'); var encodeUrl = require('encodeurl'); var escapeHtml = require('escape-html'); var http = require('http'); -var isAbsolute = require('./utils').isAbsolute; var onFinished = require('on-finished'); var path = require('path'); +var pathIsAbsolute = require('path-is-absolute'); var merge = require('utils-merge'); var sign = require('cookie-signature').sign; var normalizeType = require('./utils').normalizeType; @@ -107,29 +107,11 @@ res.send = function send(body) { // settings var app = this.app; - // allow status / body + // support res.send(status, body) if (arguments.length === 2) { - // res.send(body, status) backwards compat - if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') { - deprecate('res.send(body, status): Use res.status(status).send(body) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.send(status, body): Use res.status(status).send(body) instead'); - this.statusCode = arguments[0]; - chunk = arguments[1]; - } - } - - // disambiguate res.send(status) and res.send(status, num) - if (typeof chunk === 'number' && arguments.length === 1) { - // res.send(status) will set status message as text string - if (!this.get('Content-Type')) { - this.type('txt'); - } - - deprecate('res.send(status): Use res.sendStatus(status) instead'); - this.statusCode = chunk; - chunk = statusCodes[chunk]; + deprecate('res.send(status, body): Use res.status(status).send(body) instead'); + this.statusCode = arguments[0]; + chunk = arguments[1]; } switch (typeof chunk) { @@ -223,17 +205,11 @@ res.send = function send(body) { res.json = function json(obj) { var val = obj; - // allow status / body + // support res.json(status, obj) if (arguments.length === 2) { - // res.json(body, status) backwards compat - if (typeof arguments[1] === 'number') { - deprecate('res.json(obj, status): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[0]; - val = arguments[1]; - } + deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); + this.statusCode = arguments[0]; + val = arguments[1]; } // settings @@ -265,17 +241,11 @@ res.json = function json(obj) { res.jsonp = function jsonp(obj) { var val = obj; - // allow status / body + // support res.jsonp(status, obj) if (arguments.length === 2) { - // res.json(body, status) backwards compat - if (typeof arguments[1] === 'number') { - deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); - this.statusCode = arguments[0]; - val = arguments[1]; - } + deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); + this.statusCode = arguments[0]; + val = arguments[1]; } // settings @@ -400,7 +370,7 @@ res.sendFile = function sendFile(path, options, callback) { opts = {}; } - if (!opts.root && !isAbsolute(path)) { + if (!opts.root && !pathIsAbsolute(path)) { throw new TypeError('path must be absolute or specify root to res.sendFile'); } @@ -420,78 +390,6 @@ res.sendFile = function sendFile(path, options, callback) { }); }; -/** - * Transfer the file at the given `path`. - * - * Automatically sets the _Content-Type_ response header field. - * The callback `callback(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.'); - * } - * }); - * }); - * - * @public - */ - -res.sendfile = function (path, options, callback) { - var done = callback; - var req = this.req; - var res = this; - var next = req.next; - var opts = options || {}; - - // support function as second arg - if (typeof options === 'function') { - done = options; - opts = {}; - } - - // create file stream - var file = send(req, path, opts); - - // transfer - sendfile(res, file, opts, function (err) { - if (done) return done(err); - if (err && err.code === 'EISDIR') return next(); - - // next() all but write errors - if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') { - next(err); - } - }); -}; - -res.sendfile = deprecate.function(res.sendfile, - 'res.sendfile: Use res.sendFile instead'); - /** * Transfer the file at the given `path` as an attachment. * @@ -500,7 +398,7 @@ res.sendfile = deprecate.function(res.sendfile, * when the data transfer is complete, or when an error has * ocurred. Be sure to check `res.headersSent` if you plan to respond. * - * This method uses `res.sendfile()`. + * This method uses `res.sendFile()`. * * @public */ diff --git a/lib/router/layer.js b/lib/router/layer.js index fe9210cb9de..e9ca5c70b43 100644 --- a/lib/router/layer.js +++ b/lib/router/layer.js @@ -23,132 +23,134 @@ var debug = require('debug')('express:router:layer'); var hasOwnProperty = Object.prototype.hasOwnProperty; -/** - * Module exports. - * @public - */ +class _Layer { + constructor(path, options, fn) { + debug('new %s', path); + var opts = options || {}; -module.exports = Layer; + this.handle = fn; + this.name = fn.name || ''; + this.params = undefined; + this.path = undefined; + this.regexp = pathRegexp(path, this.keys = [], opts); -function Layer(path, options, fn) { - if (!(this instanceof Layer)) { - return new Layer(path, options, fn); + if (path === '/' && opts.end === false) { + this.regexp.fast_slash = true; + } } - debug('new %s', path); - var opts = options || {}; - - this.handle = fn; - this.name = fn.name || ''; - this.params = undefined; - this.path = undefined; - this.regexp = pathRegexp(path, this.keys = [], opts); + /** + * Handle the error for the layer. + * + * @param {Error} error + * @param {Request} req + * @param {Response} res + * @param {function} next + * @api private + */ + + handle_error(error, req, res, next) { + var fn = this.handle; + + if (fn.length !== 4) { + // not a standard error handler + return next(error); + } - if (path === '/' && opts.end === false) { - this.regexp.fast_slash = true; + try { + fn(error, req, res, next); + } catch (err) { + next(err); + } } -} - -/** - * Handle the error for the layer. - * - * @param {Error} error - * @param {Request} req - * @param {Response} res - * @param {function} next - * @api private - */ - -Layer.prototype.handle_error = function handle_error(error, req, res, next) { - var fn = this.handle; - if (fn.length !== 4) { - // not a standard error handler - return next(error); - } + /** + * Handle the request for the layer. + * + * @param {Request} req + * @param {Response} res + * @param {function} next + * @api private + */ + + handle_request(req, res, next) { + var fn = this.handle; + + if (fn.length > 3) { + // not a standard request handler + return next(); + } - try { - fn(error, req, res, next); - } catch (err) { - next(err); + try { + fn(req, res, next); + } catch (err) { + next(err); + } } -}; -/** - * Handle the request for the layer. - * - * @param {Request} req - * @param {Response} res - * @param {function} next - * @api private - */ + /** + * Check if this route matches `path`, if so + * populate `.params`. + * + * @param {String} path + * @return {Boolean} + * @api private + */ + + match(path) { + if (path == null) { + // no path, nothing matches + this.params = undefined; + this.path = undefined; + return false; + } -Layer.prototype.handle_request = function handle(req, res, next) { - var fn = this.handle; + if (this.regexp.fast_slash) { + // fast path non-ending match for / (everything matches) + this.params = {}; + this.path = ''; + return true; + } - if (fn.length > 3) { - // not a standard request handler - return next(); - } + var m = this.regexp.exec(path); - try { - fn(req, res, next); - } catch (err) { - next(err); - } -}; + if (!m) { + this.params = undefined; + this.path = undefined; + return false; + } -/** - * Check if this route matches `path`, if so - * populate `.params`. - * - * @param {String} path - * @return {Boolean} - * @api private - */ + // store values + this.params = {}; + this.path = m[0]; -Layer.prototype.match = function match(path) { - if (path == null) { - // no path, nothing matches - this.params = undefined; - this.path = undefined; - return false; - } + var keys = this.keys; + var params = this.params; - if (this.regexp.fast_slash) { - // fast path non-ending match for / (everything matches) - this.params = {}; - this.path = ''; - return true; - } + for (var i = 1; i < m.length; i++) { + var key = keys[i - 1]; + var prop = key.name; + var val = decode_param(m[i]); - var m = this.regexp.exec(path); + if (val !== undefined || !(hasOwnProperty.call(params, prop))) { + params[prop] = val; + } + } - if (!m) { - this.params = undefined; - this.path = undefined; - return false; + return true; } - // store values - this.params = {}; - this.path = m[0]; - - var keys = this.keys; - var params = this.params; +} - for (var i = 1; i < m.length; i++) { - var key = keys[i - 1]; - var prop = key.name; - var val = decode_param(m[i]); +/** + * To allow the use of Layer with or without 'new' + */ - if (val !== undefined || !(hasOwnProperty.call(params, prop))) { - params[prop] = val; - } +function Layer(path, options, fn) { + if (!(this instanceof _Layer)) { + return new _Layer(path, options, fn); } - - return true; -}; +} /** * Decode param value. @@ -174,3 +176,11 @@ function decode_param(val) { throw err; } } + +/** + * Module exports. + * @public + */ + +module.exports = Layer; + diff --git a/lib/router/route.js b/lib/router/route.js index 2788d7b7353..493fe073762 100644 --- a/lib/router/route.js +++ b/lib/router/route.js @@ -26,13 +26,6 @@ var methods = require('methods'); var slice = Array.prototype.slice; var toString = Object.prototype.toString; -/** - * Module exports. - * @public - */ - -module.exports = Route; - /** * Initialize `Route` with the given `path`, * @@ -40,148 +33,151 @@ module.exports = Route; * @public */ -function Route(path) { - this.path = path; - this.stack = []; +class Route { + constructor(path) { + this.path = path; + this.stack = []; - debug('new %s', path); - - // route handlers for various http methods - this.methods = {}; -} + debug('new %s', path); -/** - * Determine if the route handles a given method. - * @private - */ - -Route.prototype._handles_method = function _handles_method(method) { - if (this.methods._all) { - return true; + // route handlers for various http methods + this.methods = {}; } - var name = method.toLowerCase(); + /** + * Determine if the route handles a given method. + * @private + */ - if (name === 'head' && !this.methods['head']) { - name = 'get'; - } + _handles_method(method) { + if (this.methods._all) { + return true; + } - return Boolean(this.methods[name]); -}; + var name = method.toLowerCase(); -/** - * @return {Array} supported HTTP methods - * @private - */ - -Route.prototype._options = function _options() { - var methods = Object.keys(this.methods); + if (name === 'head' && !this.methods['head']) { + name = 'get'; + } - // append automatic head - if (this.methods.get && !this.methods.head) { - methods.push('head'); + return Boolean(this.methods[name]); } - for (var i = 0; i < methods.length; i++) { - // make upper case - methods[i] = methods[i].toUpperCase(); - } + /** + * @return {Array} supported HTTP methods + * @private + */ - return methods; -}; + _options() { + var methods = Object.keys(this.methods); -/** - * dispatch req, res into this route - * @private - */ + // append automatic head + if (this.methods.get && !this.methods.head) { + methods.push('head'); + } -Route.prototype.dispatch = function dispatch(req, res, done) { - var idx = 0; - var stack = this.stack; - if (stack.length === 0) { - return done(); - } + for (var i = 0; i < methods.length; i++) { + // make upper case + methods[i] = methods[i].toUpperCase(); + } - var method = req.method.toLowerCase(); - if (method === 'head' && !this.methods['head']) { - method = 'get'; + return methods; } - req.route = this; + /** + * dispatch req, res into this route + * @private + */ - next(); - - function next(err) { - if (err && err === 'route') { + dispatch(req, res, done) { + var idx = 0; + var stack = this.stack; + if (stack.length === 0) { return done(); } - var layer = stack[idx++]; - if (!layer) { - return done(err); + var method = req.method.toLowerCase(); + if (method === 'head' && !this.methods['head']) { + method = 'get'; } - if (layer.method && layer.method !== method) { - return next(err); - } + req.route = this; + + next(); + + function next(err) { + if (err && err === 'route') { + return done(); + } + + var layer = stack[idx++]; + if (!layer) { + return done(err); + } + + if (layer.method && layer.method !== method) { + return next(err); + } - if (err) { - layer.handle_error(err, req, res, next); - } else { - layer.handle_request(req, res, next); + if (err) { + layer.handle_error(err, req, res, next); + } else { + layer.handle_request(req, res, next); + } } } -}; -/** - * Add a handler for all HTTP verbs to this route. - * - * Behaves just like middleware and can respond or call `next` - * to continue processing. - * - * You can use multiple `.all` call to add multiple handlers. - * - * function check_something(req, res, next){ - * next(); - * }; - * - * function validate_user(req, res, next){ - * next(); - * }; - * - * route - * .all(validate_user) - * .all(check_something) - * .get(function(req, res, next){ - * res.send('hello world'); - * }); - * - * @param {function} handler - * @return {Route} for chaining - * @api public - */ + /** + * Add a handler for all HTTP verbs to this route. + * + * Behaves just like middleware and can respond or call `next` + * to continue processing. + * + * You can use multiple `.all` call to add multiple handlers. + * + * function check_something(req, res, next){ + * next(); + * }; + * + * function validate_user(req, res, next){ + * next(); + * }; + * + * route + * .all(validate_user) + * .all(check_something) + * .get(function(req, res, next){ + * res.send('hello world'); + * }); + * + * @param {function} handler + * @return {Route} for chaining + * @api public + */ + + all() { + var handles = flatten(slice.call(arguments)); -Route.prototype.all = function all() { - var handles = flatten(slice.call(arguments)); + for (var i = 0; i < handles.length; i++) { + var handle = handles[i]; - for (var i = 0; i < handles.length; i++) { - var handle = handles[i]; + if (typeof handle !== 'function') { + var type = toString.call(handle); + var msg = 'Route.all() requires callback functions but got a ' + type; + throw new TypeError(msg); + } - if (typeof handle !== 'function') { - var type = toString.call(handle); - var msg = 'Route.all() requires callback functions but got a ' + type; - throw new TypeError(msg); - } + var layer = Layer('/', {}, handle); + layer.method = undefined; - var layer = Layer('/', {}, handle); - layer.method = undefined; + this.methods._all = true; + this.stack.push(layer); + } - this.methods._all = true; - this.stack.push(layer); + return this; } - return this; -}; +} methods.forEach(function(method){ Route.prototype[method] = function(){ @@ -208,3 +204,12 @@ methods.forEach(function(method){ return this; }; }); + +/** + * Module exports. + * @public + */ + +module.exports = Route; + + diff --git a/lib/utils.js b/lib/utils.js index f418c5807c7..078e0fd59d0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -12,13 +12,10 @@ * @api private */ -var contentDisposition = require('content-disposition'); -var contentType = require('content-type'); -var deprecate = require('depd')('express'); -var flatten = require('array-flatten'); var mime = require('send').mime; -var basename = require('path').basename; +var contentType = require('content-type'); var etag = require('etag'); +var flatten = require('array-flatten'); var proxyaddr = require('proxy-addr'); var qs = require('qs'); var querystring = require('querystring'); @@ -57,31 +54,6 @@ exports.wetag = function wetag(body, encoding){ return etag(buf, {weak: true}); }; -/** - * Check if `path` looks absolute. - * - * @param {String} path - * @return {Boolean} - * @api private - */ - -exports.isAbsolute = function(path){ - if ('/' === path[0]) return true; - if (':' === path[1] && ('\\' === path[2] || '/' === path[2])) return true; // Windows device path - if ('\\\\' === path.substring(0, 2)) return true; // Microsoft Azure absolute path -}; - -/** - * Flatten the given `arr`. - * - * @param {Array} arr - * @return {Array} - * @api private - */ - -exports.flatten = deprecate.function(flatten, - 'utils.flatten: use array-flatten npm module instead'); - /** * Normalize the given `type`, for example "html" becomes "text/html". * @@ -114,18 +86,6 @@ exports.normalizeTypes = function(types){ return ret; }; -/** - * Generate Content-Disposition header appropriate for the filename. - * non-ascii filenames are urlencoded and a filename* parameter is added - * - * @param {String} filename - * @return {String} - * @api private - */ - -exports.contentDisposition = deprecate.function(contentDisposition, - 'utils.contentDisposition: use content-disposition npm module instead'); - /** * Parse accept params `str` returning an * object with `.value`, `.quality` and `.params`. @@ -206,7 +166,6 @@ exports.compileQueryParser = function compileQueryParser(val) { fn = querystring.parse; break; case false: - fn = newObject; break; case 'extended': fn = parseExtendedQueryString; @@ -286,14 +245,3 @@ function parseExtendedQueryString(str) { allowPrototypes: true }); } - -/** - * Return new empty object. - * - * @return {Object} - * @api private - */ - -function newObject() { - return {}; -} diff --git a/lib/view.js b/lib/view.js index 52415d4c28b..4146b071e3a 100644 --- a/lib/view.js +++ b/lib/view.js @@ -29,13 +29,6 @@ var extname = path.extname; var join = path.join; var resolve = path.resolve; -/** - * Module exports. - * @public - */ - -module.exports = View; - /** * Initialize a new `View` with the given `name`. * @@ -50,109 +43,112 @@ module.exports = View; * @public */ -function View(name, options) { - var opts = options || {}; +class View { + constructor(name, options) { + var opts = options || {}; - this.defaultEngine = opts.defaultEngine; - this.ext = extname(name); - this.name = name; - this.root = opts.root; + this.defaultEngine = opts.defaultEngine; + this.ext = extname(name); + this.name = name; + this.root = opts.root; - if (!this.ext && !this.defaultEngine) { - throw new Error('No default engine was specified and no extension was provided.'); - } + if (!this.ext && !this.defaultEngine) { + throw new Error('No default engine was specified and no extension was provided.'); + } - var fileName = name; + var fileName = name; - if (!this.ext) { - // get extension from default engine name - this.ext = this.defaultEngine[0] !== '.' - ? '.' + this.defaultEngine - : this.defaultEngine; + if (!this.ext) { + // get extension from default engine name + this.ext = this.defaultEngine[0] !== '.' + ? '.' + this.defaultEngine + : this.defaultEngine; - fileName += this.ext; - } + fileName += this.ext; + } - if (!opts.engines[this.ext]) { - // load engine - opts.engines[this.ext] = require(this.ext.substr(1)).__express; - } + if (!opts.engines[this.ext]) { + // load engine + opts.engines[this.ext] = require(this.ext.substr(1)).__express; + } - // store loaded engine - this.engine = opts.engines[this.ext]; + // store loaded engine + this.engine = opts.engines[this.ext]; - // lookup path - this.path = this.lookup(fileName); -} + // lookup path + this.path = this.lookup(fileName); + } -/** - * Lookup view by the given `name` - * - * @param {string} name - * @private - */ + /** + * Lookup view by the given `name` + * + * @param {string} name + * @private + */ + + lookup(name) { + var path; + var roots = [].concat(this.root); -View.prototype.lookup = function lookup(name) { - var path; - var roots = [].concat(this.root); + debug('lookup "%s"', name); - debug('lookup "%s"', name); + for (var i = 0; i < roots.length && !path; i++) { + var root = roots[i]; - for (var i = 0; i < roots.length && !path; i++) { - var root = roots[i]; + // resolve the path + var loc = resolve(root, name); + var dir = dirname(loc); + var file = basename(loc); - // resolve the path - var loc = resolve(root, name); - var dir = dirname(loc); - var file = basename(loc); + // resolve the file + path = this.resolve(dir, file); + } - // resolve the file - path = this.resolve(dir, file); + return path; } - return path; -}; + /** + * Render with the given options. + * + * @param {object} options + * @param {function} callback + * @private + */ + + render(options, callback) { + debug('render "%s"', this.path); + this.engine(this.path, options, callback); + } -/** - * Render with the given options. - * - * @param {object} options - * @param {function} callback - * @private - */ + /** + * Resolve the file within the given directory. + * + * @param {string} dir + * @param {string} file + * @private + */ -View.prototype.render = function render(options, callback) { - debug('render "%s"', this.path); - this.engine(this.path, options, callback); -}; + resolve(dir, file) { + var ext = this.ext; -/** - * Resolve the file within the given directory. - * - * @param {string} dir - * @param {string} file - * @private - */ + // . + var path = join(dir, file); + var stat = tryStat(path); -View.prototype.resolve = function resolve(dir, file) { - var ext = this.ext; + if (stat && stat.isFile()) { + return path; + } - // . - var path = join(dir, file); - var stat = tryStat(path); + // /index. + path = join(dir, basename(file, ext), 'index' + ext); + stat = tryStat(path); - if (stat && stat.isFile()) { - return path; + if (stat && stat.isFile()) { + return path; + } } - // /index. - path = join(dir, basename(file, ext), 'index' + ext); - stat = tryStat(path); - - if (stat && stat.isFile()) { - return path; - } -}; +} /** * Return a stat, maybe. @@ -171,3 +167,11 @@ function tryStat(path) { return undefined; } } + +/** + * Module exports. + * @public + */ + +module.exports = View; + diff --git a/package.json b/package.json index 57dd1602884..1ecc060b787 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "express", "description": "Fast, unopinionated, minimalist web framework", - "version": "4.14.0", + "version": "5.0.0-alpha.2", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ", @@ -28,7 +28,7 @@ ], "dependencies": { "accepts": "~1.3.3", - "array-flatten": "1.1.1", + "array-flatten": "2.0.0", "content-disposition": "0.5.1", "content-type": "~1.0.2", "cookie": "0.3.1", @@ -44,10 +44,12 @@ "methods": "~1.1.2", "on-finished": "~2.3.0", "parseurl": "~1.3.1", + "path-is-absolute": "1.0.0", "path-to-regexp": "0.1.7", "proxy-addr": "~1.1.2", "qs": "6.2.0", "range-parser": "~1.2.0", + "router": "~1.1.3", "send": "0.14.1", "serve-static": "~1.11.1", "type-is": "~1.6.13", diff --git a/test/Router.js b/test/Router.js index 21cdff2c6c0..6ab8651832f 100644 --- a/test/Router.js +++ b/test/Router.js @@ -27,7 +27,7 @@ describe('Router', function(){ }); router.use('/foo', another); - router.handle({ url: '/foo/bar', method: 'GET' }, { end: done }); + router.handle({ url: '/foo/bar', method: 'GET' }, { end: done }, function(){}); }); it('should support dynamic routes', function(done){ @@ -40,7 +40,7 @@ describe('Router', function(){ }); router.use('/:foo', another); - router.handle({ url: '/test/route', method: 'GET' }, { end: done }); + router.handle({ url: '/test/route', method: 'GET' }, { end: done }, function(){}); }); it('should handle blank URL', function(done){ @@ -65,7 +65,7 @@ describe('Router', function(){ res.end(); }); - router.handle({ url: '/', method: 'GET' }, { end: done }); + router.handle({ url: '/', method: 'GET' }, { end: done }, function(){}); }); describe('.handle', function(){ @@ -82,7 +82,7 @@ describe('Router', function(){ done(); } } - router.handle({ url: '/foo', method: 'GET' }, res); + router.handle({ url: '/foo', method: 'GET' }, res, function(){}); }) }) @@ -342,15 +342,15 @@ describe('Router', function(){ describe('.use', function() { it('should require arguments', function(){ var router = new Router(); - router.use.bind(router).should.throw(/requires middleware function/) + assert.throws(router.use.bind(router), /argument handler is required/) }) it('should not accept non-functions', function(){ var router = new Router(); - router.use.bind(router, '/', 'hello').should.throw(/requires middleware function.*string/) - router.use.bind(router, '/', 5).should.throw(/requires middleware function.*number/) - router.use.bind(router, '/', null).should.throw(/requires middleware function.*Null/) - router.use.bind(router, '/', new Date()).should.throw(/requires middleware function.*Date/) + assert.throws(router.use.bind(router, '/', 'hello'), /argument handler must be a function/) + assert.throws(router.use.bind(router, '/', 5), /argument handler must be a function/) + assert.throws(router.use.bind(router, '/', null), /argument handler must be a function/) + assert.throws(router.use.bind(router, '/', new Date()), /argument handler must be a function/) }) it('should accept array of middleware', function(done){ @@ -377,6 +377,16 @@ describe('Router', function(){ }) describe('.param', function() { + it('should require function', function () { + var router = new Router(); + assert.throws(router.param.bind(router, 'id'), /argument fn is required/); + }); + + it('should reject non-function', function () { + var router = new Router(); + assert.throws(router.param.bind(router, 'id', 42), /argument fn must be a function/); + }); + it('should call param function when routing VERBS', function(done) { var router = new Router(); diff --git a/test/app.del.js b/test/app.del.js deleted file mode 100644 index d419fbb1580..00000000000 --- a/test/app.del.js +++ /dev/null @@ -1,17 +0,0 @@ - -var express = require('../') - , request = require('supertest'); - -describe('app.del()', function(){ - it('should alias app.delete()', function(done){ - var app = express(); - - app.del('/tobi', function(req, res){ - res.end('deleted tobi!'); - }); - - request(app) - .del('/tobi') - .expect('deleted tobi!', done); - }) -}) diff --git a/test/app.js b/test/app.js index 941d35ff1cc..c5fc93efa56 100644 --- a/test/app.js +++ b/test/app.js @@ -55,18 +55,6 @@ describe('app.mountpath', function(){ }) }) -describe('app.router', function(){ - it('should throw with notice', function(done){ - var app = express() - - try { - app.router; - } catch(err) { - done(); - } - }) -}) - describe('app.path()', function(){ it('should return the canonical', function(){ var app = express() diff --git a/test/app.listen.js b/test/app.listen.js index b6f68578934..f32c357d928 100644 --- a/test/app.listen.js +++ b/test/app.listen.js @@ -6,8 +6,8 @@ describe('app.listen()', function(){ it('should wrap with an HTTP server', function(done){ var app = express(); - app.del('/tobi', function(req, res){ - res.end('deleted tobi!'); + app.get('/tobi', function(req, res){ + res.end('got tobi!'); }); var server = app.listen(9999, function(){ diff --git a/test/app.options.js b/test/app.options.js index 20234723a57..3f13f1d244b 100644 --- a/test/app.options.js +++ b/test/app.options.js @@ -6,28 +6,28 @@ describe('OPTIONS', function(){ it('should default to the routes defined', function(done){ var app = express(); - app.del('/', function(){}); + app.post('/', function(){}); app.get('/users', function(req, res){}); app.put('/users', function(req, res){}); request(app) .options('/users') - .expect('Allow', 'GET,HEAD,PUT') - .expect(200, 'GET,HEAD,PUT', done); + .expect('Allow', 'GET, HEAD, PUT') + .expect(200, 'GET, HEAD, PUT', done); }) it('should only include each method once', function(done){ var app = express(); - app.del('/', function(){}); + app.delete('/', function(){}); app.get('/users', function(req, res){}); app.put('/users', function(req, res){}); app.get('/users', function(req, res){}); request(app) .options('/users') - .expect('Allow', 'GET,HEAD,PUT') - .expect(200, 'GET,HEAD,PUT', done); + .expect('Allow', 'GET, HEAD, PUT') + .expect(200, 'GET, HEAD, PUT', done); }) it('should not be affected by app.all', function(done){ @@ -44,8 +44,8 @@ describe('OPTIONS', function(){ request(app) .options('/users') .expect('x-hit', '1') - .expect('Allow', 'GET,HEAD,PUT') - .expect(200, 'GET,HEAD,PUT', done); + .expect('Allow', 'GET, HEAD, PUT') + .expect(200, 'GET, HEAD, PUT', done); }) it('should not respond if the path is not defined', function(done){ @@ -68,8 +68,8 @@ describe('OPTIONS', function(){ request(app) .options('/other') - .expect('Allow', 'GET,HEAD') - .expect(200, 'GET,HEAD', done); + .expect('Allow', 'GET, HEAD') + .expect(200, 'GET, HEAD', done); }) describe('when error occurs in respone handler', function () { diff --git a/test/app.param.js b/test/app.param.js index 30885bcdc89..7996995f885 100644 --- a/test/app.param.js +++ b/test/app.param.js @@ -3,47 +3,6 @@ var express = require('../') , request = require('supertest'); describe('app', function(){ - describe('.param(fn)', function(){ - it('should map app.param(name, ...) logic', function(done){ - var app = express(); - - app.param(function(name, regexp){ - if (Object.prototype.toString.call(regexp) == '[object RegExp]') { // See #1557 - return function(req, res, next, val){ - var captures; - if (captures = regexp.exec(String(val))) { - req.params[name] = captures[1]; - next(); - } else { - next('route'); - } - } - } - }) - - app.param(':name', /^([a-zA-Z]+)$/); - - app.get('/user/:name', function(req, res){ - res.send(req.params.name); - }); - - request(app) - .get('/user/tj') - .end(function(err, res){ - res.text.should.equal('tj'); - request(app) - .get('/user/123') - .expect(404, done); - }); - - }) - - it('should fail if not given fn', function(){ - var app = express(); - app.param.bind(app, ':name', 'bob').should.throw(); - }) - }) - describe('.param(names, fn)', function(){ it('should map the array', function(done){ var app = express(); diff --git a/test/app.router.js b/test/app.router.js index 03b43eb081a..12a0f48ab7d 100644 --- a/test/app.router.js +++ b/test/app.router.js @@ -34,7 +34,7 @@ describe('app.router', function(){ }) describe('methods', function(){ - methods.concat('del').forEach(function(method){ + methods.forEach(function(method){ if (method === 'connect') return; it('should include ' + method.toUpperCase(), function(done){ @@ -56,7 +56,7 @@ describe('app.router', function(){ it('should reject numbers for app.' + method, function(){ var app = express(); - app[method].bind(app, '/', 3).should.throw(/Number/); + assert.throws(app[method].bind(app, '/', 3), /argument handler must be a function/); }) }); diff --git a/test/app.routes.error.js b/test/app.routes.error.js index 7c49d50ffe2..e6cbdf4ce3e 100644 --- a/test/app.routes.error.js +++ b/test/app.routes.error.js @@ -48,7 +48,7 @@ describe('app', function(){ b.should.be.true; c.should.be.true; d.should.be.false; - res.send(204); + res.sendStatus(204); }); request(app) diff --git a/test/app.use.js b/test/app.use.js index b2031e4c56c..4a35c488e81 100644 --- a/test/app.use.js +++ b/test/app.use.js @@ -1,5 +1,6 @@ var after = require('after'); +var assert = require('assert'); var express = require('..'); var request = require('supertest'); @@ -255,15 +256,15 @@ describe('app', function(){ describe('.use(path, middleware)', function(){ it('should reject missing functions', function () { var app = express(); - app.use.bind(app, '/').should.throw(/requires middleware function/); + assert.throws(app.use.bind(app, '/'), /requires middleware function/); }) it('should reject non-functions as middleware', function () { var app = express(); - app.use.bind(app, '/', 'hi').should.throw(/requires middleware function.*string/); - app.use.bind(app, '/', 5).should.throw(/requires middleware function.*number/); - app.use.bind(app, '/', null).should.throw(/requires middleware function.*Null/); - app.use.bind(app, '/', new Date()).should.throw(/requires middleware function.*Date/); + assert.throws(app.use.bind(app, '/', 'hi'), /argument handler must be a function/); + assert.throws(app.use.bind(app, '/', 5), /argument handler must be a function/); + assert.throws(app.use.bind(app, '/', null), /argument handler must be a function/); + assert.throws(app.use.bind(app, '/', new Date()), /argument handler must be a function/); }) it('should strip path from req.url', function (done) { diff --git a/test/req.acceptsCharset.js b/test/req.acceptsCharset.js deleted file mode 100644 index 0d0ed8b5e41..00000000000 --- a/test/req.acceptsCharset.js +++ /dev/null @@ -1,49 +0,0 @@ - -var express = require('../') - , request = require('supertest'); - -describe('req', function(){ - describe('.acceptsCharset(type)', function(){ - describe('when Accept-Charset is not present', function(){ - it('should return true', function(done){ - var app = express(); - - app.use(function(req, res, next){ - res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no'); - }); - - request(app) - .get('/') - .expect('yes', done); - }) - }) - - describe('when Accept-Charset is not present', function(){ - it('should return true when present', function(done){ - var app = express(); - - app.use(function(req, res, next){ - res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no'); - }); - - request(app) - .get('/') - .set('Accept-Charset', 'foo, bar, utf-8') - .expect('yes', done); - }) - - it('should return false otherwise', function(done){ - var app = express(); - - app.use(function(req, res, next){ - res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no'); - }); - - request(app) - .get('/') - .set('Accept-Charset', 'foo, bar') - .expect('no', done); - }) - }) - }) -}) diff --git a/test/req.acceptsEncoding.js b/test/req.acceptsEncoding.js deleted file mode 100644 index 930b4ea76c1..00000000000 --- a/test/req.acceptsEncoding.js +++ /dev/null @@ -1,36 +0,0 @@ - -var express = require('../') - , request = require('supertest'); - -describe('req', function(){ - describe('.acceptsEncoding', function(){ - it('should be true if encoding accpeted', function(done){ - var app = express(); - - app.use(function(req, res){ - req.acceptsEncoding('gzip').should.be.ok; - req.acceptsEncoding('deflate').should.be.ok; - res.end(); - }); - - request(app) - .get('/') - .set('Accept-Encoding', ' gzip, deflate') - .expect(200, done); - }) - - it('should be false if encoding not accpeted', function(done){ - var app = express(); - - app.use(function(req, res){ - req.acceptsEncoding('bogus').should.not.be.ok; - res.end(); - }); - - request(app) - .get('/') - .set('Accept-Encoding', ' gzip, deflate') - .expect(200, done); - }) - }) -}) diff --git a/test/req.acceptsLanguage.js b/test/req.acceptsLanguage.js deleted file mode 100644 index 36afc47f929..00000000000 --- a/test/req.acceptsLanguage.js +++ /dev/null @@ -1,53 +0,0 @@ - -var express = require('../') - , request = require('supertest'); - -describe('req', function(){ - describe('.acceptsLanguage', function(){ - it('should be true if language accpeted', function(done){ - var app = express(); - - app.use(function(req, res){ - req.acceptsLanguage('en-us').should.be.ok; - req.acceptsLanguage('en').should.be.ok; - res.end(); - }); - - request(app) - .get('/') - .set('Accept-Language', 'en;q=.5, en-us') - .expect(200, done); - }) - - it('should be false if language not accpeted', function(done){ - var app = express(); - - app.use(function(req, res){ - req.acceptsLanguage('es').should.not.be.ok; - res.end(); - }); - - request(app) - .get('/') - .set('Accept-Language', 'en;q=.5, en-us') - .expect(200, done); - }) - - describe('when Accept-Language is not present', function(){ - it('should always return true', function(done){ - var app = express(); - - app.use(function(req, res){ - req.acceptsLanguage('en').should.be.ok; - req.acceptsLanguage('es').should.be.ok; - req.acceptsLanguage('jp').should.be.ok; - res.end(); - }); - - request(app) - .get('/') - .expect(200, done); - }) - }) - }) -}) diff --git a/test/req.host.js b/test/req.host.js index 8fa3409054f..4bbc1645401 100644 --- a/test/req.host.js +++ b/test/req.host.js @@ -28,7 +28,7 @@ describe('req', function(){ request(app) .post('/') .set('Host', 'example.com:3000') - .expect('example.com', done); + .expect(200, 'example.com:3000', done); }) it('should return undefined otherwise', function(done){ @@ -67,7 +67,7 @@ describe('req', function(){ request(app) .post('/') .set('Host', '[::1]:3000') - .expect('[::1]', done); + .expect(200, '[::1]:3000', done); }) describe('when "trust proxy" is enabled', function(){ diff --git a/test/req.param.js b/test/req.param.js deleted file mode 100644 index 1e827f03058..00000000000 --- a/test/req.param.js +++ /dev/null @@ -1,61 +0,0 @@ - -var express = require('../') - , request = require('supertest') - , bodyParser = require('body-parser') - -describe('req', function(){ - describe('.param(name, default)', function(){ - it('should use the default value unless defined', function(done){ - var app = express(); - - app.use(function(req, res){ - res.end(req.param('name', 'tj')); - }); - - request(app) - .get('/') - .expect('tj', done); - }) - }) - - describe('.param(name)', function(){ - it('should check req.query', function(done){ - var app = express(); - - app.use(function(req, res){ - res.end(req.param('name')); - }); - - request(app) - .get('/?name=tj') - .expect('tj', done); - }) - - it('should check req.body', function(done){ - var app = express(); - - app.use(bodyParser.json()); - - app.use(function(req, res){ - res.end(req.param('name')); - }); - - request(app) - .post('/') - .send({ name: 'tj' }) - .expect('tj', done); - }) - - it('should check req.params', function(done){ - var app = express(); - - app.get('/user/:name', function(req, res){ - res.end(req.param('filter') + req.param('name')); - }); - - request(app) - .get('/user/tj') - .expect('undefinedtj', done); - }) - }) -}) diff --git a/test/req.query.js b/test/req.query.js index d3d29abd16d..1caeaa6ac66 100644 --- a/test/req.query.js +++ b/test/req.query.js @@ -80,23 +80,6 @@ describe('req', function(){ }); }); - describe('when "query parser fn" is missing', function () { - it('should act like "extended"', function (done) { - var app = express(); - - delete app.settings['query parser']; - delete app.settings['query parser fn']; - - app.use(function (req, res) { - res.send(req.query); - }); - - request(app) - .get('/?user[name]=tj&user.name=tj') - .expect(200, '{"user":{"name":"tj"},"user.name":"tj"}', done); - }); - }); - describe('when "query parser" an unknown value', function () { it('should throw', function () { createApp.bind(null, 'bogus').should.throw(/unknown value.*query parser/); diff --git a/test/res.json.js b/test/res.json.js index 69f6723af54..9aab76e5ad4 100644 --- a/test/res.json.js +++ b/test/res.json.js @@ -160,32 +160,4 @@ describe('res', function(){ .expect(201, '{"id":1}', done) }) }) - - describe('.json(object, status)', function(){ - it('should respond with json and set the .statusCode for backwards compat', function(done){ - var app = express(); - - app.use(function(req, res){ - res.json({ id: 1 }, 201); - }); - - request(app) - .get('/') - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(201, '{"id":1}', done) - }) - - it('should use status as second number for backwards compat', function(done){ - var app = express(); - - app.use(function(req, res){ - res.json(200, 201); - }); - - request(app) - .get('/') - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(201, '200', done) - }) - }) }) diff --git a/test/res.jsonp.js b/test/res.jsonp.js index 64b41fb9a6b..6bf49d94aa2 100644 --- a/test/res.jsonp.js +++ b/test/res.jsonp.js @@ -301,34 +301,6 @@ describe('res', function(){ }) }) - describe('.jsonp(object, status)', function(){ - it('should respond with json and set the .statusCode for backwards compat', function(done){ - var app = express(); - - app.use(function(req, res){ - res.jsonp({ id: 1 }, 201); - }); - - request(app) - .get('/') - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(201, '{"id":1}', done) - }) - - it('should use status as second number for backwards compat', function(done){ - var app = express(); - - app.use(function(req, res){ - res.jsonp(200, 201); - }); - - request(app) - .get('/') - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(201, '200', done) - }) - }) - it('should not override previous Content-Types', function(done){ var app = express(); diff --git a/test/res.send.js b/test/res.send.js index 719424657c4..6b1203e0505 100644 --- a/test/res.send.js +++ b/test/res.send.js @@ -49,21 +49,6 @@ describe('res', function(){ }) }) - describe('.send(code)', function(){ - it('should set .statusCode', function(done){ - var app = express(); - - app.use(function(req, res){ - res.send(201).should.equal(res); - }); - - request(app) - .get('/') - .expect('Created') - .expect(201, done); - }) - }) - describe('.send(code, body)', function(){ it('should set .statusCode and body', function(done){ var app = express(); @@ -79,33 +64,33 @@ describe('res', function(){ }) }) - describe('.send(body, code)', function(){ - it('should be supported for backwards compat', function(done){ + describe('.send(code, number)', function(){ + it('should send number as json', function(done){ var app = express(); app.use(function(req, res){ - res.send('Bad!', 400); + res.send(200, 0.123); }); request(app) .get('/') - .expect('Bad!') - .expect(400, done); + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200, '0.123', done); }) }) - describe('.send(code, number)', function(){ - it('should send number as json', function(done){ + describe('.send(Number)', function(){ + it('should send as application/json', function(done){ var app = express(); app.use(function(req, res){ - res.send(200, 0.123); + res.send(1000); }); request(app) .get('/') .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200, '0.123', done); + .expect(200, '1000', done) }) }) @@ -412,7 +397,7 @@ describe('res', function(){ app.use(function (req, res) { res.set('etag', '"asdf"'); - res.send(200); + res.send('hello!'); }); app.enable('etag'); @@ -463,7 +448,7 @@ describe('res', function(){ app.use(function (req, res) { res.set('etag', '"asdf"'); - res.send(200); + res.send('hello!'); }); request(app) diff --git a/test/res.sendFile.js b/test/res.sendFile.js index 705c15a8b7e..3179581dc03 100644 --- a/test/res.sendFile.js +++ b/test/res.sendFile.js @@ -293,437 +293,8 @@ describe('res', function(){ .expect(200, 'to', done) }) }) - - describe('.sendfile(path, fn)', function(){ - it('should invoke the callback when complete', function(done){ - var app = express(); - var cb = after(2, done); - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.html', cb) - }); - - request(app) - .get('/') - .expect(200, cb); - }) - - it('should utilize the same options as express.static()', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.html', { maxAge: 60000 }); - }); - - request(app) - .get('/') - .expect('Cache-Control', 'public, max-age=60') - .end(done); - }) - - it('should invoke the callback when client aborts', function (done) { - var cb = after(1, done); - var app = express(); - - app.use(function (req, res) { - setImmediate(function () { - res.sendfile('test/fixtures/name.txt', function (err) { - should(err).be.ok; - err.code.should.equal('ECONNABORTED'); - cb(); - }); - }); - test.abort(); - }); - - var test = request(app).get('/'); - test.expect(200, cb); - }) - - it('should invoke the callback when client already aborted', function (done) { - var cb = after(1, done); - var app = express(); - - app.use(function (req, res) { - onFinished(res, function () { - res.sendfile('test/fixtures/name.txt', function (err) { - should(err).be.ok; - err.code.should.equal('ECONNABORTED'); - cb(); - }); - }); - test.abort(); - }); - - var test = request(app).get('/'); - test.expect(200, cb); - }) - - it('should invoke the callback without error when HEAD', function (done) { - var app = express(); - var cb = after(2, done); - - app.use(function (req, res) { - res.sendfile('test/fixtures/name.txt', cb); - }); - - request(app) - .head('/') - .expect(200, cb); - }); - - it('should invoke the callback without error when 304', function (done) { - var app = express(); - var cb = after(3, done); - - app.use(function (req, res) { - res.sendfile('test/fixtures/name.txt', cb); - }); - - request(app) - .get('/') - .expect('ETag', /^(?:W\/)?"[^"]+"$/) - .expect(200, 'tobi', function (err, res) { - if (err) return cb(err); - var etag = res.headers.etag; - request(app) - .get('/') - .set('If-None-Match', etag) - .expect(304, cb); - }); - }); - - it('should invoke the callback on 404', function(done){ - var app = express(); - var calls = 0; - - app.use(function(req, res){ - res.sendfile('test/fixtures/nope.html', function(err){ - assert.equal(calls++, 0); - assert(!res.headersSent); - res.send(err.message); - }); - }); - - request(app) - .get('/') - .expect(200, /^ENOENT.*?, stat/, done); - }) - - it('should not override manual content-types', function(done){ - var app = express(); - - app.use(function(req, res){ - res.contentType('txt'); - res.sendfile('test/fixtures/user.html'); - }); - - request(app) - .get('/') - .expect('Content-Type', 'text/plain; charset=utf-8') - .end(done); - }) - - it('should invoke the callback on 403', function(done){ - var app = express() - , calls = 0; - - app.use(function(req, res){ - res.sendfile('test/fixtures/foo/../user.html', function(err){ - assert(!res.headersSent); - ++calls; - res.send(err.message); - }); - }); - - request(app) - .get('/') - .expect('Forbidden') - .expect(200, done); - }) - - it('should invoke the callback on socket error', function(done){ - var app = express() - , calls = 0; - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.html', function(err){ - assert(!res.headersSent); - req.socket.listeners('error').should.have.length(1); // node's original handler - done(); - }); - - req.socket.emit('error', new Error('broken!')); - }); - - request(app) - .get('/') - .end(function(){}); - }) - }) - - describe('.sendfile(path)', function(){ - it('should not serve dotfiles', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('test/fixtures/.name'); - }); - - request(app) - .get('/') - .expect(404, done); - }) - - it('should accept dotfiles option', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('test/fixtures/.name', { dotfiles: 'allow' }); - }); - - request(app) - .get('/') - .expect(200, 'tobi', done); - }) - - it('should accept headers option', function(done){ - var app = express(); - var headers = { - 'x-success': 'sent', - 'x-other': 'done' - }; - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.html', { 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 app = express(); - var headers = { 'x-success': 'sent' }; - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.nothing', { headers: headers }); - }); - - request(app) - .get('/') - .expect(utils.shouldNotHaveHeader('X-Success')) - .expect(404, done); - }) - - it('should transfer a file', function (done) { - var app = express(); - - app.use(function (req, res) { - res.sendfile('test/fixtures/name.txt'); - }); - - request(app) - .get('/') - .expect(200, 'tobi', done); - }); - - it('should transfer a directory index file', function (done) { - var app = express(); - - app.use(function (req, res) { - res.sendfile('test/fixtures/blog/'); - }); - - request(app) - .get('/') - .expect(200, 'index', done); - }); - - it('should 404 for directory without trailing slash', function (done) { - var app = express(); - - app.use(function (req, res) { - res.sendfile('test/fixtures/blog'); - }); - - request(app) - .get('/') - .expect(404, done); - }); - - it('should transfer a file with urlencoded name', function (done) { - var app = express(); - - app.use(function (req, res) { - res.sendfile('test/fixtures/%25%20of%20dogs.txt'); - }); - - request(app) - .get('/') - .expect(200, '20%', done); - }); - - it('should not error if the client aborts', function (done) { - var cb = after(1, done); - var app = express(); - - app.use(function (req, res) { - setImmediate(function () { - res.sendfile(path.resolve(fixtures, 'name.txt')); - cb(); - }); - test.abort(); - }); - - app.use(function (err, req, res, next) { - err.code.should.be.empty; - cb(); - }); - - var test = request(app).get('/'); - test.expect(200, cb); - }) - - describe('with an absolute path', function(){ - it('should transfer the file', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile(__dirname + '/fixtures/user.html'); - }); - - request(app) - .get('/') - .expect('Content-Type', 'text/html; charset=UTF-8') - .expect(200, '

{{user.name}}

', done); - }) - }) - - describe('with a relative path', function(){ - it('should transfer the file', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.html'); - }); - - request(app) - .get('/') - .expect('Content-Type', 'text/html; charset=UTF-8') - .expect(200, '

{{user.name}}

', done); - }) - - it('should serve relative to "root"', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('user.html', { root: 'test/fixtures/' }); - }); - - request(app) - .get('/') - .expect('Content-Type', 'text/html; charset=UTF-8') - .expect(200, '

{{user.name}}

', done); - }) - - it('should consider ../ malicious when "root" is not set', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('test/fixtures/foo/../user.html'); - }); - - request(app) - .get('/') - .expect(403, done); - }) - - it('should allow ../ when "root" is set', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('foo/../user.html', { root: 'test/fixtures' }); - }); - - request(app) - .get('/') - .expect(200, done); - }) - - it('should disallow requesting out of "root"', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('foo/../../user.html', { root: 'test/fixtures' }); - }); - - request(app) - .get('/') - .expect(403, done); - }) - - it('should next(404) when not found', function(done){ - var app = express() - , calls = 0; - - app.use(function(req, res){ - res.sendfile('user.html'); - }); - - app.use(function(req, res){ - assert(0, 'this should not be called'); - }); - - app.use(function(err, req, res, next){ - ++calls; - next(err); - }); - - request(app) - .get('/') - .end(function(err, res){ - res.statusCode.should.equal(404); - calls.should.equal(1); - done(); - }); - }) - - describe('with non-GET', function(){ - it('should still serve', function(done){ - var app = express() - , calls = 0; - - app.use(function(req, res){ - res.sendfile(__dirname + '/fixtures/name.txt'); - }); - - request(app) - .get('/') - .expect('tobi', done); - }) - }) - }) - }) }) - describe('.sendfile(path, options)', function () { - it('should pass options to send module', function (done) { - var app = express() - - app.use(function (req, res) { - res.sendfile(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 }) - }) - - request(app) - .get('/') - .expect(200, 'to', done) - }) - }) - function createApp(path, options, fn) { var app = express(); diff --git a/test/utils.js b/test/utils.js index b6731962601..62d34bd105d 100644 --- a/test/utils.js +++ b/test/utils.js @@ -69,28 +69,3 @@ describe('utils.wetag(body, encoding)', function(){ .should.eql('W/"0-1B2M2Y8AsgTpgAmY7PhCfg"'); }) }) - -describe('utils.isAbsolute()', function(){ - it('should support windows', function(){ - assert(utils.isAbsolute('c:\\')); - assert(utils.isAbsolute('c:/')); - assert(!utils.isAbsolute(':\\')); - }) - - it('should support windows unc', function(){ - assert(utils.isAbsolute('\\\\foo\\bar')) - }) - - it('should support unices', function(){ - assert(utils.isAbsolute('/foo/bar')); - assert(!utils.isAbsolute('foo/bar')); - }) -}) - -describe('utils.flatten(arr)', function(){ - it('should flatten an array', function(){ - var arr = ['one', ['two', ['three', 'four'], 'five']]; - utils.flatten(arr) - .should.eql(['one', 'two', 'three', 'four', 'five']); - }) -})