Compare commits

...

68 Commits

Author SHA1 Message Date
Douglas Christopher Wilson
4d8093302f 4.10.5 2014-12-10 23:53:31 -05:00
Douglas Christopher Wilson
4370908674 deps: type-is@~1.5.4 2014-12-10 23:50:58 -05:00
Douglas Christopher Wilson
f0b679d02d deps: accepts@~1.1.4 2014-12-10 23:49:34 -05:00
Douglas Christopher Wilson
c24463d829 Fix res.send double-calling res.end for HEAD requests
fixes #2467
2014-12-10 12:27:08 -05:00
Douglas Christopher Wilson
656e214937 4.10.4 2014-11-25 00:17:24 -05:00
Douglas Christopher Wilson
4b26bbde2d Fix res.sendFile logging standard write errors
closes #2451
2014-11-24 23:28:37 -05:00
Douglas Christopher Wilson
7fcc8b190d 4.10.3 2014-11-23 18:48:55 -05:00
Douglas Christopher Wilson
b326ae89df Fix res.sendFile logging standard write errors
fixes #2433
2014-11-23 16:53:11 -05:00
Douglas Christopher Wilson
869ddd775c deps: update example dependencies 2014-11-23 15:58:24 -05:00
Douglas Christopher Wilson
997fd74e8c deps: qs@2.3.3 2014-11-23 15:56:59 -05:00
Douglas Christopher Wilson
e3e41a1118 Merge tag '3.18.4' 2014-11-23 15:54:20 -05:00
Douglas Christopher Wilson
6c8bcd5c4e 3.18.4 2014-11-23 15:42:03 -05:00
Douglas Christopher Wilson
95e63ec287 deps: should@~4.3.0 2014-11-23 15:36:59 -05:00
Douglas Christopher Wilson
ebca5887cc deps: proxy-addr@~1.0.4 2014-11-23 15:34:57 -05:00
Douglas Christopher Wilson
8535d3a990 deps: supertest@~0.15.0 2014-11-23 14:58:56 -05:00
Douglas Christopher Wilson
13184c4379 deps: etag@~1.5.1 2014-11-23 14:58:22 -05:00
Douglas Christopher Wilson
eaba4eeb70 deps: connect@2.27.4 2014-11-23 14:53:23 -05:00
Douglas Christopher Wilson
ac56cf4606 4.10.2 2014-11-09 19:09:24 -05:00
Josemar Magalhaes
6dea32cd18 examples: add multi router example
closes #2434
2014-11-09 19:01:13 -05:00
Douglas Christopher Wilson
5fab60bc6c Correctly invoke async router callback asynchronously 2014-11-09 18:50:00 -05:00
Douglas Christopher Wilson
881e1ba660 deps: type-is@~1.5.3 2014-11-09 18:42:33 -05:00
Douglas Christopher Wilson
0e488c19df deps: accepts@~1.1.3 2014-11-09 18:41:01 -05:00
Douglas Christopher Wilson
2262a18900 Merge tag '3.18.3' 2014-11-09 18:39:46 -05:00
Douglas Christopher Wilson
28c6952d1c 3.18.3 2014-11-09 18:35:34 -05:00
Douglas Christopher Wilson
01e3530a31 deps: should@~4.2.1 2014-11-09 18:33:17 -05:00
Douglas Christopher Wilson
77c83d0c57 deps: connect@2.27.3 2014-11-09 18:32:43 -05:00
Douglas Christopher Wilson
b4eaf89186 examples: remove invalid cors example
Not only is the example not even standards-compliant, but it
encourages bad security settings and practices.
2014-11-06 21:14:02 -05:00
Douglas Christopher Wilson
e4debea297 Remove unused source file 2014-11-01 14:09:12 -04:00
Jaime Agudo
1c96e18d20 docs: fix Gratipay links
fixes #2424
2014-11-01 00:09:54 -04:00
Douglas Christopher Wilson
8bb013ec95 4.10.1 2014-10-29 01:15:58 -04:00
Douglas Christopher Wilson
6c0031bfd8 deps: qs@2.3.2 2014-10-29 01:14:44 -04:00
Douglas Christopher Wilson
ab6c189504 Merge tag '3.18.2' 2014-10-29 01:14:04 -04:00
Douglas Christopher Wilson
a12ae729bd 3.18.2 2014-10-29 01:10:45 -04:00
Douglas Christopher Wilson
d53a0cd91e deps: connect@2.27.2 2014-10-29 01:08:36 -04:00
Aria Stewart
eabd4564aa Fix handling of URLs containing :// in the path
fixes #2421
2014-10-29 00:33:02 -04:00
Douglas Christopher Wilson
d40dc651f3 4.10.0 2014-10-23 22:27:45 -04:00
Douglas Christopher Wilson
68290ee87a Fix handling of invalid empty URLs
fixes #2399
2014-10-23 21:33:38 -04:00
Douglas Christopher Wilson
6614352563 Add support for app.set('views', array)
closes #2320
2014-10-23 17:28:53 -04:00
Douglas Christopher Wilson
0e5f2f84ea Use path.resolve in view lookup 2014-10-23 15:55:17 -04:00
Douglas Christopher Wilson
b1d0c19ca1 examples: make main app file names consistent
fixes #2408
2014-10-23 02:39:38 -04:00
lemmy
dfa7ee4732 Pass context to .forEach instead of closure
Has a slight performance improvement

closes #2347
2014-10-23 02:30:09 -04:00
Douglas Christopher Wilson
e9539fc780 docs: visionmedia is now tj on Github 2014-10-23 02:20:51 -04:00
Fishrock123
5f7a37ee51 docs: misc. tweaks
closes #2394
2014-10-23 02:18:24 -04:00
Douglas Christopher Wilson
ff3a368b2f deps: update example dependencies 2014-10-23 02:08:34 -04:00
Douglas Christopher Wilson
ccc45a74f8 Merge tag '3.18.1' 2014-10-23 02:06:20 -04:00
Douglas Christopher Wilson
cd9d2ec6a9 deps: on-finished@~2.1.1 2014-10-23 01:45:58 -04:00
Douglas Christopher Wilson
ce7bbae007 deps: serve-static@~1.7.1 2014-10-23 01:43:19 -04:00
Douglas Christopher Wilson
6a5dd52deb deps: qs@2.3.0 2014-10-23 01:38:48 -04:00
Douglas Christopher Wilson
6c2f7fb48d deps: finalhandler@0.3.2 2014-10-23 01:37:00 -04:00
Douglas Christopher Wilson
4dd970578a Fix res.send to mention res.sendStatus 2014-10-23 01:35:16 -04:00
Douglas Christopher Wilson
88dfd36eaa 3.18.1 2014-10-23 01:26:21 -04:00
Douglas Christopher Wilson
5759b3e9f5 deps: send@0.10.1 2014-10-23 01:25:07 -04:00
Douglas Christopher Wilson
c939a771c0 deps: connect@2.27.1 2014-10-23 01:23:54 -04:00
Douglas Christopher Wilson
af1043844f Fix internal utils.merge deprecation warnings 2014-10-22 15:18:10 -04:00
Douglas Christopher Wilson
dd763ec5b8 deps: should@~4.1.0 2014-10-22 15:10:22 -04:00
Douglas Christopher Wilson
9c2c21aaaf deps: mocha@~2.0.0 2014-10-22 15:08:49 -04:00
Douglas Christopher Wilson
366000184f 3.18.0 2014-10-18 00:57:48 -04:00
Douglas Christopher Wilson
4d1ee23f84 Use etag module to generate ETag headers 2014-10-18 00:53:17 -04:00
Douglas Christopher Wilson
6f31218ecc Use content-disposition module 2014-10-17 23:45:58 -04:00
Douglas Christopher Wilson
6cd4859035 deps: finalhandler@0.3.1 2014-10-17 22:37:52 -04:00
Douglas Christopher Wilson
4f1cd4f73c deps: etag@~1.5.0 2014-10-17 22:37:49 -04:00
Douglas Christopher Wilson
0e5613363f Use content-disposition module 2014-10-17 21:08:05 -04:00
Fishrock123
7a7f18c20b build: misc. updates to packaging
closes #2398
2014-10-17 20:49:56 -04:00
Douglas Christopher Wilson
b766aad112 deps: jade@~1.7.0 2014-10-17 20:45:57 -04:00
Douglas Christopher Wilson
7488e27609 deps: send@0.10.0 2014-10-17 20:45:09 -04:00
Douglas Christopher Wilson
bc9d854763 deps: depd@~1.0.0 2014-10-17 20:44:07 -04:00
Douglas Christopher Wilson
2e20a85810 deps: debug@~2.1.0 2014-10-17 20:43:23 -04:00
Douglas Christopher Wilson
a706408208 deps: connect@2.27.0 2014-10-17 20:42:12 -04:00
40 changed files with 678 additions and 300 deletions

38
.gitignore vendored
View File

@@ -1,16 +1,26 @@
coverage/
.DS_Store
*.seed
# OS X
.DS_Store*
Icon?
._*
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Linux
.directory
*~
# npm
node_modules
*.log
*.csv
*.dat
*.out
*.pid
*.swp
*.swo
*.gz
# Coveralls
coverage
# Benchmarking
benchmarks/graphs
testing
node_modules/
testing
test.js
.idea

View File

@@ -1,11 +0,0 @@
.git*
benchmarks/
coverage/
docs/
examples/
support/
test/
testing.js
.DS_Store
.travis.yml
Contributing.md

View File

@@ -1,3 +1,77 @@
4.10.5 / 2014-12-10
===================
* Fix `res.send` double-calling `res.end` for `HEAD` requests
* deps: accepts@~1.1.4
- deps: mime-types@~2.0.4
* deps: type-is@~1.5.4
- deps: mime-types@~2.0.4
4.10.4 / 2014-11-24
===================
* Fix `res.sendfile` logging standard write errors
4.10.3 / 2014-11-23
===================
* Fix `res.sendFile` logging standard write errors
* deps: etag@~1.5.1
* deps: proxy-addr@~1.0.4
- deps: ipaddr.js@0.1.5
* deps: qs@2.3.3
- Fix `arrayLimit` behavior
4.10.2 / 2014-11-09
===================
* Correctly invoke async router callback asynchronously
* deps: accepts@~1.1.3
- deps: mime-types@~2.0.3
* deps: type-is@~1.5.3
- deps: mime-types@~2.0.3
4.10.1 / 2014-10-28
===================
* Fix handling of URLs containing `://` in the path
* deps: qs@2.3.2
- Fix parsing of mixed objects and values
4.10.0 / 2014-10-23
===================
* Add support for `app.set('views', array)`
- Views are looked up in sequence in array of directories
* Fix `res.send(status)` to mention `res.sendStatus(status)`
* Fix handling of invalid empty URLs
* Use `content-disposition` module for `res.attachment`/`res.download`
- Sends standards-compliant `Content-Disposition` header
- Full Unicode support
* Use `path.resolve` in view lookup
* deps: debug@~2.1.0
- Implement `DEBUG_FD` env variable support
* deps: depd@~1.0.0
* deps: etag@~1.5.0
- Improve string performance
- Slightly improve speed for weak ETags over 1KB
* deps: finalhandler@0.3.2
- Terminate in progress response only on error
- Use `on-finished` to determine request status
- deps: debug@~2.1.0
- deps: on-finished@~2.1.1
* deps: on-finished@~2.1.1
- Fix handling of pipelined requests
* deps: qs@2.3.0
- Fix parsing of mixed implicit and explicit arrays
* deps: send@0.10.1
- deps: debug@~2.1.0
- deps: depd@~1.0.0
- deps: etag@~1.5.0
- deps: on-finished@~2.1.1
* deps: serve-static@~1.7.1
- deps: send@0.10.1
4.9.8 / 2014-10-17
==================
@@ -534,6 +608,82 @@
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route
- Router & Route - public API
3.18.4 / 2014-11-23
===================
* deps: connect@2.27.4
- deps: body-parser@~1.9.3
- deps: compression@~1.2.1
- deps: errorhandler@~1.2.3
- deps: express-session@~1.9.2
- deps: qs@2.3.3
- deps: serve-favicon@~2.1.7
- deps: serve-static@~1.5.1
- deps: type-is@~1.5.3
* deps: etag@~1.5.1
* deps: proxy-addr@~1.0.4
- deps: ipaddr.js@0.1.5
3.18.3 / 2014-11-09
===================
* deps: connect@2.27.3
- Correctly invoke async callback asynchronously
- deps: csurf@~1.6.3
3.18.2 / 2014-10-28
===================
* deps: connect@2.27.2
- Fix handling of URLs containing `://` in the path
- deps: body-parser@~1.9.2
- deps: qs@2.3.2
3.18.1 / 2014-10-22
===================
* Fix internal `utils.merge` deprecation warnings
* deps: connect@2.27.1
- deps: body-parser@~1.9.1
- deps: express-session@~1.9.1
- deps: finalhandler@0.3.2
- deps: morgan@~1.4.1
- deps: qs@2.3.0
- deps: serve-static@~1.7.1
* deps: send@0.10.1
- deps: on-finished@~2.1.1
3.18.0 / 2014-10-17
===================
* Use `content-disposition` module for `res.attachment`/`res.download`
- Sends standards-compliant `Content-Disposition` header
- Full Unicode support
* Use `etag` module to generate `ETag` headers
* deps: connect@2.27.0
- Use `http-errors` module for creating errors
- Use `utils-merge` module for merging objects
- deps: body-parser@~1.9.0
- deps: compression@~1.2.0
- deps: connect-timeout@~1.4.0
- deps: debug@~2.1.0
- deps: depd@~1.0.0
- deps: express-session@~1.9.0
- deps: finalhandler@0.3.1
- deps: method-override@~2.3.0
- deps: morgan@~1.4.0
- deps: response-time@~2.2.0
- deps: serve-favicon@~2.1.6
- deps: serve-index@~1.5.0
- deps: serve-static@~1.7.0
* deps: debug@~2.1.0
- Implement `DEBUG_FD` env variable support
* deps: depd@~1.0.0
* deps: send@0.10.0
- deps: debug@~2.1.0
- deps: depd@~1.0.0
- deps: etag@~1.5.0
3.17.8 / 2014-10-15
===================

View File

@@ -2,10 +2,10 @@
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
[![NPM Version](https://img.shields.io/npm/v/express.svg?style=flat)](https://www.npmjs.org/package/express)
[![Build Status](https://img.shields.io/travis/strongloop/express.svg?style=flat)](https://travis-ci.org/strongloop/express)
[![Coverage Status](https://img.shields.io/coveralls/strongloop/express.svg?style=flat)](https://coveralls.io/r/strongloop/express)
[![Gittip](https://img.shields.io/gittip/dougwilson.svg?style=flat)](https://www.gittip.com/dougwilson/)
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url]
```js
var express = require('express')
@@ -18,14 +18,34 @@ app.get('/', function (req, res) {
app.listen(3000)
```
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/strongloop/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/strongloop/express/wiki/New-features-in-4.x).
### Installation
```bash
$ npm install express
```
## Features
* Robust routing
* Focus on high performance
* Super-high test coverage
* HTTP helpers (redirection, caching, etc)
* View system supporting 14+ template engines
* Content negotiation
* Executable for generating applications quickly
## Docs & Community
* [Website and Documentation](http://expressjs.com/) - [[website repo](https://github.com/strongloop/expressjs.com)]
* [#express](https://webchat.freenode.net/?channels=express) on freenode IRC
* [Github Organization](https://github.com/expressjs) for Official Middleware & Modules
* Visit the [Wiki](https://github.com/strongloop/express/wiki)
* [Google Group](https://groups.google.com/group/express-js) for discussion
* [Русскоязычная документация](http://jsman.ru/express/)
* [한국어 문서](http://expressjs.kr) - [[website repo](https://github.com/Hanul/expressjs.kr)]
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/strongloop/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/strongloop/express/wiki/New-features-in-4.x).
## Quick Start
The quickest way to get started with express is to utilize the executable [`express(1)`](https://github.com/expressjs/generator) to generate an application as shown below:
@@ -54,16 +74,6 @@ $ npm install
$ npm start
```
## Features
* Robust routing
* HTTP helpers (redirection, caching, etc)
* View system supporting 14+ template engines
* Content negotiation
* Focus on high performance
* Executable for generating applications quickly
* High test coverage
## Philosophy
The Express philosophy is to provide small, robust tooling for HTTP servers, making
@@ -71,23 +81,12 @@ $ npm start
HTTP APIs.
Express does not force you to use any specific ORM or template engine. With support for over
14 template engines via [Consolidate.js](https://github.com/visionmedia/consolidate.js),
14 template engines via [Consolidate.js](https://github.com/tj/consolidate.js),
you can quickly craft your perfect framework.
## More Information
## Examples
* [Website and Documentation](http://expressjs.com/) - [[website repo](https://github.com/strongloop/expressjs.com)]
* [Github Organization](https://github.com/expressjs) for Official Middleware & Modules
* [#express](https://webchat.freenode.net/?channels=express) on freenode IRC
* Visit the [Wiki](https://github.com/strongloop/express/wiki)
* [Google Group](https://groups.google.com/group/express-js) for discussion
* [Русскоязычная документация](http://jsman.ru/express/)
* [한국어 문서](http://expressjs.kr) - [[website repo](https://github.com/Hanul/expressjs.kr)]
* Run express examples [online](https://runnable.com/express)
## Viewing Examples
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
To view the examples, clone the Express repo & install the dependancies:
```bash
$ git clone git://github.com/strongloop/express.git --depth 1
@@ -97,32 +96,40 @@ $ npm install
Then run whichever example you want:
$ node examples/content-negotiation
```bash
$ node examples/content-negotiation
```
You can also view live examples here:
## Tests
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
## Running Tests
To run the test suite, first invoke the following command within the repo, installing the development dependencies:
To run the test suite, first install the dependancies, then run `npm test`:
```bash
$ npm install
```
Then run the tests:
```bash
$ npm test
```
### Contributors
### People
* Author: [TJ Holowaychuk](https://github.com/visionmedia)
* Lead Maintainer: [Douglas Christopher Wilson](https://github.com/dougwilson)
* [All Contributors](https://github.com/strongloop/express/graphs/contributors)
The original author of Express is [TJ Holowaychuk](https://github.com/tj) [![TJ's Gratipay][gratipay-image-visionmedia]][gratipay-url-visionmedia]
The current lead maintainer is [Douglas Christopher Wilson](https://github.com/dougwilson) [![Doug's Gratipay][gratipay-image-dougwilson]][gratipay-url-dougwilson]
[List of all contributors](https://github.com/strongloop/express/graphs/contributors)
### License
[MIT](LICENSE)
[npm-image]: https://img.shields.io/npm/v/express.svg?style=flat
[npm-url]: https://npmjs.org/package/express
[downloads-image]: https://img.shields.io/npm/dm/express.svg?style=flat
[downloads-url]: https://npmjs.org/package/express
[travis-image]: https://img.shields.io/travis/strongloop/express.svg?style=flat
[travis-url]: https://travis-ci.org/strongloop/express
[coveralls-image]: https://img.shields.io/coveralls/strongloop/express.svg?style=flat
[coveralls-url]: https://coveralls.io/r/strongloop/express?branch=master
[gratipay-image-visionmedia]: https://img.shields.io/gratipay/visionmedia.svg?style=flat
[gratipay-url-visionmedia]: https://gratipay.com/visionmedia/
[gratipay-image-dougwilson]: https://img.shields.io/gratipay/dougwilson.svg?style=flat
[gratipay-url-dougwilson]: https://gratipay.com/dougwilson/

View File

@@ -1,5 +1,5 @@
// check out https://github.com/visionmedia/node-pwd
// check out https://github.com/tj/node-pwd
/**
* Module dependencies.

View File

@@ -1,48 +0,0 @@
/**
* Module dependencies.
*/
var express = require('../..');
var logger = require('morgan');
var app = express();
var bodyParser = require('body-parser');
var api = express();
// app middleware
app.use(express.static(__dirname + '/public'));
// api middleware
api.use(logger('dev'));
api.use(bodyParser.json());
/**
* CORS support.
*/
api.all('*', function(req, res, next){
if (!req.get('Origin')) return next();
// use "*" here to accept any origin
res.set('Access-Control-Allow-Origin', 'http://localhost:3000');
res.set('Access-Control-Allow-Methods', 'PUT');
res.set('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
// res.set('Access-Control-Allow-Max-Age', 3600);
if ('OPTIONS' == req.method) return res.send(200);
next();
});
/**
* PUT an existing user.
*/
api.put('/user/:id', function(req, res){
console.log(req.body);
res.send(204);
});
app.listen(3000);
api.listen(3001);
console.log('app listening on 3000');
console.log('api listening on 3001');

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<script>
var req = new XMLHttpRequest;
req.open('PUT', 'http://localhost:3001/user/1', false);
req.setRequestHeader('Content-Type', 'application/json');
req.send('{"name":"tobi","species":"ferret"}');
console.log(req.responseText);
</script>
</body>
</html>

View File

@@ -0,0 +1,13 @@
var express = require('../../..');
var apiv1 = express.Router();
apiv1.get('/', function(req, res) {
res.send('Hello from APIv1 root route.');
});
apiv1.get('/users', function(req, res) {
res.send('List of APIv1 users.');
});
module.exports = apiv1;

View File

@@ -0,0 +1,13 @@
var express = require('../../..');
var apiv2 = express.Router();
apiv2.get('/', function(req, res) {
res.send('Hello from APIv2 root route.');
});
apiv2.get('/users', function(req, res) {
res.send('List of APIv2 users.');
});
module.exports = apiv2;

View File

@@ -0,0 +1,16 @@
var express = require('../..');
var app = module.exports = express();
app.use('/api/v1', require('./controllers/api_v1'));
app.use('/api/v2', require('./controllers/api_v2'));
app.get('/', function(req, res) {
res.send('Hello form root route.');
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -4,7 +4,6 @@
var finalhandler = require('finalhandler');
var flatten = require('./utils').flatten;
var mixin = require('utils-merge');
var Router = require('./router');
var methods = require('methods');
var middleware = require('./middleware/init');
@@ -16,6 +15,7 @@ var compileETag = require('./utils').compileETag;
var compileQueryParser = require('./utils').compileQueryParser;
var compileTrust = require('./utils').compileTrust;
var deprecate = require('depd')('express');
var merge = require('utils-merge');
var resolve = require('path').resolve;
var slice = Array.prototype.slice;
@@ -154,7 +154,6 @@ app.handle = function(req, res, done) {
app.use = function use(fn) {
var offset = 0;
var path = '/';
var self = this;
// default path to '/'
// disambiguate app.use([fn])
@@ -190,7 +189,7 @@ app.use = function use(fn) {
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = self;
fn.parent = this;
// restore .app property on req and res
router.use(path, function mounted_app(req, res, next) {
@@ -203,8 +202,8 @@ app.use = function use(fn) {
});
// mounted an app
fn.emit('mount', self);
});
fn.emit('mount', this);
}, this);
return this;
};
@@ -247,7 +246,7 @@ app.route = function(path){
* so if you're using ".ejs" extensions you dont need to do anything.
*
* Some template engines do not follow this convention, the
* [Consolidate.js](https://github.com/visionmedia/consolidate.js)
* [Consolidate.js](https://github.com/tj/consolidate.js)
* library was created to map all of node's popular template
* engines to follow this convention, thus allowing them to
* work seamlessly within Express.
@@ -278,17 +277,16 @@ app.engine = function(ext, fn){
*/
app.param = function(name, fn){
var self = this;
self.lazyrouter();
this.lazyrouter();
if (Array.isArray(name)) {
name.forEach(function(key) {
self.param(key, fn);
});
this.param(key, fn);
}, this);
return this;
}
self._router.param(name, fn);
this._router.param(name, fn);
return this;
};
@@ -488,13 +486,15 @@ app.render = function(name, options, fn){
}
// merge app.locals
mixin(opts, this.locals);
merge(opts, this.locals);
// merge options._locals
if (options._locals) mixin(opts, options._locals);
if (options._locals) {
merge(opts, options._locals);
}
// merge options
mixin(opts, options);
merge(opts, options);
// set .cache unless explicitly provided
opts.cache = null == opts.cache
@@ -513,7 +513,10 @@ app.render = function(name, options, fn){
});
if (!view.path) {
var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"');
var dirs = Array.isArray(view.root) && view.root.length > 1
? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
: 'directory "' + view.root + '"'
var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
err.view = view;
return fn(err);
}

View File

@@ -3,7 +3,7 @@
*/
var EventEmitter = require('events').EventEmitter;
var mixin = require('utils-merge');
var mixin = require('merge-descriptors');
var proto = require('./application');
var Route = require('./router/route');
var Router = require('./router');

View File

@@ -2,18 +2,18 @@
* Module dependencies.
*/
var contentDisposition = require('content-disposition');
var deprecate = require('depd')('express');
var escapeHtml = require('escape-html');
var http = require('http');
var isAbsolute = require('./utils').isAbsolute;
var onFinished = require('on-finished');
var path = require('path');
var mixin = require('utils-merge');
var merge = require('utils-merge');
var sign = require('cookie-signature').sign;
var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
var setCharset = require('./utils').setCharset;
var contentDisposition = require('./utils').contentDisposition;
var statusCodes = http.STATUS_CODES;
var cookie = require('cookie');
var send = require('send');
@@ -109,7 +109,7 @@ res.send = function send(body) {
this.type('txt');
}
deprecate('res.send(status): Use res.status(status).end() instead');
deprecate('res.send(status): Use res.sendStatus(status) instead');
this.statusCode = chunk;
chunk = http.STATUS_CODES[chunk];
}
@@ -182,14 +182,14 @@ res.send = function send(body) {
chunk = '';
}
// skip body for HEAD
if (isHead) {
// skip body for HEAD
this.end();
} else {
// respond
this.end(chunk, encoding);
}
// respond
this.end(chunk, encoding);
return this;
};
@@ -398,8 +398,8 @@ res.sendFile = function sendFile(path, options, fn) {
if (fn) return fn(err);
if (err && err.code === 'EISDIR') return next();
// next() all but aborted errors
if (err && err.code !== 'ECONNABORT') {
// next() all but write errors
if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') {
next(err);
}
});
@@ -467,8 +467,8 @@ res.sendfile = function(path, options, fn){
if (fn) return fn(err);
if (err && err.code === 'EISDIR') return next();
// next() all but aborted errors
if (err && err.code !== 'ECONNABORT') {
// next() all but write errors
if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') {
next(err);
}
});
@@ -626,9 +626,13 @@ res.format = function(obj){
* @api public
*/
res.attachment = function(filename){
if (filename) this.type(extname(filename));
res.attachment = function attachment(filename) {
if (filename) {
this.type(extname(filename));
}
this.set('Content-Disposition', contentDisposition(filename));
return this;
};
@@ -692,7 +696,7 @@ res.get = function(field){
res.clearCookie = function(name, options){
var opts = { expires: new Date(1), path: '/' };
return this.cookie(name, '', options
? mixin(opts, options)
? merge(opts, options)
: opts);
};
@@ -721,7 +725,7 @@ res.clearCookie = function(name, options){
*/
res.cookie = function(name, val, options){
options = mixin({}, options);
options = merge({}, options);
var secret = this.req.secret;
var signed = options.signed;
if (signed && !secret) throw new Error('cookieParser("secret") required for signed cookies');

View File

@@ -126,7 +126,7 @@ proto.handle = function(req, res, done) {
var search = 1 + req.url.indexOf('?');
var pathlength = search ? search - 1 : req.url.length;
var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://');
var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://');
var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
var idx = 0;
var removed = '';
@@ -183,7 +183,8 @@ proto.handle = function(req, res, done) {
}
if (!layer) {
return done(layerError);
setImmediate(done, layerError);
return;
}
self.match_layer(layer, req, res, function (err, path) {
@@ -409,7 +410,6 @@ proto.process_params = function(layer, called, req, res, done) {
proto.use = function use(fn) {
var offset = 0;
var path = '/';
var self = this;
// default path to '/'
// disambiguate router.use([fn])
@@ -442,15 +442,15 @@ proto.use = function use(fn) {
debug('use %s %s', path, fn.name || '<anonymous>');
var layer = new Layer(path, {
sensitive: self.caseSensitive,
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined;
self.stack.push(layer);
});
this.stack.push(layer);
}, this);
return this;
};

View File

@@ -95,6 +95,13 @@ Layer.prototype.handle_request = function handle(req, res, next) {
*/
Layer.prototype.match = function match(path) {
if (path == null) {
// no path, nothing matches
this.params = undefined;
this.path = undefined;
return false;
}
if (this.regexp.fast_slash) {
// fast path non-ending match for / (everything matches)
this.params = {};

View File

@@ -1,56 +0,0 @@
/**
* Expose `Layer`.
*/
module.exports = Match;
function Match(layer, path, params) {
this.layer = layer;
this.params = {};
this.path = path || '';
if (!params) {
return this;
}
var keys = layer.keys;
var n = 0;
var prop;
var key;
var val;
for (var i = 0; i < params.length; i++) {
key = keys[i];
val = decode_param(params[i]);
prop = key
? key.name
: n++;
this.params[prop] = val;
}
return this;
};
/**
* Decode param value.
*
* @param {string} val
* @return {string}
* @api private
*/
function decode_param(val){
if (typeof val !== 'string') {
return val;
}
try {
return decodeURIComponent(val);
} catch (e) {
var err = new TypeError("Failed to decode param '" + val + "'");
err.status = 400;
throw err;
}
}

View File

@@ -131,7 +131,6 @@ Route.prototype.dispatch = function(req, res, done){
*/
Route.prototype.all = function(){
var self = this;
var callbacks = utils.flatten([].slice.call(arguments));
callbacks.forEach(function(fn) {
if (typeof fn !== 'function') {
@@ -143,16 +142,15 @@ Route.prototype.all = function(){
var layer = Layer('/', {}, fn);
layer.method = undefined;
self.methods._all = true;
self.stack.push(layer);
});
this.methods._all = true;
this.stack.push(layer);
}, this);
return self;
return this;
};
methods.forEach(function(method){
Route.prototype[method] = function(){
var self = this;
var callbacks = utils.flatten([].slice.call(arguments));
callbacks.forEach(function(fn) {
@@ -162,14 +160,14 @@ methods.forEach(function(method){
throw new Error(msg);
}
debug('%s %s', method, self.path);
debug('%s %s', method, this.path);
var layer = Layer('/', {}, fn);
layer.method = method;
self.methods[method] = true;
self.stack.push(layer);
});
return self;
this.methods[method] = true;
this.stack.push(layer);
}, this);
return this;
};
});

View File

@@ -2,6 +2,8 @@
* Module dependencies.
*/
var contentDisposition = require('content-disposition');
var deprecate = require('depd')('express');
var mime = require('send').mime;
var basename = require('path').basename;
var etag = require('etag');
@@ -22,9 +24,9 @@ var typer = require('media-typer');
exports.etag = function (body, encoding) {
var buf = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
: body
: body;
return etag(buf, {weak: false})
return etag(buf, {weak: false});
};
/**
@@ -39,9 +41,9 @@ exports.etag = function (body, encoding) {
exports.wetag = function wetag(body, encoding){
var buf = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
: body
: body;
return etag(buf, {weak: true})
return etag(buf, {weak: true});
};
/**
@@ -120,18 +122,8 @@ exports.normalizeTypes = function(types){
* @api private
*/
exports.contentDisposition = function(filename){
var ret = 'attachment';
if (filename) {
filename = basename(filename);
// if filename contains non-ascii characters, add a utf-8 version ala RFC 5987
ret = /[^\040-\176]/.test(filename)
? 'attachment; filename="' + encodeURI(filename) + '"; filename*=UTF-8\'\'' + encodeURI(filename)
: 'attachment; filename="' + filename + '"';
}
return ret;
};
exports.contentDisposition = deprecate.function(contentDisposition,
'utils.contentDisposition: use content-disposition npm module instead');
/**
* Parse accept params `str` returning an

View File

@@ -2,14 +2,21 @@
* Module dependencies.
*/
var debug = require('debug')('express:view');
var path = require('path');
var fs = require('fs');
var utils = require('./utils');
/**
* Module variables.
* @private
*/
var dirname = path.dirname;
var basename = path.basename;
var extname = path.extname;
var exists = fs.existsSync || path.existsSync;
var join = path.join;
var resolve = path.resolve;
/**
* Expose `View`.
@@ -45,23 +52,32 @@ function View(name, options) {
}
/**
* Lookup view by the given `path`
* Lookup view by the given `name`
*
* @param {String} path
* @param {String} name
* @return {String}
* @api private
*/
View.prototype.lookup = function(path){
var ext = this.ext;
View.prototype.lookup = function lookup(name) {
var path;
var roots = [].concat(this.root);
// <path>.<engine>
if (!utils.isAbsolute(path)) path = join(this.root, path);
if (exists(path)) return path;
debug('lookup "%s"', name);
// <path>/index.<engine>
path = join(dirname(path), basename(path, ext), 'index' + ext);
if (exists(path)) return path;
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 file
path = this.resolve(dir, file);
}
return path;
};
/**
@@ -72,6 +88,55 @@ View.prototype.lookup = function(path){
* @api private
*/
View.prototype.render = function(options, fn){
View.prototype.render = function render(options, fn) {
debug('render "%s"', this.path);
this.engine(this.path, options, fn);
};
/**
* Resolve the file within the given directory.
*
* @param {string} dir
* @param {string} file
* @private
*/
View.prototype.resolve = function resolve(dir, file) {
var ext = this.ext;
var path;
var stat;
// <path>.<ext>
path = join(dir, file);
stat = tryStat(path);
if (stat && stat.isFile()) {
return path;
}
// <path>/index.<ext>
path = join(dir, basename(file, ext), 'index' + ext);
stat = tryStat(path);
if (stat && stat.isFile()) {
return path;
}
};
/**
* Return a stat, maybe.
*
* @param {string} path
* @return {fs.Stats}
* @private
*/
function tryStat(path) {
debug('stat "%s"', path);
try {
return fs.statSync(path);
} catch (e) {
return undefined;
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Fast, unopinionated, minimalist web framework",
"version": "4.9.8",
"version": "4.10.5",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
@@ -12,6 +12,9 @@
"Roman Shtylman <shtylman+expressjs@gmail.com>",
"Young Jae Sim <hanul@hanul.me>"
],
"license": "MIT",
"repository": "strongloop/express",
"homepage": "http://expressjs.com/",
"keywords": [
"express",
"framework",
@@ -23,29 +26,27 @@
"app",
"api"
],
"repository": "strongloop/express",
"license": "MIT",
"homepage": "http://expressjs.com/",
"dependencies": {
"accepts": "~1.1.2",
"accepts": "~1.1.4",
"content-disposition": "0.5.0",
"cookie-signature": "1.0.5",
"debug": "~2.0.0",
"depd": "0.4.5",
"debug": "~2.1.0",
"depd": "~1.0.0",
"escape-html": "1.0.1",
"etag": "~1.4.0",
"finalhandler": "0.2.0",
"etag": "~1.5.1",
"finalhandler": "0.3.2",
"fresh": "0.2.4",
"media-typer": "0.3.0",
"methods": "1.1.0",
"on-finished": "~2.1.0",
"on-finished": "~2.1.1",
"parseurl": "~1.3.0",
"path-to-regexp": "0.1.3",
"proxy-addr": "~1.0.3",
"qs": "2.2.4",
"proxy-addr": "~1.0.4",
"qs": "2.3.3",
"range-parser": "~1.0.2",
"send": "0.9.3",
"serve-static": "~1.6.4",
"type-is": "~1.5.2",
"send": "0.10.1",
"serve-static": "~1.7.1",
"type-is": "~1.5.4",
"vary": "~1.0.0",
"cookie": "0.1.2",
"merge-descriptors": "0.0.2",
@@ -54,27 +55,33 @@
"devDependencies": {
"after": "0.8.1",
"istanbul": "0.3.2",
"mocha": "~1.21.5",
"should": "~4.0.4",
"supertest": "~0.14.0",
"mocha": "~2.0.0",
"should": "~4.3.0",
"supertest": "~0.15.0",
"ejs": "~1.0.0",
"marked": "0.3.2",
"hjs": "~0.0.6",
"body-parser": "~1.8.2",
"body-parser": "~1.9.3",
"connect-redis": "~2.1.0",
"cookie-parser": "~1.3.3",
"express-session": "~1.8.2",
"jade": "~1.6.0",
"method-override": "~2.2.0",
"morgan": "~1.3.1",
"multiparty": "~3.3.2",
"express-session": "~1.9.2",
"jade": "~1.7.0",
"method-override": "~2.3.0",
"morgan": "~1.5.0",
"multiparty": "~4.0.0",
"vhost": "~3.0.0"
},
"engines": {
"node": ">= 0.10.0"
},
"files": [
"LICENSE",
"History.md",
"Readme.md",
"index.js",
"lib/"
],
"scripts": {
"prepublish": "npm prune",
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/",

View File

@@ -43,6 +43,16 @@ describe('Router', function(){
router.handle({ url: '/test/route', method: 'GET' }, { end: done });
});
it('should handle blank URL', function(done){
var router = new Router();
router.use(function (req, res) {
false.should.be.true;
});
router.handle({ url: '', method: 'GET' }, {}, done);
});
describe('.handle', function(){
it('should dispatch', function(done){
var router = new Router();
@@ -209,6 +219,23 @@ describe('Router', function(){
});
});
it('should ignore FQDN in path', function (done) {
var request = { hit: 0, url: '/proxy/http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use('/proxy', function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, '/http://example.com/blog/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should adjust FQDN req.url', function (done) {
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
var router = new Router();

View File

@@ -1,4 +1,4 @@
var app = require('../../examples/auth/app')
var app = require('../../examples/auth')
var request = require('supertest')
function getCookie(res) {

View File

@@ -1,5 +1,5 @@
var app = require('../../examples/cookies/app')
var app = require('../../examples/cookies')
, request = require('supertest');
describe('cookies', function(){

View File

@@ -1,5 +1,5 @@
var app = require('../../examples/downloads/app')
var app = require('../../examples/downloads')
, request = require('supertest');
describe('downloads', function(){

View File

@@ -0,0 +1,44 @@
var app = require('../../examples/multi-router')
var request = require('supertest')
describe('multi-router', function(){
describe('GET /',function(){
it('should respond with root handler', function(done){
request(app)
.get('/')
.expect(200, 'Hello form root route.', done)
})
})
describe('GET /api/v1/',function(){
it('should respond with APIv1 root handler', function(done){
request(app)
.get('/api/v1/')
.expect(200, 'Hello from APIv1 root route.', done)
})
})
describe('GET /api/v1/users',function(){
it('should respond with users from APIv1', function(done){
request(app)
.get('/api/v1/users')
.expect(200, 'List of APIv1 users.', done)
})
})
describe('GET /api/v2/',function(){
it('should respond with APIv2 root handler', function(done){
request(app)
.get('/api/v2/')
.expect(200, 'Hello from APIv2 root route.', done)
})
})
describe('GET /api/v2/users',function(){
it('should respond with users from APIv2', function(done){
request(app)
.get('/api/v2/users')
.expect(200, 'List of APIv2 users.', done)
})
})
})

View File

@@ -1,4 +1,4 @@
var app = require('../../examples/params/app')
var app = require('../../examples/params')
var request = require('supertest')
describe('params', function(){

View File

@@ -1,4 +1,4 @@
var app = require('../../examples/resource/app')
var app = require('../../examples/resource')
var request = require('supertest')
describe('resource', function(){

View File

@@ -131,6 +131,64 @@ describe('app', function(){
})
})
describe('when "views" is given', function(){
it('should lookup the file in the path', function(done){
var app = express();
app.set('views', __dirname + '/fixtures/default_layout');
app.locals.user = { name: 'tobi' };
app.render('user.jade', function(err, str){
if (err) return done(err);
str.should.equal('<p>tobi</p>');
done();
})
})
describe('when array of paths', function(){
it('should lookup the file in the path', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.locals.user = { name: 'tobi' };
app.render('user.jade', function(err, str){
if (err) return done(err);
str.should.equal('<span>tobi</span>');
done();
})
})
it('should lookup in later paths until found', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.locals.name = 'tobi';
app.render('name.jade', function(err, str){
if (err) return done(err);
str.should.equal('<p>tobi</p>');
done();
})
})
it('should error if file does not exist', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.locals.name = 'tobi';
app.render('pet.jade', function(err, str){
err.message.should.equal('Failed to lookup view "pet.jade" in views directories "' + __dirname + '/fixtures/local_layout" or "' + __dirname + '/fixtures/default_layout"');
done();
})
})
})
})
describe('when a "view" constructor is given', function(){
it('should create an instance of it', function(done){
var app = express();

View File

@@ -0,0 +1 @@
p= name

View File

@@ -0,0 +1 @@
p= user.name

1
test/fixtures/local_layout/user.jade vendored Normal file
View File

@@ -0,0 +1 @@
span= user.name

View File

@@ -56,10 +56,8 @@ describe('res', function(){
request(app)
.get('/')
.expect('Content-Disposition', 'attachment;' +
' filename="%E6%97%A5%E6%9C%AC%E8%AA%9E.txt";' +
' filename*=UTF-8\'\'%E6%97%A5%E6%9C%AC%E8%AA%9E.txt',
done);
.expect('Content-Disposition', 'attachment; filename="???.txt"; filename*=UTF-8\'\'%E6%97%A5%E6%9C%AC%E8%AA%9E.txt')
.expect(200, done);
})
it('should set the Content-Type', function(done){

View File

@@ -1,9 +1,9 @@
var express = require('../')
, request = require('supertest')
, mixin = require('utils-merge')
, cookie = require('cookie')
, cookieParser = require('cookie-parser')
var merge = require('utils-merge');
describe('res', function(){
describe('.cookie(name, object)', function(){
@@ -113,7 +113,7 @@ describe('res', function(){
var app = express();
var options = { maxAge: 1000 };
var optionsCopy = mixin({}, options);
var optionsCopy = merge({}, options);
app.use(function(req, res){
res.cookie('name', 'tobi', options)

View File

@@ -114,6 +114,54 @@ describe('res', function(){
.expect('<p>This is an email</p>', done);
})
})
describe('when "views" is given', function(){
it('should lookup the file in the path', function(done){
var app = express();
app.set('views', __dirname + '/fixtures/default_layout');
app.use(function(req, res){
res.render('user.jade', { user: { name: 'tobi' } });
});
request(app)
.get('/')
.expect('<p>tobi</p>', done);
})
describe('when array of paths', function(){
it('should lookup the file in the path', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.use(function(req, res){
res.render('user.jade', { user: { name: 'tobi' } });
});
request(app)
.get('/')
.expect('<span>tobi</span>', done);
})
it('should lookup in later paths until found', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.use(function(req, res){
res.render('name.jade', { name: 'tobi' });
});
request(app)
.get('/')
.expect('<p>tobi</p>', done);
})
})
})
})
describe('.render(name, option)', function(){

View File

@@ -69,6 +69,27 @@ describe('res', function(){
.end(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 "dotfiles" option', function () {
it('should not serve dotfiles by default', function (done) {
var app = createApp(path.resolve(__dirname, 'fixtures/.name'));
@@ -455,6 +476,27 @@ describe('res', function(){
.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();