mirror of
https://github.com/expressjs/express.git
synced 2026-02-26 08:45:36 +00:00
Compare commits
289 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc38d896ea | ||
|
|
b2518fe135 | ||
|
|
63286e1192 | ||
|
|
c00f2f8596 | ||
|
|
91891e3aee | ||
|
|
728917164c | ||
|
|
bf1980f1b4 | ||
|
|
3c1a964362 | ||
|
|
947fb8b274 | ||
|
|
c5193536e5 | ||
|
|
d08fd64190 | ||
|
|
2470ae6c72 | ||
|
|
916a75cf19 | ||
|
|
daacb749c1 | ||
|
|
f29399c4e1 | ||
|
|
f6ac068ab0 | ||
|
|
7eb65eeca2 | ||
|
|
178fe15091 | ||
|
|
381f278d0a | ||
|
|
534fa181c6 | ||
|
|
80847d8c82 | ||
|
|
bb8abf1f90 | ||
|
|
cf41a8f254 | ||
|
|
1716e3b067 | ||
|
|
12f92a50dc | ||
|
|
51d33edb79 | ||
|
|
2a0c35a108 | ||
|
|
d4de82b853 | ||
|
|
e2102263ce | ||
|
|
d2b1a89b4a | ||
|
|
d9937c628a | ||
|
|
276db8c49a | ||
|
|
1768d94a1a | ||
|
|
6bc7574ab5 | ||
|
|
3dca534995 | ||
|
|
4b1b8e420f | ||
|
|
70767b19ac | ||
|
|
7d277c1c15 | ||
|
|
fa1fcd9fec | ||
|
|
2de6514b4b | ||
|
|
d07c06363f | ||
|
|
4e97533fd2 | ||
|
|
d7d6219a1e | ||
|
|
9b18461bbc | ||
|
|
b77aa38c98 | ||
|
|
cbb251377e | ||
|
|
d6ed469de3 | ||
|
|
49284c236b | ||
|
|
be18487f7d | ||
|
|
094ff11949 | ||
|
|
d2d0afff64 | ||
|
|
33bb8fc4b6 | ||
|
|
b97f6eb506 | ||
|
|
621d074bd8 | ||
|
|
d7e7b2e7d7 | ||
|
|
2d6b735b4f | ||
|
|
a3115882d4 | ||
|
|
3d188fe13e | ||
|
|
8327708ec2 | ||
|
|
c8640b3465 | ||
|
|
3ce5f9b493 | ||
|
|
46f0bfc65f | ||
|
|
0b49e7f1fd | ||
|
|
f6f47f428c | ||
|
|
20635d03fc | ||
|
|
4d032cda05 | ||
|
|
4127ba10b0 | ||
|
|
b6ae091bdf | ||
|
|
a206b4e273 | ||
|
|
2b2733c235 | ||
|
|
7fb7bcc0f7 | ||
|
|
0299bee8fa | ||
|
|
6a581c9961 | ||
|
|
4986b1cb4c | ||
|
|
3de4e4276d | ||
|
|
27f195374d | ||
|
|
ff0de5eb27 | ||
|
|
f4ddef1570 | ||
|
|
9eafaa23d8 | ||
|
|
0b12cc0cac | ||
|
|
fdd0ccabe8 | ||
|
|
8c36eab679 | ||
|
|
5c145b5490 | ||
|
|
d7bef52591 | ||
|
|
1576a95e87 | ||
|
|
7f92fe66e0 | ||
|
|
0cf02d4667 | ||
|
|
ef52b80d75 | ||
|
|
1ca01c0c47 | ||
|
|
fbceae2716 | ||
|
|
ad3ca25c58 | ||
|
|
666ffc62d8 | ||
|
|
6680132392 | ||
|
|
f13f4652da | ||
|
|
059c068c7b | ||
|
|
49947f1476 | ||
|
|
0dddd772c0 | ||
|
|
0f87c6f392 | ||
|
|
1643ae442c | ||
|
|
2594f3103b | ||
|
|
8473b3c338 | ||
|
|
59cb99e9be | ||
|
|
7119f2b16d | ||
|
|
a57efea173 | ||
|
|
4a4ca7347a | ||
|
|
570f60d36e | ||
|
|
d13e613584 | ||
|
|
9204e1f42a | ||
|
|
22ca953e96 | ||
|
|
7989c883fe | ||
|
|
e05a52078a | ||
|
|
ddac571fdf | ||
|
|
982d24b475 | ||
|
|
552b441f8a | ||
|
|
e8f8ea7e05 | ||
|
|
4f5b27dd81 | ||
|
|
cca88a7c47 | ||
|
|
ea427c1bb4 | ||
|
|
0bd6c311cf | ||
|
|
7f606ebf29 | ||
|
|
a3b5adcf4a | ||
|
|
1150ca7264 | ||
|
|
4aea02310a | ||
|
|
17cea29013 | ||
|
|
8449f23f0d | ||
|
|
2cb029f896 | ||
|
|
7e32fa1be6 | ||
|
|
1168d0bb8b | ||
|
|
7d0f1c3db9 | ||
|
|
19abf7684b | ||
|
|
c652cf7eed | ||
|
|
19fd6f85b0 | ||
|
|
b6c5b0511f | ||
|
|
0e42a37edd | ||
|
|
b24ed15878 | ||
|
|
12e070e39a | ||
|
|
b886eb52cf | ||
|
|
d8237b976b | ||
|
|
15590d75b2 | ||
|
|
e8b471ff4f | ||
|
|
767db01b79 | ||
|
|
df413a41f3 | ||
|
|
52775a52ad | ||
|
|
112bc92d78 | ||
|
|
d8df26680f | ||
|
|
1854a5d35f | ||
|
|
54d3ffa9a0 | ||
|
|
0ee4dd82b5 | ||
|
|
454c4b2350 | ||
|
|
696e150f0a | ||
|
|
819265c7ae | ||
|
|
baf8b14a71 | ||
|
|
06e7685d65 | ||
|
|
8858f20d93 | ||
|
|
65f67e2ec0 | ||
|
|
8e63521f68 | ||
|
|
9f4968aaa3 | ||
|
|
afea3c0ae8 | ||
|
|
edaabe66cf | ||
|
|
f724730e1a | ||
|
|
e49d0dc9e3 | ||
|
|
e7a3fbaf48 | ||
|
|
1a9a837c45 | ||
|
|
ab8d116f42 | ||
|
|
d0b6b3dfcf | ||
|
|
f34944c539 | ||
|
|
11c74d72eb | ||
|
|
035685918c | ||
|
|
88cffadcaa | ||
|
|
3b7ca43170 | ||
|
|
7cd86a01da | ||
|
|
dc054d190a | ||
|
|
9019424725 | ||
|
|
928952e7f0 | ||
|
|
a28b7a85cf | ||
|
|
3fc8dc54ee | ||
|
|
0d77305a1a | ||
|
|
323c185079 | ||
|
|
1d0da9036b | ||
|
|
683ba1cd75 | ||
|
|
e4ff5281c9 | ||
|
|
7414a1f463 | ||
|
|
fd3b40533b | ||
|
|
21bb2ef30e | ||
|
|
da4c639954 | ||
|
|
d046208ca2 | ||
|
|
04f383087f | ||
|
|
fd86ab8da2 | ||
|
|
e29fa25bb4 | ||
|
|
b43205ca98 | ||
|
|
112dbb2ab4 | ||
|
|
3e32721e24 | ||
|
|
8ba3f39b33 | ||
|
|
82bdbad5e0 | ||
|
|
d29cf4d5e3 | ||
|
|
1623936a25 | ||
|
|
fa6a40526a | ||
|
|
c6e6203020 | ||
|
|
997a558a73 | ||
|
|
a01326adac | ||
|
|
76e8bfa1dc | ||
|
|
8dc67af606 | ||
|
|
996d319263 | ||
|
|
1c3bd36be6 | ||
|
|
4ea6f21b02 | ||
|
|
916c53737d | ||
|
|
ec5b9f5c61 | ||
|
|
b2382a7336 | ||
|
|
f684a64df7 | ||
|
|
5d03d0eac8 | ||
|
|
544c6665f5 | ||
|
|
cf8005e63f | ||
|
|
25ef8425d2 | ||
|
|
577cc1d1a0 | ||
|
|
3c87a6aede | ||
|
|
7c1f90bf16 | ||
|
|
7bcf5f5085 | ||
|
|
4fe1073f10 | ||
|
|
968b00c3d7 | ||
|
|
ef497fdae4 | ||
|
|
bcdeee2df5 | ||
|
|
23a49ff61e | ||
|
|
b4b2efee0f | ||
|
|
92c45199bd | ||
|
|
bd6908516d | ||
|
|
e6eeec3f03 | ||
|
|
269dc5323f | ||
|
|
efbc3f95ee | ||
|
|
99e839a274 | ||
|
|
32dbda1460 | ||
|
|
bcee730354 | ||
|
|
abe0ffa311 | ||
|
|
b601d64203 | ||
|
|
f381f2d9b6 | ||
|
|
12507cfcd0 | ||
|
|
185e327e29 | ||
|
|
c468f5ff20 | ||
|
|
ce3d1fe07e | ||
|
|
3136e98dce | ||
|
|
d1b1dfd472 | ||
|
|
ca306eace1 | ||
|
|
fd35351594 | ||
|
|
8a15f83d72 | ||
|
|
d91bf81c31 | ||
|
|
fd27f1f4e1 | ||
|
|
f0ad557987 | ||
|
|
9bb47fba30 | ||
|
|
78d489d730 | ||
|
|
8ffb9f9477 | ||
|
|
9cb147370e | ||
|
|
81036aa639 | ||
|
|
e7caaf6757 | ||
|
|
a525252690 | ||
|
|
0ebfc6b7bf | ||
|
|
381b3b0f77 | ||
|
|
ba8a4c5a8d | ||
|
|
2cccbc186e | ||
|
|
83bbf0902d | ||
|
|
10618ced22 | ||
|
|
7f26cfca91 | ||
|
|
b89a597029 | ||
|
|
746044b6c2 | ||
|
|
bdfd288eec | ||
|
|
7e01531e50 | ||
|
|
3ffceff3ed | ||
|
|
75422c16bf | ||
|
|
e66667e465 | ||
|
|
7d6208e0af | ||
|
|
f498b660da | ||
|
|
e606d99dc8 | ||
|
|
6aba1b4c49 | ||
|
|
6ee9433f29 | ||
|
|
ffe663aedf | ||
|
|
2a105df9f2 | ||
|
|
9c731f1883 | ||
|
|
5a4e9125de | ||
|
|
9db1367c2d | ||
|
|
8258ce14f4 | ||
|
|
ac573cf830 | ||
|
|
e799c0fb7b | ||
|
|
73c5533e66 | ||
|
|
3b1f747f96 | ||
|
|
9e9827d236 | ||
|
|
a76d508424 | ||
|
|
c361a06bd4 | ||
|
|
3428543bb8 | ||
|
|
9cdbc80522 | ||
|
|
6775658ed5 | ||
|
|
7df7f7a575 |
@@ -1,7 +1,7 @@
|
||||
|
||||
## Website Issues
|
||||
|
||||
Issues for the expressjs.com website go here https://github.com/visionmedia/expressjs.com
|
||||
Issues for the expressjs.com website go here https://github.com/strongloop/expressjs.com
|
||||
|
||||
## PRs and Code contributions
|
||||
|
||||
|
||||
677
History.md
677
History.md
@@ -1,3 +1,325 @@
|
||||
4.9.3 / 2014-09-18
|
||||
==================
|
||||
|
||||
* deps: proxy-addr@~1.0.2
|
||||
- Fix a global leak when multiple subnets are trusted
|
||||
- deps: ipaddr.js@0.1.3
|
||||
|
||||
4.9.2 / 2014-09-17
|
||||
==================
|
||||
|
||||
* Fix regression for empty string `path` in `app.use`
|
||||
* Fix `router.use` to accept array of middleware without path
|
||||
* Improve error message for bad `app.use` arguments
|
||||
|
||||
4.9.1 / 2014-09-16
|
||||
==================
|
||||
|
||||
* Fix `app.use` to accept array of middleware without path
|
||||
* deps: depd@0.4.5
|
||||
* deps: etag@~1.3.1
|
||||
* deps: send@0.9.2
|
||||
- deps: depd@0.4.5
|
||||
- deps: etag@~1.3.1
|
||||
- deps: range-parser@~1.0.2
|
||||
* deps: serve-static@~1.6.2
|
||||
- deps: send@0.9.2
|
||||
|
||||
4.9.0 / 2014-09-08
|
||||
==================
|
||||
|
||||
* Add `res.sendStatus`
|
||||
* Invoke callback for sendfile when client aborts
|
||||
- Applies to `res.sendFile`, `res.sendfile`, and `res.download`
|
||||
- `err` will be populated with request aborted error
|
||||
* Support IP address host in `req.subdomains`
|
||||
* Use `etag` to generate `ETag` headers
|
||||
* deps: accepts@~1.1.0
|
||||
- update `mime-types`
|
||||
* deps: cookie-signature@1.0.5
|
||||
* deps: debug@~2.0.0
|
||||
* deps: finalhandler@0.2.0
|
||||
- Set `X-Content-Type-Options: nosniff` header
|
||||
- deps: debug@~2.0.0
|
||||
* deps: fresh@0.2.4
|
||||
* deps: media-typer@0.3.0
|
||||
- Throw error when parameter format invalid on parse
|
||||
* deps: qs@2.2.3
|
||||
- Fix issue where first empty value in array is discarded
|
||||
* deps: range-parser@~1.0.2
|
||||
* deps: send@0.9.1
|
||||
- Add `lastModified` option
|
||||
- Use `etag` to generate `ETag` header
|
||||
- deps: debug@~2.0.0
|
||||
- deps: fresh@0.2.4
|
||||
* deps: serve-static@~1.6.1
|
||||
- Add `lastModified` option
|
||||
- deps: send@0.9.1
|
||||
* deps: type-is@~1.5.1
|
||||
- fix `hasbody` to be true for `content-length: 0`
|
||||
- deps: media-typer@0.3.0
|
||||
- deps: mime-types@~2.0.1
|
||||
* deps: vary@~1.0.0
|
||||
- Accept valid `Vary` header string as `field`
|
||||
|
||||
4.8.8 / 2014-09-04
|
||||
==================
|
||||
|
||||
* deps: send@0.8.5
|
||||
- Fix a path traversal issue when using `root`
|
||||
- Fix malicious path detection for empty string path
|
||||
* deps: serve-static@~1.5.4
|
||||
- deps: send@0.8.5
|
||||
|
||||
4.8.7 / 2014-08-29
|
||||
==================
|
||||
|
||||
* deps: qs@2.2.2
|
||||
- Remove unnecessary cloning
|
||||
|
||||
4.8.6 / 2014-08-27
|
||||
==================
|
||||
|
||||
* deps: qs@2.2.0
|
||||
- Array parsing fix
|
||||
- Performance improvements
|
||||
|
||||
4.8.5 / 2014-08-18
|
||||
==================
|
||||
|
||||
* deps: send@0.8.3
|
||||
- deps: destroy@1.0.3
|
||||
- deps: on-finished@2.1.0
|
||||
* deps: serve-static@~1.5.3
|
||||
- deps: send@0.8.3
|
||||
|
||||
4.8.4 / 2014-08-14
|
||||
==================
|
||||
|
||||
* deps: qs@1.2.2
|
||||
* deps: send@0.8.2
|
||||
- Work around `fd` leak in Node.js 0.10 for `fs.ReadStream`
|
||||
* deps: serve-static@~1.5.2
|
||||
- deps: send@0.8.2
|
||||
|
||||
4.8.3 / 2014-08-10
|
||||
==================
|
||||
|
||||
* deps: parseurl@~1.3.0
|
||||
* deps: qs@1.2.1
|
||||
* deps: serve-static@~1.5.1
|
||||
- Fix parsing of weird `req.originalUrl` values
|
||||
- deps: parseurl@~1.3.0
|
||||
- deps: utils-merge@1.0.0
|
||||
|
||||
4.8.2 / 2014-08-07
|
||||
==================
|
||||
|
||||
* deps: qs@1.2.0
|
||||
- Fix parsing array of objects
|
||||
|
||||
4.8.1 / 2014-08-06
|
||||
==================
|
||||
|
||||
* fix incorrect deprecation warnings on `res.download`
|
||||
* deps: qs@1.1.0
|
||||
- Accept urlencoded square brackets
|
||||
- Accept empty values in implicit array notation
|
||||
|
||||
4.8.0 / 2014-08-05
|
||||
==================
|
||||
|
||||
* add `res.sendFile`
|
||||
- accepts a file system path instead of a URL
|
||||
- requires an absolute path or `root` option specified
|
||||
* deprecate `res.sendfile` -- use `res.sendFile` instead
|
||||
* support mounted app as any argument to `app.use()`
|
||||
* deps: qs@1.0.2
|
||||
- Complete rewrite
|
||||
- Limits array length to 20
|
||||
- Limits object depth to 5
|
||||
- Limits parameters to 1,000
|
||||
* deps: send@0.8.1
|
||||
- Add `extensions` option
|
||||
* deps: serve-static@~1.5.0
|
||||
- Add `extensions` option
|
||||
- deps: send@0.8.1
|
||||
|
||||
4.7.4 / 2014-08-04
|
||||
==================
|
||||
|
||||
* fix `res.sendfile` regression for serving directory index files
|
||||
* deps: send@0.7.4
|
||||
- Fix incorrect 403 on Windows and Node.js 0.11
|
||||
- Fix serving index files without root dir
|
||||
* deps: serve-static@~1.4.4
|
||||
- deps: send@0.7.4
|
||||
|
||||
4.7.3 / 2014-08-04
|
||||
==================
|
||||
|
||||
* deps: send@0.7.3
|
||||
- Fix incorrect 403 on Windows and Node.js 0.11
|
||||
* deps: serve-static@~1.4.3
|
||||
- Fix incorrect 403 on Windows and Node.js 0.11
|
||||
- deps: send@0.7.3
|
||||
|
||||
4.7.2 / 2014-07-27
|
||||
==================
|
||||
|
||||
* deps: depd@0.4.4
|
||||
- Work-around v8 generating empty stack traces
|
||||
* deps: send@0.7.2
|
||||
- deps: depd@0.4.4
|
||||
* deps: serve-static@~1.4.2
|
||||
|
||||
4.7.1 / 2014-07-26
|
||||
==================
|
||||
|
||||
* deps: depd@0.4.3
|
||||
- Fix exception when global `Error.stackTraceLimit` is too low
|
||||
* deps: send@0.7.1
|
||||
- deps: depd@0.4.3
|
||||
* deps: serve-static@~1.4.1
|
||||
|
||||
4.7.0 / 2014-07-25
|
||||
==================
|
||||
|
||||
* fix `req.protocol` for proxy-direct connections
|
||||
* configurable query parser with `app.set('query parser', parser)`
|
||||
- `app.set('query parser', 'extended')` parse with "qs" module
|
||||
- `app.set('query parser', 'simple')` parse with "querystring" core module
|
||||
- `app.set('query parser', false)` disable query string parsing
|
||||
- `app.set('query parser', true)` enable simple parsing
|
||||
* deprecate `res.json(status, obj)` -- use `res.status(status).json(obj)` instead
|
||||
* deprecate `res.jsonp(status, obj)` -- use `res.status(status).jsonp(obj)` instead
|
||||
* deprecate `res.send(status, body)` -- use `res.status(status).send(body)` instead
|
||||
* deps: debug@1.0.4
|
||||
* deps: depd@0.4.2
|
||||
- Add `TRACE_DEPRECATION` environment variable
|
||||
- Remove non-standard grey color from color output
|
||||
- Support `--no-deprecation` argument
|
||||
- Support `--trace-deprecation` argument
|
||||
* deps: finalhandler@0.1.0
|
||||
- Respond after request fully read
|
||||
- deps: debug@1.0.4
|
||||
* deps: parseurl@~1.2.0
|
||||
- Cache URLs based on original value
|
||||
- Remove no-longer-needed URL mis-parse work-around
|
||||
- Simplify the "fast-path" `RegExp`
|
||||
* deps: send@0.7.0
|
||||
- Add `dotfiles` option
|
||||
- Cap `maxAge` value to 1 year
|
||||
- deps: debug@1.0.4
|
||||
- deps: depd@0.4.2
|
||||
* deps: serve-static@~1.4.0
|
||||
- deps: parseurl@~1.2.0
|
||||
- deps: send@0.7.0
|
||||
* perf: prevent multiple `Buffer` creation in `res.send`
|
||||
|
||||
4.6.1 / 2014-07-12
|
||||
==================
|
||||
|
||||
* fix `subapp.mountpath` regression for `app.use(subapp)`
|
||||
|
||||
4.6.0 / 2014-07-11
|
||||
==================
|
||||
|
||||
* accept multiple callbacks to `app.use()`
|
||||
* add explicit "Rosetta Flash JSONP abuse" protection
|
||||
- previous versions are not vulnerable; this is just explicit protection
|
||||
* catch errors in multiple `req.param(name, fn)` handlers
|
||||
* deprecate `res.redirect(url, status)` -- use `res.redirect(status, url)` instead
|
||||
* fix `res.send(status, num)` to send `num` as json (not error)
|
||||
* remove unnecessary escaping when `res.jsonp` returns JSON response
|
||||
* support non-string `path` in `app.use(path, fn)`
|
||||
- supports array of paths
|
||||
- supports `RegExp`
|
||||
* router: fix optimization on router exit
|
||||
* router: refactor location of `try` blocks
|
||||
* router: speed up standard `app.use(fn)`
|
||||
* deps: debug@1.0.3
|
||||
- Add support for multiple wildcards in namespaces
|
||||
* deps: finalhandler@0.0.3
|
||||
- deps: debug@1.0.3
|
||||
* deps: methods@1.1.0
|
||||
- add `CONNECT`
|
||||
* deps: parseurl@~1.1.3
|
||||
- faster parsing of href-only URLs
|
||||
* deps: path-to-regexp@0.1.3
|
||||
* deps: send@0.6.0
|
||||
- deps: debug@1.0.3
|
||||
* deps: serve-static@~1.3.2
|
||||
- deps: parseurl@~1.1.3
|
||||
- deps: send@0.6.0
|
||||
* perf: fix arguments reassign deopt in some `res` methods
|
||||
|
||||
4.5.1 / 2014-07-06
|
||||
==================
|
||||
|
||||
* fix routing regression when altering `req.method`
|
||||
|
||||
4.5.0 / 2014-07-04
|
||||
==================
|
||||
|
||||
* add deprecation message to non-plural `req.accepts*`
|
||||
* add deprecation message to `res.send(body, status)`
|
||||
* add deprecation message to `res.vary()`
|
||||
* add `headers` option to `res.sendfile`
|
||||
- use to set headers on successful file transfer
|
||||
* add `mergeParams` option to `Router`
|
||||
- merges `req.params` from parent routes
|
||||
* add `req.hostname` -- correct name for what `req.host` returns
|
||||
* deprecate things with `depd` module
|
||||
* deprecate `req.host` -- use `req.hostname` instead
|
||||
* fix behavior when handling request without routes
|
||||
* fix handling when `route.all` is only route
|
||||
* invoke `router.param()` only when route matches
|
||||
* restore `req.params` after invoking router
|
||||
* use `finalhandler` for final response handling
|
||||
* use `media-typer` to alter content-type charset
|
||||
* deps: accepts@~1.0.7
|
||||
* deps: send@0.5.0
|
||||
- Accept string for `maxage` (converted by `ms`)
|
||||
- Include link in default redirect response
|
||||
* deps: serve-static@~1.3.0
|
||||
- Accept string for `maxAge` (converted by `ms`)
|
||||
- Add `setHeaders` option
|
||||
- Include HTML link in redirect response
|
||||
- deps: send@0.5.0
|
||||
* deps: type-is@~1.3.2
|
||||
|
||||
4.4.5 / 2014-06-26
|
||||
==================
|
||||
|
||||
* deps: cookie-signature@1.0.4
|
||||
- fix for timing attacks
|
||||
|
||||
4.4.4 / 2014-06-20
|
||||
==================
|
||||
|
||||
* fix `res.attachment` Unicode filenames in Safari
|
||||
* fix "trim prefix" debug message in `express:router`
|
||||
* deps: accepts@~1.0.5
|
||||
* deps: buffer-crc32@0.2.3
|
||||
|
||||
4.4.3 / 2014-06-11
|
||||
==================
|
||||
|
||||
* fix persistence of modified `req.params[name]` from `app.param()`
|
||||
* deps: accepts@1.0.3
|
||||
- deps: negotiator@0.4.6
|
||||
* deps: debug@1.0.2
|
||||
* deps: send@0.4.3
|
||||
- Do not throw un-catchable error on file open race condition
|
||||
- Use `escape-html` for HTML escaping
|
||||
- deps: debug@1.0.2
|
||||
- deps: finished@1.2.2
|
||||
- deps: fresh@0.2.2
|
||||
* deps: serve-static@1.2.3
|
||||
- Do not throw un-catchable error on file open race condition
|
||||
- deps: send@0.4.3
|
||||
|
||||
4.4.2 / 2014-06-09
|
||||
==================
|
||||
|
||||
@@ -171,6 +493,355 @@
|
||||
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route
|
||||
- Router & Route - public API
|
||||
|
||||
3.17.3 / 2014-09-18
|
||||
===================
|
||||
|
||||
* deps: proxy-addr@~1.0.2
|
||||
- Fix a global leak when multiple subnets are trusted
|
||||
- deps: ipaddr.js@0.1.3
|
||||
|
||||
3.17.2 / 2014-09-15
|
||||
===================
|
||||
|
||||
* Use `crc` instead of `buffer-crc32` for speed
|
||||
* deps: connect@2.26.1
|
||||
- deps: body-parser@~1.8.2
|
||||
- deps: depd@0.4.5
|
||||
- deps: express-session@~1.8.2
|
||||
- deps: morgan@~1.3.1
|
||||
- deps: serve-favicon@~2.1.3
|
||||
- deps: serve-static@~1.6.2
|
||||
* deps: depd@0.4.5
|
||||
* deps: send@0.9.2
|
||||
- deps: depd@0.4.5
|
||||
- deps: etag@~1.3.1
|
||||
- deps: range-parser@~1.0.2
|
||||
|
||||
3.17.1 / 2014-09-08
|
||||
===================
|
||||
|
||||
* Fix error in `req.subdomains` on empty host
|
||||
|
||||
3.17.0 / 2014-09-08
|
||||
===================
|
||||
|
||||
* Support `X-Forwarded-Host` in `req.subdomains`
|
||||
* Support IP address host in `req.subdomains`
|
||||
* deps: connect@2.26.0
|
||||
- deps: body-parser@~1.8.1
|
||||
- deps: compression@~1.1.0
|
||||
- deps: connect-timeout@~1.3.0
|
||||
- deps: cookie-parser@~1.3.3
|
||||
- deps: cookie-signature@1.0.5
|
||||
- deps: csurf@~1.6.1
|
||||
- deps: debug@~2.0.0
|
||||
- deps: errorhandler@~1.2.0
|
||||
- deps: express-session@~1.8.1
|
||||
- deps: finalhandler@0.2.0
|
||||
- deps: fresh@0.2.4
|
||||
- deps: media-typer@0.3.0
|
||||
- deps: method-override@~2.2.0
|
||||
- deps: morgan@~1.3.0
|
||||
- deps: qs@2.2.3
|
||||
- deps: serve-favicon@~2.1.3
|
||||
- deps: serve-index@~1.2.1
|
||||
- deps: serve-static@~1.6.1
|
||||
- deps: type-is@~1.5.1
|
||||
- deps: vhost@~3.0.0
|
||||
* deps: cookie-signature@1.0.5
|
||||
* deps: debug@~2.0.0
|
||||
* deps: fresh@0.2.4
|
||||
* deps: media-typer@0.3.0
|
||||
- Throw error when parameter format invalid on parse
|
||||
* deps: range-parser@~1.0.2
|
||||
* deps: send@0.9.1
|
||||
- Add `lastModified` option
|
||||
- Use `etag` to generate `ETag` header
|
||||
- deps: debug@~2.0.0
|
||||
- deps: fresh@0.2.4
|
||||
* deps: vary@~1.0.0
|
||||
- Accept valid `Vary` header string as `field`
|
||||
|
||||
3.16.10 / 2014-09-04
|
||||
====================
|
||||
|
||||
* deps: connect@2.25.10
|
||||
- deps: serve-static@~1.5.4
|
||||
* deps: send@0.8.5
|
||||
- Fix a path traversal issue when using `root`
|
||||
- Fix malicious path detection for empty string path
|
||||
|
||||
3.16.9 / 2014-08-29
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.9
|
||||
- deps: body-parser@~1.6.7
|
||||
- deps: qs@2.2.2
|
||||
|
||||
3.16.8 / 2014-08-27
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.8
|
||||
- deps: body-parser@~1.6.6
|
||||
- deps: csurf@~1.4.1
|
||||
- deps: qs@2.2.0
|
||||
|
||||
3.16.7 / 2014-08-18
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.7
|
||||
- deps: body-parser@~1.6.5
|
||||
- deps: express-session@~1.7.6
|
||||
- deps: morgan@~1.2.3
|
||||
- deps: serve-static@~1.5.3
|
||||
* deps: send@0.8.3
|
||||
- deps: destroy@1.0.3
|
||||
- deps: on-finished@2.1.0
|
||||
|
||||
3.16.6 / 2014-08-14
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.6
|
||||
- deps: body-parser@~1.6.4
|
||||
- deps: qs@1.2.2
|
||||
- deps: serve-static@~1.5.2
|
||||
* deps: send@0.8.2
|
||||
- Work around `fd` leak in Node.js 0.10 for `fs.ReadStream`
|
||||
|
||||
3.16.5 / 2014-08-11
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.5
|
||||
- Fix backwards compatibility in `logger`
|
||||
|
||||
3.16.4 / 2014-08-10
|
||||
===================
|
||||
|
||||
* Fix original URL parsing in `res.location`
|
||||
* deps: connect@2.25.4
|
||||
- Fix `query` middleware breaking with argument
|
||||
- deps: body-parser@~1.6.3
|
||||
- deps: compression@~1.0.11
|
||||
- deps: connect-timeout@~1.2.2
|
||||
- deps: express-session@~1.7.5
|
||||
- deps: method-override@~2.1.3
|
||||
- deps: on-headers@~1.0.0
|
||||
- deps: parseurl@~1.3.0
|
||||
- deps: qs@1.2.1
|
||||
- deps: response-time@~2.0.1
|
||||
- deps: serve-index@~1.1.6
|
||||
- deps: serve-static@~1.5.1
|
||||
* deps: parseurl@~1.3.0
|
||||
|
||||
3.16.3 / 2014-08-07
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.3
|
||||
- deps: multiparty@3.3.2
|
||||
|
||||
3.16.2 / 2014-08-07
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.2
|
||||
- deps: body-parser@~1.6.2
|
||||
- deps: qs@1.2.0
|
||||
|
||||
3.16.1 / 2014-08-06
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.1
|
||||
- deps: body-parser@~1.6.1
|
||||
- deps: qs@1.1.0
|
||||
|
||||
3.16.0 / 2014-08-05
|
||||
===================
|
||||
|
||||
* deps: connect@2.25.0
|
||||
- deps: body-parser@~1.6.0
|
||||
- deps: compression@~1.0.10
|
||||
- deps: csurf@~1.4.0
|
||||
- deps: express-session@~1.7.4
|
||||
- deps: qs@1.0.2
|
||||
- deps: serve-static@~1.5.0
|
||||
* deps: send@0.8.1
|
||||
- Add `extensions` option
|
||||
|
||||
3.15.3 / 2014-08-04
|
||||
===================
|
||||
|
||||
* fix `res.sendfile` regression for serving directory index files
|
||||
* deps: connect@2.24.3
|
||||
- deps: serve-index@~1.1.5
|
||||
- deps: serve-static@~1.4.4
|
||||
* deps: send@0.7.4
|
||||
- Fix incorrect 403 on Windows and Node.js 0.11
|
||||
- Fix serving index files without root dir
|
||||
|
||||
3.15.2 / 2014-07-27
|
||||
===================
|
||||
|
||||
* deps: connect@2.24.2
|
||||
- deps: body-parser@~1.5.2
|
||||
- deps: depd@0.4.4
|
||||
- deps: express-session@~1.7.2
|
||||
- deps: morgan@~1.2.2
|
||||
- deps: serve-static@~1.4.2
|
||||
* deps: depd@0.4.4
|
||||
- Work-around v8 generating empty stack traces
|
||||
* deps: send@0.7.2
|
||||
- deps: depd@0.4.4
|
||||
|
||||
3.15.1 / 2014-07-26
|
||||
===================
|
||||
|
||||
* deps: connect@2.24.1
|
||||
- deps: body-parser@~1.5.1
|
||||
- deps: depd@0.4.3
|
||||
- deps: express-session@~1.7.1
|
||||
- deps: morgan@~1.2.1
|
||||
- deps: serve-index@~1.1.4
|
||||
- deps: serve-static@~1.4.1
|
||||
* deps: depd@0.4.3
|
||||
- Fix exception when global `Error.stackTraceLimit` is too low
|
||||
* deps: send@0.7.1
|
||||
- deps: depd@0.4.3
|
||||
|
||||
3.15.0 / 2014-07-22
|
||||
===================
|
||||
|
||||
* Fix `req.protocol` for proxy-direct connections
|
||||
* Pass options from `res.sendfile` to `send`
|
||||
* deps: connect@2.24.0
|
||||
- deps: body-parser@~1.5.0
|
||||
- deps: compression@~1.0.9
|
||||
- deps: connect-timeout@~1.2.1
|
||||
- deps: debug@1.0.4
|
||||
- deps: depd@0.4.2
|
||||
- deps: express-session@~1.7.0
|
||||
- deps: finalhandler@0.1.0
|
||||
- deps: method-override@~2.1.2
|
||||
- deps: morgan@~1.2.0
|
||||
- deps: multiparty@3.3.1
|
||||
- deps: parseurl@~1.2.0
|
||||
- deps: serve-static@~1.4.0
|
||||
* deps: debug@1.0.4
|
||||
* deps: depd@0.4.2
|
||||
- Add `TRACE_DEPRECATION` environment variable
|
||||
- Remove non-standard grey color from color output
|
||||
- Support `--no-deprecation` argument
|
||||
- Support `--trace-deprecation` argument
|
||||
* deps: parseurl@~1.2.0
|
||||
- Cache URLs based on original value
|
||||
- Remove no-longer-needed URL mis-parse work-around
|
||||
- Simplify the "fast-path" `RegExp`
|
||||
* deps: send@0.7.0
|
||||
- Add `dotfiles` option
|
||||
- Cap `maxAge` value to 1 year
|
||||
- deps: debug@1.0.4
|
||||
- deps: depd@0.4.2
|
||||
|
||||
3.14.0 / 2014-07-11
|
||||
===================
|
||||
|
||||
* add explicit "Rosetta Flash JSONP abuse" protection
|
||||
- previous versions are not vulnerable; this is just explicit protection
|
||||
* deprecate `res.redirect(url, status)` -- use `res.redirect(status, url)` instead
|
||||
* fix `res.send(status, num)` to send `num` as json (not error)
|
||||
* remove unnecessary escaping when `res.jsonp` returns JSON response
|
||||
* deps: basic-auth@1.0.0
|
||||
- support empty password
|
||||
- support empty username
|
||||
* deps: connect@2.23.0
|
||||
- deps: debug@1.0.3
|
||||
- deps: express-session@~1.6.4
|
||||
- deps: method-override@~2.1.0
|
||||
- deps: parseurl@~1.1.3
|
||||
- deps: serve-static@~1.3.1
|
||||
* deps: debug@1.0.3
|
||||
- Add support for multiple wildcards in namespaces
|
||||
* deps: methods@1.1.0
|
||||
- add `CONNECT`
|
||||
* deps: parseurl@~1.1.3
|
||||
- faster parsing of href-only URLs
|
||||
|
||||
3.13.0 / 2014-07-03
|
||||
===================
|
||||
|
||||
* add deprecation message to `app.configure`
|
||||
* add deprecation message to `req.auth`
|
||||
* use `basic-auth` to parse `Authorization` header
|
||||
* deps: connect@2.22.0
|
||||
- deps: csurf@~1.3.0
|
||||
- deps: express-session@~1.6.1
|
||||
- deps: multiparty@3.3.0
|
||||
- deps: serve-static@~1.3.0
|
||||
* deps: send@0.5.0
|
||||
- Accept string for `maxage` (converted by `ms`)
|
||||
- Include link in default redirect response
|
||||
|
||||
3.12.1 / 2014-06-26
|
||||
===================
|
||||
|
||||
* deps: connect@2.21.1
|
||||
- deps: cookie-parser@1.3.2
|
||||
- deps: cookie-signature@1.0.4
|
||||
- deps: express-session@~1.5.2
|
||||
- deps: type-is@~1.3.2
|
||||
* deps: cookie-signature@1.0.4
|
||||
- fix for timing attacks
|
||||
|
||||
3.12.0 / 2014-06-21
|
||||
===================
|
||||
|
||||
* use `media-typer` to alter content-type charset
|
||||
* deps: connect@2.21.0
|
||||
- deprecate `connect(middleware)` -- use `app.use(middleware)` instead
|
||||
- deprecate `connect.createServer()` -- use `connect()` instead
|
||||
- fix `res.setHeader()` patch to work with with get -> append -> set pattern
|
||||
- deps: compression@~1.0.8
|
||||
- deps: errorhandler@~1.1.1
|
||||
- deps: express-session@~1.5.0
|
||||
- deps: serve-index@~1.1.3
|
||||
|
||||
3.11.0 / 2014-06-19
|
||||
===================
|
||||
|
||||
* deprecate things with `depd` module
|
||||
* deps: buffer-crc32@0.2.3
|
||||
* deps: connect@2.20.2
|
||||
- deprecate `verify` option to `json` -- use `body-parser` npm module instead
|
||||
- deprecate `verify` option to `urlencoded` -- use `body-parser` npm module instead
|
||||
- deprecate things with `depd` module
|
||||
- use `finalhandler` for final response handling
|
||||
- use `media-typer` to parse `content-type` for charset
|
||||
- deps: body-parser@1.4.3
|
||||
- deps: connect-timeout@1.1.1
|
||||
- deps: cookie-parser@1.3.1
|
||||
- deps: csurf@1.2.2
|
||||
- deps: errorhandler@1.1.0
|
||||
- deps: express-session@1.4.0
|
||||
- deps: multiparty@3.2.9
|
||||
- deps: serve-index@1.1.2
|
||||
- deps: type-is@1.3.1
|
||||
- deps: vhost@2.0.0
|
||||
|
||||
3.10.5 / 2014-06-11
|
||||
===================
|
||||
|
||||
* deps: connect@2.19.6
|
||||
- deps: body-parser@1.3.1
|
||||
- deps: compression@1.0.7
|
||||
- deps: debug@1.0.2
|
||||
- deps: serve-index@1.1.1
|
||||
- deps: serve-static@1.2.3
|
||||
* deps: debug@1.0.2
|
||||
* deps: send@0.4.3
|
||||
- Do not throw un-catchable error on file open race condition
|
||||
- Use `escape-html` for HTML escaping
|
||||
- deps: debug@1.0.2
|
||||
- deps: finished@1.2.2
|
||||
- deps: fresh@0.2.2
|
||||
|
||||
3.10.4 / 2014-06-09
|
||||
===================
|
||||
|
||||
@@ -213,7 +884,7 @@
|
||||
===================
|
||||
|
||||
* deps: connect@2.19.1
|
||||
- deprecate `methodOverride()` -- use `method-override` module directly
|
||||
- deprecate `methodOverride()` -- use `method-override` npm module instead
|
||||
- deps: body-parser@1.3.0
|
||||
- deps: method-override@2.0.1
|
||||
- deps: multiparty@3.2.8
|
||||
@@ -1377,7 +2048,7 @@ Shaw]
|
||||
* Added "encoding" option to Request#render(). Closes #299
|
||||
* Added "dump exceptions" setting, which is enabled by default.
|
||||
* Added simple ejs template engine support
|
||||
* Added error reponse support for text/plain, application/json. Closes #297
|
||||
* Added error response support for text/plain, application/json. Closes #297
|
||||
* Added callback function param to Request#error()
|
||||
* Added Request#sendHead()
|
||||
* Added Request#stream()
|
||||
@@ -1591,7 +2262,7 @@ Shaw]
|
||||
* Updated sample chat app to show messages on load
|
||||
* Updated libxmljs parseString -> parseHtmlString
|
||||
* Fixed `make init` to work with older versions of git
|
||||
* Fixed specs can now run independant specs for those who cant build deps. Closes #127
|
||||
* Fixed specs can now run independent specs for those who cant build deps. Closes #127
|
||||
* Fixed issues introduced by the node url module changes. Closes 126.
|
||||
* Fixed two assertions failing due to Collection#keys() returning strings
|
||||
* Fixed faulty Collection#toArray() spec due to keys() returning strings
|
||||
|
||||
113
Readme.md
113
Readme.md
@@ -1,48 +1,58 @@
|
||||
[](http://expressjs.com/)
|
||||
[](http://expressjs.com/)
|
||||
|
||||
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
|
||||
|
||||
[](http://badge.fury.io/js/express)
|
||||
[](https://travis-ci.org/visionmedia/express)
|
||||
[](https://coveralls.io/r/visionmedia/express)
|
||||
[](https://www.gittip.com/visionmedia/)
|
||||
[](https://www.npmjs.org/package/express)
|
||||
[](https://travis-ci.org/strongloop/express)
|
||||
[](https://coveralls.io/r/strongloop/express)
|
||||
[](https://www.gittip.com/dougwilson/)
|
||||
|
||||
```js
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.send('Hello World');
|
||||
});
|
||||
app.get('/', function (req, res) {
|
||||
res.send('Hello World')
|
||||
})
|
||||
|
||||
app.listen(3000);
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/visionmedia/express/wiki/New-features-in-4.x).
|
||||
**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
|
||||
### Installation
|
||||
|
||||
$ npm install express
|
||||
```bash
|
||||
$ npm install express
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
The quickest way to get started with express is to utilize the executable [`express(1)`](http://github.com/expressjs/generator) to generate an application as shown below:
|
||||
|
||||
Install the executable. The executable's major version will match Express's:
|
||||
|
||||
$ npm install -g express-generator@3
|
||||
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:
|
||||
|
||||
Create the app:
|
||||
Install the executable. The executable's major version will match Express's:
|
||||
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
```bash
|
||||
$ npm install -g express-generator@4
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
Create the app:
|
||||
|
||||
$ npm install
|
||||
```bash
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
```
|
||||
|
||||
Start the server:
|
||||
Install dependencies:
|
||||
|
||||
$ npm start
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Start the server:
|
||||
|
||||
```bash
|
||||
$ npm start
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
@@ -61,53 +71,58 @@ app.listen(3000);
|
||||
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](http://github.com/visionmedia/consolidate.js),
|
||||
14 template engines via [Consolidate.js](https://github.com/visionmedia/consolidate.js),
|
||||
you can quickly craft your perfect framework.
|
||||
|
||||
## More Information
|
||||
|
||||
* [Website and Documentation](http://expressjs.com/) stored at [visionmedia/expressjs.com](https://github.com/visionmedia/expressjs.com)
|
||||
* Join #express on freenode
|
||||
* [Google Group](http://groups.google.com/group/express-js) for discussion
|
||||
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) and [defunctzombie](https://twitter.com/defunctzombie) on twitter for updates
|
||||
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
|
||||
* [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:
|
||||
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
|
||||
|
||||
$ git clone git://github.com/visionmedia/express.git --depth 1
|
||||
$ cd express
|
||||
$ npm install
|
||||
```bash
|
||||
$ git clone git://github.com/strongloop/express.git --depth 1
|
||||
$ cd express
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Then run whichever tests you want:
|
||||
Then run whichever example you want:
|
||||
|
||||
$ node examples/content-negotiation
|
||||
|
||||
You can also view live examples here:
|
||||
You can also view live examples here:
|
||||
|
||||
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
|
||||
<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 invoke the following command within the repo, installing the development dependencies:
|
||||
|
||||
$ npm install
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Then run the tests:
|
||||
Then run the tests:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
$ npm test
|
||||
```
|
||||
|
||||
## Contributors
|
||||
|
||||
Author: [TJ Holowaychuk](http://github.com/visionmedia)
|
||||
Lead Maintainer: [Roman Shtylman](https://github.com/defunctzombie)
|
||||
Contributors: https://github.com/visionmedia/express/graphs/contributors
|
||||
### Contributors
|
||||
|
||||
## License
|
||||
* 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)
|
||||
|
||||
MIT
|
||||
### License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
var express = require('../..');
|
||||
var hash = require('./pass').hash;
|
||||
var bodyParser = require('body-parser');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var session = require('express-session');
|
||||
|
||||
var app = module.exports = express();
|
||||
@@ -17,9 +16,12 @@ app.set('views', __dirname + '/views');
|
||||
|
||||
// middleware
|
||||
|
||||
app.use(bodyParser());
|
||||
app.use(cookieParser('shhhh, very secret'));
|
||||
app.use(session());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(session({
|
||||
resave: false, // don't save session if unmodified
|
||||
saveUninitialized: false, // don't create session until something stored
|
||||
secret: 'shhhh, very secret'
|
||||
}));
|
||||
|
||||
// Session-persisted message middleware
|
||||
|
||||
|
||||
@@ -3,15 +3,11 @@
|
||||
*/
|
||||
|
||||
var express = require('../../');
|
||||
var cookie-parser = require('cookie-parser');
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
// pass a secret to cookieParser() for signed cookies
|
||||
app.use(cookieParser('manny is cool'));
|
||||
|
||||
// add req.session cookie support
|
||||
app.use(cookieSession());
|
||||
app.use(cookieSession({ secret: 'manny is cool' }));
|
||||
|
||||
// do something with the session
|
||||
app.use(count);
|
||||
|
||||
@@ -17,8 +17,8 @@ if ('test' != process.env.NODE_ENV) app.use(logger(':method :url'));
|
||||
// for signing the cookies.
|
||||
app.use(cookieParser('my secret here'));
|
||||
|
||||
// parses json, x-www-form-urlencoded, and multipart/form-data
|
||||
app.use(bodyParser());
|
||||
// parses x-www-form-urlencoded
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
|
||||
app.get('/', function(req, res){
|
||||
if (req.cookies.remember) {
|
||||
|
||||
@@ -15,7 +15,7 @@ app.use(express.static(__dirname + '/public'));
|
||||
// api middleware
|
||||
|
||||
api.use(logger('dev'));
|
||||
api.use(bodyParser());
|
||||
api.use(bodyParser.json());
|
||||
|
||||
/**
|
||||
* CORS support.
|
||||
@@ -25,7 +25,7 @@ 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', 'GET, POST');
|
||||
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);
|
||||
@@ -33,12 +33,12 @@ api.all('*', function(req, res, next){
|
||||
});
|
||||
|
||||
/**
|
||||
* POST a user.
|
||||
* PUT an existing user.
|
||||
*/
|
||||
|
||||
api.post('/user', function(req, res){
|
||||
api.put('/user/:id', function(req, res){
|
||||
console.log(req.body);
|
||||
res.send(201);
|
||||
res.send(204);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<body>
|
||||
<script>
|
||||
var req = new XMLHttpRequest;
|
||||
req.open('POST', 'http://localhost:3001/user', false);
|
||||
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>
|
||||
</html>
|
||||
|
||||
@@ -10,6 +10,7 @@ app.get('/', function(req, res){
|
||||
+ '<li>Download <a href="/files/amazing.txt">amazing.txt</a>.</li>'
|
||||
+ '<li>Download <a href="/files/utf-8 한中日.txt">utf-8 한中日.txt</a>.</li>'
|
||||
+ '<li>Download <a href="/files/missing.txt">missing.txt</a>.</li>'
|
||||
+ '<li>Download <a href="/files/CCTV大赛上海分赛区.txt">CCTV大赛上海分赛区.txt</a>.</li>'
|
||||
+ '</ul>');
|
||||
});
|
||||
|
||||
|
||||
2
examples/downloads/files/CCTV大赛上海分赛区.txt
Normal file
2
examples/downloads/files/CCTV大赛上海分赛区.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Only for test.
|
||||
The file name is faked.
|
||||
@@ -20,7 +20,8 @@ function error(err, req, res, next) {
|
||||
if (!test) console.error(err.stack);
|
||||
|
||||
// respond with 500 "Internal Server Error".
|
||||
res.send(500);
|
||||
res.status(500);
|
||||
res.send('Internal Server Error');
|
||||
}
|
||||
|
||||
app.get('/', function(req, res){
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var session = require('express-session');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var bodyParser = require('body-parser');
|
||||
var methodOverride = require('method-override');
|
||||
|
||||
@@ -38,11 +37,14 @@ if (!module.parent) app.use(logger('dev'));
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// session support
|
||||
app.use(cookieParser('some secret here'));
|
||||
app.use(session());
|
||||
app.use(session({
|
||||
resave: false, // don't save session if unmodified
|
||||
saveUninitialized: false, // don't create session until something stored
|
||||
secret: 'some secret here'
|
||||
}));
|
||||
|
||||
// parse request bodies (req.body)
|
||||
app.use(bodyParser());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
// allow overriding methods in query (?_method=put)
|
||||
app.use(methodOverride('_method'));
|
||||
|
||||
@@ -7,18 +7,26 @@ var app = express();
|
||||
var logger = require('morgan');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var bodyParser = require('body-parser');
|
||||
var methodOverride = require('method-override');
|
||||
var site = require('./site');
|
||||
var post = require('./post');
|
||||
var user = require('./user');
|
||||
|
||||
module.exports = app;
|
||||
|
||||
// Config
|
||||
|
||||
app.set('view engine', 'jade');
|
||||
app.set('views', __dirname + '/views');
|
||||
app.use(logger('dev'));
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.use(express.logger('dev'));
|
||||
}
|
||||
|
||||
app.use(methodOverride('_method'));
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// General
|
||||
@@ -27,7 +35,7 @@ app.get('/', site.index);
|
||||
|
||||
// User
|
||||
|
||||
app.all('/users', user.list);
|
||||
app.get('/users', user.list);
|
||||
app.all('/user/:id/:op?', user.load);
|
||||
app.get('/user/:id', user.view);
|
||||
app.get('/user/:id/view', user.view);
|
||||
|
||||
@@ -15,7 +15,9 @@ exports.load = function(req, res, next){
|
||||
if (req.user) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('cannot find user ' + id));
|
||||
var err = new Error('cannot find user ' + id);
|
||||
err.status = 404;
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,4 +42,4 @@ exports.update = function(req, res){
|
||||
req.user.name = user.name;
|
||||
req.user.email = user.email;
|
||||
res.redirect('back');
|
||||
};
|
||||
};
|
||||
|
||||
@@ -47,14 +47,14 @@ app.get('/search/:query?', function(req, res){
|
||||
});
|
||||
|
||||
/**
|
||||
* GET client javascript. Here we use sendfile()
|
||||
* GET client javascript. Here we use sendFile()
|
||||
* because serving __dirname with the static() middleware
|
||||
* would also mean serving our server "index.js" and the "search.jade"
|
||||
* template.
|
||||
*/
|
||||
|
||||
app.get('/client.js', function(req, res){
|
||||
res.sendfile(__dirname + '/client.js');
|
||||
res.sendFile(__dirname + '/client.js');
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
|
||||
@@ -3,18 +3,16 @@
|
||||
// $ redis-server
|
||||
|
||||
var express = require('../..');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var session = require('express-session');
|
||||
|
||||
var app = express();
|
||||
|
||||
// Required by session() middleware
|
||||
// pass the secret for signed cookies
|
||||
// (required by session())
|
||||
app.use(cookieParser('keyboard cat'));
|
||||
|
||||
// Populates req.session
|
||||
app.use(session());
|
||||
app.use(session({
|
||||
resave: false, // don't save session if unmodified
|
||||
saveUninitialized: false, // don't create session until something stored
|
||||
secret: 'keyboard cat'
|
||||
}));
|
||||
|
||||
app.get('/', function(req, res){
|
||||
var body = '';
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var session = require('express-session');
|
||||
|
||||
// pass the express to the connect redis module
|
||||
@@ -15,13 +14,13 @@ var app = express();
|
||||
|
||||
app.use(logger('dev'));
|
||||
|
||||
// Required by session() middleware
|
||||
// pass the secret for signed cookies
|
||||
// (required by session())
|
||||
app.use(cookieParser('keyboard cat'));
|
||||
|
||||
// Populates req.session
|
||||
app.use(session({ store: new RedisStore }));
|
||||
app.use(session({
|
||||
resave: false, // don't save session if unmodified
|
||||
saveUninitialized: false, // don't create session until something stored
|
||||
secret: 'keyboard cat',
|
||||
store: new RedisStore
|
||||
}));
|
||||
|
||||
app.get('/', function(req, res){
|
||||
var body = '';
|
||||
|
||||
@@ -34,13 +34,13 @@ function GithubView(name, options){
|
||||
GithubView.prototype.render = function(options, fn){
|
||||
var self = this;
|
||||
var opts = {
|
||||
host: 'rawgithub.com',
|
||||
port: 80,
|
||||
host: 'raw.githubusercontent.com',
|
||||
port: 443,
|
||||
path: this.path,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
http.request(opts, function(res) {
|
||||
https.request(opts, function(res) {
|
||||
var buf = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(str){ buf += str });
|
||||
|
||||
@@ -23,7 +23,7 @@ app.engine('md', function(str, options, fn){
|
||||
});
|
||||
|
||||
// pointing to a particular github repo to load files from it
|
||||
app.set('views', 'visionmedia/express');
|
||||
app.set('views', 'strongloop/express');
|
||||
|
||||
// register a new view constructor
|
||||
app.set('view', GithubView);
|
||||
@@ -36,7 +36,7 @@ app.get('/', function(req, res){
|
||||
});
|
||||
|
||||
app.get('/Readme.md', function(req, res){
|
||||
// rendering a view from https://github.com/visionmedia/express/blob/master/Readme.md
|
||||
// rendering a view from https://github.com/strongloop/express/blob/master/Readme.md
|
||||
res.render('Readme.md');
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ function error(status, msg) {
|
||||
app.use('/api', function(req, res, next){
|
||||
var key = req.query['api-key'];
|
||||
|
||||
// key isnt present
|
||||
// key isn't present
|
||||
if (!key) return next(error(400, 'api key required'));
|
||||
|
||||
// key is invalid
|
||||
@@ -49,7 +49,7 @@ var apiKeys = ['foo', 'bar', 'baz'];
|
||||
// these two objects will serve as our faux database
|
||||
|
||||
var repos = [
|
||||
{ name: 'express', url: 'http://github.com/visionmedia/express' }
|
||||
{ name: 'express', url: 'http://github.com/strongloop/express' }
|
||||
, { name: 'stylus', url: 'http://github.com/learnboost/stylus' }
|
||||
, { name: 'cluster', url: 'http://github.com/learnboost/cluster' }
|
||||
];
|
||||
@@ -93,14 +93,16 @@ app.get('/api/user/:name/repos', function(req, res, next){
|
||||
app.use(function(err, req, res, next){
|
||||
// whatever you want here, feel free to populate
|
||||
// properties on `err` to treat it differently in here.
|
||||
res.send(err.status || 500, { error: err.message });
|
||||
res.status(err.status || 500);
|
||||
res.send({ error: err.message });
|
||||
});
|
||||
|
||||
// our custom JSON 404 middleware. Since it's placed last
|
||||
// it will be the last middleware called, if all others
|
||||
// invoke next() and do not respond.
|
||||
app.use(function(req, res){
|
||||
res.send(404, { error: "Lame, can't find that" });
|
||||
res.status(404);
|
||||
res.send({ error: "Lame, can't find that" });
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var finalhandler = require('finalhandler');
|
||||
var flatten = require('./utils').flatten;
|
||||
var mixin = require('utils-merge');
|
||||
var escapeHtml = require('escape-html');
|
||||
var Router = require('./router');
|
||||
var methods = require('methods');
|
||||
var middleware = require('./middleware/init');
|
||||
@@ -12,9 +13,11 @@ 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('./utils').deprecate;
|
||||
var deprecate = require('depd')('express');
|
||||
var resolve = require('path').resolve;
|
||||
var slice = Array.prototype.slice;
|
||||
|
||||
/**
|
||||
* Application prototype.
|
||||
@@ -51,6 +54,7 @@ app.defaultConfiguration = function(){
|
||||
this.set('etag', 'weak');
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
this.set('env', env);
|
||||
this.set('query parser', 'extended');
|
||||
this.set('subdomain offset', 2);
|
||||
this.set('trust proxy', false);
|
||||
|
||||
@@ -104,7 +108,7 @@ app.lazyrouter = function() {
|
||||
strict: this.enabled('strict routing')
|
||||
});
|
||||
|
||||
this._router.use(query());
|
||||
this._router.use(query(this.get('query parser fn')));
|
||||
this._router.use(middleware.init(this));
|
||||
}
|
||||
};
|
||||
@@ -119,45 +123,22 @@ app.lazyrouter = function() {
|
||||
*/
|
||||
|
||||
app.handle = function(req, res, done) {
|
||||
var env = this.get('env');
|
||||
var router = this._router;
|
||||
|
||||
this._router.handle(req, res, function(err) {
|
||||
if (done) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
// unhandled error
|
||||
if (err) {
|
||||
// default to 500
|
||||
if (res.statusCode < 400) res.statusCode = 500;
|
||||
debug('default %s', res.statusCode);
|
||||
|
||||
// respect err.status
|
||||
if (err.status) res.statusCode = err.status;
|
||||
|
||||
// production gets a basic error message
|
||||
var msg = 'production' == env
|
||||
? http.STATUS_CODES[res.statusCode]
|
||||
: err.stack || err.toString();
|
||||
msg = escapeHtml(msg);
|
||||
|
||||
// log to stderr in a non-test env
|
||||
if ('test' != env) console.error(err.stack || err.toString());
|
||||
if (res.headersSent) return req.socket.destroy();
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader('Content-Length', Buffer.byteLength(msg));
|
||||
if ('HEAD' == req.method) return res.end();
|
||||
res.end(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 404
|
||||
debug('default 404');
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
if ('HEAD' == req.method) return res.end();
|
||||
res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
|
||||
// final handler
|
||||
done = done || finalhandler(req, res, {
|
||||
env: this.get('env'),
|
||||
onerror: logerror.bind(this)
|
||||
});
|
||||
|
||||
// no routes
|
||||
if (!router) {
|
||||
debug('no routes defined on app');
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
router.handle(req, res, done);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -170,37 +151,60 @@ app.handle = function(req, res, done) {
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.use = function(route, fn){
|
||||
var mount_app;
|
||||
app.use = function use(fn) {
|
||||
var offset = 0;
|
||||
var path = '/';
|
||||
var self = this;
|
||||
|
||||
// default route to '/'
|
||||
if ('string' != typeof route) fn = route, route = '/';
|
||||
// default path to '/'
|
||||
// disambiguate app.use([fn])
|
||||
if (typeof fn !== 'function') {
|
||||
var arg = fn;
|
||||
|
||||
// express app
|
||||
if (fn.handle && fn.set) mount_app = fn;
|
||||
while (Array.isArray(arg) && arg.length !== 0) {
|
||||
arg = arg[0];
|
||||
}
|
||||
|
||||
// restore .app property on req and res
|
||||
if (mount_app) {
|
||||
debug('.use app under %s', route);
|
||||
mount_app.mountpath = route;
|
||||
fn = function(req, res, next) {
|
||||
// first arg is the path
|
||||
if (typeof arg !== 'function') {
|
||||
offset = 1;
|
||||
path = fn;
|
||||
}
|
||||
}
|
||||
|
||||
var fns = flatten(slice.call(arguments, offset));
|
||||
|
||||
if (fns.length === 0) {
|
||||
throw new TypeError('app.use() requires middleware functions');
|
||||
}
|
||||
|
||||
// setup router
|
||||
this.lazyrouter();
|
||||
var router = this._router;
|
||||
|
||||
fns.forEach(function (fn) {
|
||||
// non-express app
|
||||
if (!fn || !fn.handle || !fn.set) {
|
||||
return router.use(path, fn);
|
||||
}
|
||||
|
||||
debug('.use app under %s', path);
|
||||
fn.mountpath = path;
|
||||
fn.parent = self;
|
||||
|
||||
// restore .app property on req and res
|
||||
router.use(path, function mounted_app(req, res, next) {
|
||||
var orig = req.app;
|
||||
mount_app.handle(req, res, function(err) {
|
||||
fn.handle(req, res, function (err) {
|
||||
req.__proto__ = orig.request;
|
||||
res.__proto__ = orig.response;
|
||||
next(err);
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
this.lazyrouter();
|
||||
this._router.use(route, fn);
|
||||
|
||||
// mounted an app
|
||||
if (mount_app) {
|
||||
mount_app.parent = this;
|
||||
mount_app.emit('mount', this);
|
||||
}
|
||||
// mounted an app
|
||||
fn.emit('mount', self);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -318,6 +322,10 @@ app.set = function(setting, val){
|
||||
debug('compile etag %s', val);
|
||||
this.set('etag fn', compileETag(val));
|
||||
break;
|
||||
case 'query parser':
|
||||
debug('compile query parser %s', val);
|
||||
this.set('query parser fn', compileQueryParser(val));
|
||||
break;
|
||||
case 'trust proxy':
|
||||
debug('compile trust proxy %s', val);
|
||||
this.set('trust proxy fn', compileTrust(val));
|
||||
@@ -420,7 +428,7 @@ methods.forEach(function(method){
|
||||
this.lazyrouter();
|
||||
|
||||
var route = this._router.route(path);
|
||||
route[method].apply(route, [].slice.call(arguments, 1));
|
||||
route[method].apply(route, slice.call(arguments, 1));
|
||||
return this;
|
||||
};
|
||||
});
|
||||
@@ -439,7 +447,7 @@ app.all = function(path){
|
||||
this.lazyrouter();
|
||||
|
||||
var route = this._router.route(path);
|
||||
var args = [].slice.call(arguments, 1);
|
||||
var args = slice.call(arguments, 1);
|
||||
methods.forEach(function(method){
|
||||
route[method].apply(route, args);
|
||||
});
|
||||
@@ -449,7 +457,7 @@ app.all = function(path){
|
||||
|
||||
// del -> delete alias
|
||||
|
||||
app.del = deprecate(app.delete, 'app.del: Use app.delete instead');
|
||||
app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead');
|
||||
|
||||
/**
|
||||
* Render the given view `name` name with `options`
|
||||
@@ -547,3 +555,14 @@ app.listen = function(){
|
||||
var server = http.createServer(this);
|
||||
return server.listen.apply(server, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Log error using console.error.
|
||||
*
|
||||
* @param {Error} err
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function logerror(err){
|
||||
if (this.get('env') !== 'test') console.error(err.stack || err.toString());
|
||||
}
|
||||
|
||||
@@ -2,36 +2,27 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var qs = require('qs');
|
||||
var parseUrl = require('parseurl');
|
||||
var qs = require('qs');
|
||||
|
||||
/**
|
||||
* Query:
|
||||
*
|
||||
* Automatically parse the query-string when available,
|
||||
* populating the `req.query` object using
|
||||
* [qs](https://github.com/visionmedia/node-querystring).
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* .use(connect.query())
|
||||
* .use(function(req, res){
|
||||
* res.end(JSON.stringify(req.query));
|
||||
* });
|
||||
*
|
||||
* The `options` passed are provided to qs.parse function.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function query(options){
|
||||
module.exports = function query(options) {
|
||||
var queryparse = qs.parse;
|
||||
|
||||
if (typeof options === 'function') {
|
||||
queryparse = options;
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
return function query(req, res, next){
|
||||
if (!req.query) {
|
||||
req.query = ~req.url.indexOf('?')
|
||||
? qs.parse(parseUrl(req).query, options)
|
||||
: {};
|
||||
var val = parseUrl(req).query;
|
||||
req.query = queryparse(val, options);
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
100
lib/request.js
100
lib/request.js
@@ -3,6 +3,8 @@
|
||||
*/
|
||||
|
||||
var accepts = require('accepts');
|
||||
var deprecate = require('depd')('express');
|
||||
var isIP = require('net').isIP;
|
||||
var typeis = require('type-is');
|
||||
var http = require('http');
|
||||
var fresh = require('fresh');
|
||||
@@ -106,53 +108,55 @@ req.accepts = function(){
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given `encoding` is accepted.
|
||||
* Check if the given `encoding`s are accepted.
|
||||
*
|
||||
* @param {String} encoding
|
||||
* @param {String} ...encoding
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.acceptsEncoding = // backwards compatibility
|
||||
req.acceptsEncodings = function(){
|
||||
var accept = accepts(this);
|
||||
return accept.encodings.apply(accept, arguments);
|
||||
};
|
||||
|
||||
req.acceptsEncoding = deprecate.function(req.acceptsEncodings,
|
||||
'req.acceptsEncoding: Use acceptsEncodings instead');
|
||||
|
||||
/**
|
||||
* To do: update docs.
|
||||
*
|
||||
* Check if the given `charset` is acceptable,
|
||||
* Check if the given `charset`s are acceptable,
|
||||
* otherwise you should respond with 406 "Not Acceptable".
|
||||
*
|
||||
* @param {String} charset
|
||||
* @param {String} ...charset
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.acceptsCharset = // backwards compatibility
|
||||
req.acceptsCharsets = function(){
|
||||
var accept = accepts(this);
|
||||
return accept.charsets.apply(accept, arguments);
|
||||
};
|
||||
|
||||
req.acceptsCharset = deprecate.function(req.acceptsCharsets,
|
||||
'req.acceptsCharset: Use acceptsCharsets instead');
|
||||
|
||||
/**
|
||||
* To do: update docs.
|
||||
*
|
||||
* Check if the given `lang` is acceptable,
|
||||
* Check if the given `lang`s are acceptable,
|
||||
* otherwise you should respond with 406 "Not Acceptable".
|
||||
*
|
||||
* @param {String} lang
|
||||
* @param {String} ...lang
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.acceptsLanguage = // backwards compatibility
|
||||
req.acceptsLanguages = function(){
|
||||
var accept = accepts(this);
|
||||
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`.
|
||||
@@ -241,7 +245,9 @@ req.is = function(types){
|
||||
* Return the protocol string "http" or "https"
|
||||
* when requested with TLS. When the "trust proxy"
|
||||
* setting trusts the socket address, the
|
||||
* "X-Forwarded-Proto" header field will be trusted.
|
||||
* "X-Forwarded-Proto" header field will be trusted
|
||||
* and used if present.
|
||||
*
|
||||
* If you're running behind a reverse proxy that
|
||||
* supplies https for you this may be enabled.
|
||||
*
|
||||
@@ -249,18 +255,19 @@ req.is = function(types){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('protocol', function(){
|
||||
defineGetter(req, 'protocol', function protocol(){
|
||||
var proto = this.connection.encrypted
|
||||
? 'https'
|
||||
: 'http';
|
||||
var trust = this.app.get('trust proxy fn');
|
||||
|
||||
if (!trust(this.connection.remoteAddress)) {
|
||||
return this.connection.encrypted
|
||||
? 'https'
|
||||
: 'http';
|
||||
return proto;
|
||||
}
|
||||
|
||||
// Note: X-Forwarded-Proto is normally only ever a
|
||||
// single value, but this is to be safe.
|
||||
var proto = this.get('X-Forwarded-Proto') || 'http';
|
||||
proto = this.get('X-Forwarded-Proto') || proto;
|
||||
return proto.split(/\s*,\s*/)[0];
|
||||
});
|
||||
|
||||
@@ -273,7 +280,7 @@ req.__defineGetter__('protocol', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('secure', function(){
|
||||
defineGetter(req, 'secure', function secure(){
|
||||
return 'https' == this.protocol;
|
||||
});
|
||||
|
||||
@@ -287,7 +294,7 @@ req.__defineGetter__('secure', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('ip', function(){
|
||||
defineGetter(req, 'ip', function ip(){
|
||||
var trust = this.app.get('trust proxy fn');
|
||||
return proxyaddr(this, trust);
|
||||
});
|
||||
@@ -304,7 +311,7 @@ req.__defineGetter__('ip', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('ips', function(){
|
||||
defineGetter(req, 'ips', function ips() {
|
||||
var trust = this.app.get('trust proxy fn');
|
||||
var addrs = proxyaddr.all(this, trust);
|
||||
return addrs.slice(1).reverse();
|
||||
@@ -325,12 +332,17 @@ req.__defineGetter__('ips', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('subdomains', function(){
|
||||
defineGetter(req, 'subdomains', function subdomains() {
|
||||
var hostname = this.hostname;
|
||||
|
||||
if (!hostname) return [];
|
||||
|
||||
var offset = this.app.get('subdomain offset');
|
||||
return (this.host || '')
|
||||
.split('.')
|
||||
.reverse()
|
||||
.slice(offset);
|
||||
var subdomains = !isIP(hostname)
|
||||
? hostname.split('.').reverse()
|
||||
: [hostname];
|
||||
|
||||
return subdomains.slice(offset);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -340,12 +352,12 @@ req.__defineGetter__('subdomains', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('path', function(){
|
||||
defineGetter(req, 'path', function path() {
|
||||
return parse(this).pathname;
|
||||
});
|
||||
|
||||
/**
|
||||
* Parse the "Host" header field hostname.
|
||||
* Parse the "Host" header field to a hostname.
|
||||
*
|
||||
* When the "trust proxy" setting trusts the socket
|
||||
* address, the "X-Forwarded-Host" header field will
|
||||
@@ -355,7 +367,7 @@ req.__defineGetter__('path', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('host', function(){
|
||||
defineGetter(req, 'hostname', function hostname(){
|
||||
var trust = this.app.get('trust proxy fn');
|
||||
var host = this.get('X-Forwarded-Host');
|
||||
|
||||
@@ -376,6 +388,12 @@ req.__defineGetter__('host', function(){
|
||||
: 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
|
||||
@@ -385,7 +403,7 @@ req.__defineGetter__('host', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('fresh', function(){
|
||||
defineGetter(req, 'fresh', function(){
|
||||
var method = this.method;
|
||||
var s = this.res.statusCode;
|
||||
|
||||
@@ -409,7 +427,7 @@ req.__defineGetter__('fresh', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('stale', function(){
|
||||
defineGetter(req, 'stale', function stale(){
|
||||
return !this.fresh;
|
||||
});
|
||||
|
||||
@@ -420,7 +438,23 @@ req.__defineGetter__('stale', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('xhr', function(){
|
||||
defineGetter(req, 'xhr', function xhr(){
|
||||
var val = this.get('X-Requested-With') || '';
|
||||
return 'xmlhttprequest' == val.toLowerCase();
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper function for creating a getter on an object.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} name
|
||||
* @param {Function} getter
|
||||
* @api private
|
||||
*/
|
||||
function defineGetter(obj, name, getter) {
|
||||
Object.defineProperty(obj, name, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: getter
|
||||
});
|
||||
};
|
||||
|
||||
484
lib/response.js
484
lib/response.js
@@ -2,8 +2,11 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
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 sign = require('cookie-signature').sign;
|
||||
@@ -11,13 +14,12 @@ var normalizeType = require('./utils').normalizeType;
|
||||
var normalizeTypes = require('./utils').normalizeTypes;
|
||||
var setCharset = require('./utils').setCharset;
|
||||
var contentDisposition = require('./utils').contentDisposition;
|
||||
var deprecate = require('./utils').deprecate;
|
||||
var statusCodes = http.STATUS_CODES;
|
||||
var cookie = require('cookie');
|
||||
var send = require('send');
|
||||
var basename = path.basename;
|
||||
var extname = path.extname;
|
||||
var mime = send.mime;
|
||||
var resolve = path.resolve;
|
||||
var vary = require('vary');
|
||||
|
||||
/**
|
||||
@@ -72,80 +74,99 @@ res.links = function(links){
|
||||
* res.send(new Buffer('wahoo'));
|
||||
* res.send({ some: 'json' });
|
||||
* res.send('<p>some html</p>');
|
||||
* res.send(404, 'Sorry, cant find that');
|
||||
* res.send(404);
|
||||
*
|
||||
* @param {string|number|boolean|object|Buffer} body
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.send = function(body){
|
||||
var req = this.req;
|
||||
var head = 'HEAD' == req.method;
|
||||
var type;
|
||||
res.send = function send(body) {
|
||||
var chunk = body;
|
||||
var encoding;
|
||||
var len;
|
||||
var req = this.req;
|
||||
var type;
|
||||
|
||||
// settings
|
||||
var app = this.app;
|
||||
|
||||
// allow status / body
|
||||
if (2 == arguments.length) {
|
||||
if (arguments.length === 2) {
|
||||
// res.send(body, status) backwards compat
|
||||
if ('number' != typeof body && 'number' == typeof arguments[1]) {
|
||||
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 {
|
||||
this.statusCode = body;
|
||||
body = arguments[1];
|
||||
deprecate('res.send(status, body): Use res.status(status).send(body) instead');
|
||||
this.statusCode = arguments[0];
|
||||
chunk = arguments[1];
|
||||
}
|
||||
}
|
||||
|
||||
switch (typeof body) {
|
||||
// response status
|
||||
case 'number':
|
||||
this.get('Content-Type') || this.type('txt');
|
||||
this.statusCode = body;
|
||||
body = http.STATUS_CODES[body];
|
||||
break;
|
||||
// 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.status(status).end() instead');
|
||||
this.statusCode = chunk;
|
||||
chunk = http.STATUS_CODES[chunk];
|
||||
}
|
||||
|
||||
switch (typeof chunk) {
|
||||
// string defaulting to html
|
||||
case 'string':
|
||||
if (!this.get('Content-Type')) this.type('html');
|
||||
if (!this.get('Content-Type')) {
|
||||
this.type('html');
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'object':
|
||||
if (null == body) {
|
||||
body = '';
|
||||
} else if (Buffer.isBuffer(body)) {
|
||||
this.get('Content-Type') || this.type('bin');
|
||||
if (chunk === null) {
|
||||
chunk = '';
|
||||
} else if (Buffer.isBuffer(chunk)) {
|
||||
if (!this.get('Content-Type')) {
|
||||
this.type('bin');
|
||||
}
|
||||
} else {
|
||||
return this.json(body);
|
||||
return this.json(chunk);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// write strings in utf-8
|
||||
if ('string' === typeof body) {
|
||||
if (typeof chunk === 'string') {
|
||||
encoding = 'utf8';
|
||||
type = this.get('Content-Type');
|
||||
|
||||
// reflect this in content-type
|
||||
if ('string' === typeof type) {
|
||||
if (typeof type === 'string') {
|
||||
this.set('Content-Type', setCharset(type, 'utf-8'));
|
||||
}
|
||||
}
|
||||
|
||||
// populate Content-Length
|
||||
if (undefined !== body && !this.get('Content-Length')) {
|
||||
len = Buffer.isBuffer(body)
|
||||
? body.length
|
||||
: Buffer.byteLength(body, encoding);
|
||||
if (chunk !== undefined) {
|
||||
if (!Buffer.isBuffer(chunk)) {
|
||||
// convert chunk to Buffer; saves later double conversions
|
||||
chunk = new Buffer(chunk, encoding);
|
||||
encoding = undefined;
|
||||
}
|
||||
|
||||
len = chunk.length;
|
||||
this.set('Content-Length', len);
|
||||
}
|
||||
|
||||
// method check
|
||||
var isHead = req.method === 'HEAD';
|
||||
|
||||
// ETag support
|
||||
var etag = len !== undefined && app.get('etag fn');
|
||||
if (etag && ('GET' === req.method || 'HEAD' === req.method)) {
|
||||
if (!this.get('ETag')) {
|
||||
etag = etag(body, encoding);
|
||||
if (len !== undefined && (isHead || req.method === 'GET')) {
|
||||
var etag = app.get('etag fn');
|
||||
if (etag && !this.get('ETag')) {
|
||||
etag = etag(chunk, encoding);
|
||||
etag && this.set('ETag', etag);
|
||||
}
|
||||
}
|
||||
@@ -158,11 +179,16 @@ res.send = function(body){
|
||||
this.removeHeader('Content-Type');
|
||||
this.removeHeader('Content-Length');
|
||||
this.removeHeader('Transfer-Encoding');
|
||||
body = '';
|
||||
chunk = '';
|
||||
}
|
||||
|
||||
// skip body for HEAD
|
||||
if (isHead) {
|
||||
this.end();
|
||||
}
|
||||
|
||||
// respond
|
||||
this.end((head ? null : body), encoding);
|
||||
this.end(chunk, encoding);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -174,24 +200,24 @@ res.send = function(body){
|
||||
*
|
||||
* res.json(null);
|
||||
* res.json({ user: 'tj' });
|
||||
* res.json(500, 'oh noes!');
|
||||
* res.json(404, 'I dont have that');
|
||||
*
|
||||
* @param {string|number|boolean|object} obj
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.json = function(obj){
|
||||
res.json = function json(obj) {
|
||||
var val = obj;
|
||||
|
||||
// allow status / body
|
||||
if (2 == arguments.length) {
|
||||
if (arguments.length === 2) {
|
||||
// res.json(body, status) backwards compat
|
||||
if ('number' == typeof arguments[1]) {
|
||||
if (typeof arguments[1] === 'number') {
|
||||
deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
|
||||
this.statusCode = arguments[1];
|
||||
return 'number' === typeof obj
|
||||
? jsonNumDeprecated.call(this, obj)
|
||||
: jsonDeprecated.call(this, obj);
|
||||
} else {
|
||||
this.statusCode = obj;
|
||||
obj = arguments[1];
|
||||
deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
|
||||
this.statusCode = arguments[0];
|
||||
val = arguments[1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,20 +225,16 @@ res.json = function(obj){
|
||||
var app = this.app;
|
||||
var replacer = app.get('json replacer');
|
||||
var spaces = app.get('json spaces');
|
||||
var body = JSON.stringify(obj, replacer, spaces);
|
||||
var body = JSON.stringify(val, replacer, spaces);
|
||||
|
||||
// content-type
|
||||
this.get('Content-Type') || this.set('Content-Type', 'application/json');
|
||||
if (!this.get('Content-Type')) {
|
||||
this.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
return this.send(body);
|
||||
};
|
||||
|
||||
var jsonDeprecated = deprecate(res.json,
|
||||
'res.json(obj, status): Use res.json(status, obj) instead');
|
||||
|
||||
var jsonNumDeprecated = deprecate(res.json,
|
||||
'res.json(num, status): Use res.status(status).json(num) instead');
|
||||
|
||||
/**
|
||||
* Send JSON response with JSONP callback support.
|
||||
*
|
||||
@@ -220,24 +242,24 @@ var jsonNumDeprecated = deprecate(res.json,
|
||||
*
|
||||
* res.jsonp(null);
|
||||
* res.jsonp({ user: 'tj' });
|
||||
* res.jsonp(500, 'oh noes!');
|
||||
* res.jsonp(404, 'I dont have that');
|
||||
*
|
||||
* @param {string|number|boolean|object} obj
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.jsonp = function(obj){
|
||||
res.jsonp = function jsonp(obj) {
|
||||
var val = obj;
|
||||
|
||||
// allow status / body
|
||||
if (2 == arguments.length) {
|
||||
if (arguments.length === 2) {
|
||||
// res.json(body, status) backwards compat
|
||||
if ('number' == typeof arguments[1]) {
|
||||
if (typeof arguments[1] === 'number') {
|
||||
deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
|
||||
this.statusCode = arguments[1];
|
||||
return 'number' === typeof obj
|
||||
? jsonpNumDeprecated.call(this, obj)
|
||||
: jsonpDeprecated.call(this, obj);
|
||||
} else {
|
||||
this.statusCode = obj;
|
||||
obj = arguments[1];
|
||||
deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
|
||||
this.statusCode = arguments[0];
|
||||
val = arguments[1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,13 +267,14 @@ res.jsonp = function(obj){
|
||||
var app = this.app;
|
||||
var replacer = app.get('json replacer');
|
||||
var spaces = app.get('json spaces');
|
||||
var body = JSON.stringify(obj, replacer, spaces)
|
||||
.replace(/\u2028/g, '\\u2028')
|
||||
.replace(/\u2029/g, '\\u2029');
|
||||
var body = JSON.stringify(val, replacer, spaces);
|
||||
var callback = this.req.query[app.get('jsonp callback name')];
|
||||
|
||||
// content-type
|
||||
this.get('Content-Type') || this.set('Content-Type', 'application/json');
|
||||
if (!this.get('Content-Type')) {
|
||||
this.set('X-Content-Type-Options', 'nosniff');
|
||||
this.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
// fixup callback
|
||||
if (Array.isArray(callback)) {
|
||||
@@ -259,20 +282,50 @@ res.jsonp = function(obj){
|
||||
}
|
||||
|
||||
// jsonp
|
||||
if (callback && 'string' === typeof callback) {
|
||||
if (typeof callback === 'string' && callback.length !== 0) {
|
||||
this.charset = 'utf-8';
|
||||
this.set('X-Content-Type-Options', 'nosniff');
|
||||
this.set('Content-Type', 'text/javascript');
|
||||
var cb = callback.replace(/[^\[\]\w$.]/g, '');
|
||||
body = 'typeof ' + cb + ' === \'function\' && ' + cb + '(' + body + ');';
|
||||
|
||||
// restrict callback charset
|
||||
callback = callback.replace(/[^\[\]\w$.]/g, '');
|
||||
|
||||
// replace chars not allowed in JavaScript that are in JSON
|
||||
body = body
|
||||
.replace(/\u2028/g, '\\u2028')
|
||||
.replace(/\u2029/g, '\\u2029');
|
||||
|
||||
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
|
||||
// the typeof check is just to reduce client error noise
|
||||
body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
|
||||
}
|
||||
|
||||
return this.send(body);
|
||||
};
|
||||
|
||||
var jsonpDeprecated = deprecate(res.json,
|
||||
'res.jsonp(obj, status): Use res.jsonp(status, obj) instead');
|
||||
/**
|
||||
* Send given HTTP status code.
|
||||
*
|
||||
* Sets the response status to `statusCode` and the body of the
|
||||
* response to the standard description from node's http.STATUS_CODES
|
||||
* or the statusCode number if no description.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* res.sendStatus(200);
|
||||
*
|
||||
* @param {number} statusCode
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var jsonpNumDeprecated = deprecate(res.json,
|
||||
'res.jsonp(num, status): Use res.status(status).jsonp(num) instead');
|
||||
res.sendStatus = function sendStatus(statusCode) {
|
||||
var body = http.STATUS_CODES[statusCode] || String(statusCode);
|
||||
|
||||
this.statusCode = statusCode;
|
||||
this.type('txt');
|
||||
|
||||
return this.send(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transfer the file at the given `path`.
|
||||
@@ -285,9 +338,88 @@ var jsonpNumDeprecated = deprecate(res.json,
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `maxAge` defaulting to 0
|
||||
* - `root` root directory for relative filenames
|
||||
* - `hidden` serve hidden files, defaulting to false
|
||||
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
|
||||
* - `root` root directory for relative filenames
|
||||
* - `headers` object of headers to serve with file
|
||||
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
|
||||
*
|
||||
* Other options are passed along to `send`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* The following example illustrates how `res.sendFile()` may
|
||||
* be used as an alternative for the `static()` middleware for
|
||||
* dynamic situations. The code backing `res.sendFile()` is actually
|
||||
* the same code, so HTTP cache support etc is identical.
|
||||
*
|
||||
* app.get('/user/:uid/photos/:file', function(req, res){
|
||||
* var uid = req.params.uid
|
||||
* , file = req.params.file;
|
||||
*
|
||||
* req.user.mayViewFilesFrom(uid, function(yes){
|
||||
* if (yes) {
|
||||
* res.sendFile('/uploads/' + uid + '/' + file);
|
||||
* } else {
|
||||
* res.send(403, 'Sorry! you cant see that.');
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.sendFile = function sendFile(path, options, fn) {
|
||||
var req = this.req;
|
||||
var res = this;
|
||||
var next = req.next;
|
||||
|
||||
if (!path) {
|
||||
throw new TypeError('path argument is required to res.sendFile');
|
||||
}
|
||||
|
||||
// support function as second arg
|
||||
if (typeof options === 'function') {
|
||||
fn = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (!options.root && !isAbsolute(path)) {
|
||||
throw new TypeError('path must be absolute or specify root to res.sendFile');
|
||||
}
|
||||
|
||||
// create file stream
|
||||
var pathname = encodeURI(path);
|
||||
var file = send(req, pathname, options);
|
||||
|
||||
// transfer
|
||||
sendfile(res, file, options, function (err) {
|
||||
if (fn) return fn(err);
|
||||
if (err && err.code === 'EISDIR') return next();
|
||||
|
||||
// next() all but aborted errors
|
||||
if (err && err.code !== 'ECONNABORT') {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Transfer the file at the given `path`.
|
||||
*
|
||||
* Automatically sets the _Content-Type_ response header field.
|
||||
* The callback `fn(err)` is invoked when the transfer is complete
|
||||
* or when an error occurs. Be sure to check `res.sentHeader`
|
||||
* if you wish to attempt responding, as the header and some data
|
||||
* may have already been transferred.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
|
||||
* - `root` root directory for relative filenames
|
||||
* - `headers` object of headers to serve with file
|
||||
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
|
||||
*
|
||||
* Other options are passed along to `send`.
|
||||
*
|
||||
@@ -315,65 +447,36 @@ var jsonpNumDeprecated = deprecate(res.json,
|
||||
*/
|
||||
|
||||
res.sendfile = function(path, options, fn){
|
||||
options = options || {};
|
||||
var self = this;
|
||||
var req = self.req;
|
||||
var next = this.req.next;
|
||||
var done;
|
||||
|
||||
var req = this.req;
|
||||
var res = this;
|
||||
var next = req.next;
|
||||
|
||||
// support function as second arg
|
||||
if ('function' == typeof options) {
|
||||
if (typeof options === 'function') {
|
||||
fn = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
// socket errors
|
||||
req.socket.on('error', error);
|
||||
options = options || {};
|
||||
|
||||
// errors
|
||||
function error(err) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
|
||||
// clean up
|
||||
cleanup();
|
||||
if (!self.headersSent) self.removeHeader('Content-Disposition');
|
||||
|
||||
// callback available
|
||||
if (fn) return fn(err);
|
||||
|
||||
// list in limbo if there's no callback
|
||||
if (self.headersSent) return;
|
||||
|
||||
// delegate
|
||||
next(err);
|
||||
}
|
||||
|
||||
// streaming
|
||||
function stream(stream) {
|
||||
if (done) return;
|
||||
cleanup();
|
||||
if (fn) stream.on('end', fn);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
function cleanup() {
|
||||
req.socket.removeListener('error', error);
|
||||
}
|
||||
|
||||
// Back-compat
|
||||
options.maxage = options.maxage || options.maxAge || 0;
|
||||
// create file stream
|
||||
var file = send(req, path, options);
|
||||
|
||||
// transfer
|
||||
var file = send(req, path, options);
|
||||
file.on('error', error);
|
||||
file.on('directory', next);
|
||||
file.on('stream', stream);
|
||||
file.pipe(this);
|
||||
this.on('finish', cleanup);
|
||||
sendfile(res, file, options, function (err) {
|
||||
if (fn) return fn(err);
|
||||
if (err && err.code === 'EISDIR') return next();
|
||||
|
||||
// next() all but aborted errors
|
||||
if (err && err.code !== 'ECONNABORT') {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
res.sendfile = deprecate.function(res.sendfile,
|
||||
'res.sendfile: Use res.sendFile instead');
|
||||
|
||||
/**
|
||||
* Transfer the file at the given `path` as an attachment.
|
||||
*
|
||||
@@ -387,16 +490,24 @@ res.sendfile = function(path, options, fn){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.download = function(path, filename, fn){
|
||||
res.download = function download(path, filename, fn) {
|
||||
// support function as second arg
|
||||
if ('function' == typeof filename) {
|
||||
if (typeof filename === 'function') {
|
||||
fn = filename;
|
||||
filename = null;
|
||||
}
|
||||
|
||||
filename = filename || path;
|
||||
this.set('Content-Disposition', contentDisposition(filename));
|
||||
return this.sendfile(path, fn);
|
||||
|
||||
// set Content-Disposition when file is sent
|
||||
var headers = {
|
||||
'Content-Disposition': contentDisposition(filename)
|
||||
};
|
||||
|
||||
// Resolve the full path for sendFile
|
||||
var fullPath = resolve(path);
|
||||
|
||||
return this.sendFile(fullPath, { headers: headers }, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -540,8 +651,8 @@ res.attachment = function(filename){
|
||||
*/
|
||||
|
||||
res.set =
|
||||
res.header = function(field, val){
|
||||
if (2 == arguments.length) {
|
||||
res.header = function header(field, val) {
|
||||
if (arguments.length === 2) {
|
||||
if (Array.isArray(val)) val = val.map(String);
|
||||
else val = String(val);
|
||||
if ('content-type' == field.toLowerCase() && !/;\s*charset\s*=/.test(val)) {
|
||||
@@ -574,7 +685,7 @@ res.get = function(field){
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Object} options
|
||||
* @param {ServerResponse} for chaining
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -605,6 +716,7 @@ res.clearCookie = function(name, options){
|
||||
* @param {String} name
|
||||
* @param {String|Object} val
|
||||
* @param {Options} options
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -650,6 +762,7 @@ res.cookie = function(name, val, options){
|
||||
* res.location('../login');
|
||||
*
|
||||
* @param {String} url
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -677,32 +790,30 @@ res.location = function(url){
|
||||
* res.redirect('/foo/bar');
|
||||
* res.redirect('http://example.com');
|
||||
* res.redirect(301, 'http://example.com');
|
||||
* res.redirect('http://example.com', 301);
|
||||
* res.redirect('../login'); // /blog/post/1 -> /blog/login
|
||||
*
|
||||
* @param {String} url
|
||||
* @param {Number} code
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.redirect = function(url){
|
||||
var head = 'HEAD' == this.req.method;
|
||||
var status = 302;
|
||||
res.redirect = function redirect(url) {
|
||||
var address = url;
|
||||
var body;
|
||||
var status = 302;
|
||||
|
||||
// allow status / url
|
||||
if (2 == arguments.length) {
|
||||
if ('number' == typeof url) {
|
||||
status = url;
|
||||
url = arguments[1];
|
||||
if (arguments.length === 2) {
|
||||
if (typeof arguments[0] === 'number') {
|
||||
status = arguments[0];
|
||||
address = arguments[1];
|
||||
} else {
|
||||
deprecate('res.redirect(ur, status): Use res.redirect(status, url) instead');
|
||||
status = arguments[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Set location header
|
||||
this.location(url);
|
||||
url = this.get('Location');
|
||||
this.location(address);
|
||||
address = this.get('Location');
|
||||
|
||||
// Support text/{plain,html} by default
|
||||
this.format({
|
||||
@@ -723,7 +834,12 @@ res.redirect = function(url){
|
||||
// Respond
|
||||
this.statusCode = status;
|
||||
this.set('Content-Length', Buffer.byteLength(body));
|
||||
this.end(head ? null : body);
|
||||
|
||||
if (this.req.method === 'HEAD') {
|
||||
this.end();
|
||||
}
|
||||
|
||||
this.end(body);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -731,14 +847,16 @@ res.redirect = function(url){
|
||||
* this call is simply ignored.
|
||||
*
|
||||
* @param {Array|String} field
|
||||
* @param {ServerResponse} for chaining
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.vary = function(field){
|
||||
// checks for back-compat
|
||||
if (!field) return this;
|
||||
if (Array.isArray(field) && !field.length) return this;
|
||||
if (!field || (Array.isArray(field) && !field.length)) {
|
||||
deprecate('res.vary(): Provide a field name');
|
||||
return this;
|
||||
}
|
||||
|
||||
vary(this, field);
|
||||
|
||||
@@ -781,3 +899,69 @@ res.render = function(view, options, fn){
|
||||
// render
|
||||
app.render(view, options, fn);
|
||||
};
|
||||
|
||||
// pipe the send file stream
|
||||
function sendfile(res, file, options, callback) {
|
||||
var done = false;
|
||||
|
||||
// directory
|
||||
function ondirectory() {
|
||||
if (done) return;
|
||||
done = true;
|
||||
|
||||
var err = new Error('EISDIR, read');
|
||||
err.code = 'EISDIR';
|
||||
callback(err);
|
||||
}
|
||||
|
||||
// errors
|
||||
function onerror(err) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
callback(err);
|
||||
}
|
||||
|
||||
// ended
|
||||
function onend() {
|
||||
if (done) return;
|
||||
done = true;
|
||||
callback();
|
||||
}
|
||||
|
||||
// finished
|
||||
function onfinish(err) {
|
||||
if (err) return onerror(err);
|
||||
if (done) return;
|
||||
|
||||
setImmediate(function () {
|
||||
if (done) return;
|
||||
done = true;
|
||||
|
||||
// response finished before end of file
|
||||
var err = new Error('Request aborted');
|
||||
err.code = 'ECONNABORT';
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
file.on('end', onend);
|
||||
file.on('error', onerror);
|
||||
file.on('directory', ondirectory);
|
||||
onFinished(res, onfinish);
|
||||
|
||||
if (options.headers) {
|
||||
// set headers on successful transfer
|
||||
file.on('headers', function headers(res) {
|
||||
var obj = options.headers;
|
||||
var keys = Object.keys(obj);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var k = keys[i];
|
||||
res.setHeader(k, obj[k]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// pipe
|
||||
file.pipe(res);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -5,9 +6,18 @@
|
||||
var Route = require('./route');
|
||||
var Layer = require('./layer');
|
||||
var methods = require('methods');
|
||||
var mixin = require('utils-merge');
|
||||
var debug = require('debug')('express:router');
|
||||
var parseUrl = require('parseurl');
|
||||
var utils = require('../utils');
|
||||
|
||||
/**
|
||||
* Module variables.
|
||||
*/
|
||||
|
||||
var objectRegExp = /^\[object (\S+)\]$/;
|
||||
var slice = Array.prototype.slice;
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
/**
|
||||
* Initialize a new `Router` with the given `options`.
|
||||
@@ -30,6 +40,7 @@ var proto = module.exports = function(options) {
|
||||
router.params = {};
|
||||
router._params = [];
|
||||
router.caseSensitive = options.caseSensitive;
|
||||
router.mergeParams = options.mergeParams;
|
||||
router.strict = options.strict;
|
||||
router.stack = [];
|
||||
|
||||
@@ -113,8 +124,6 @@ proto.handle = function(req, res, done) {
|
||||
|
||||
debug('dispatching %s %s', req.method, req.url);
|
||||
|
||||
var method = req.method.toLowerCase();
|
||||
|
||||
var search = 1 + req.url.indexOf('?');
|
||||
var pathlength = search ? search - 1 : req.url.length;
|
||||
var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://');
|
||||
@@ -132,17 +141,15 @@ proto.handle = function(req, res, done) {
|
||||
var stack = self.stack;
|
||||
|
||||
// manage inter-router variables
|
||||
var parent = req.next;
|
||||
var parentParams = req.params;
|
||||
var parentUrl = req.baseUrl || '';
|
||||
done = wrap(done, function(old, err) {
|
||||
req.baseUrl = parentUrl;
|
||||
req.next = parent;
|
||||
old(err);
|
||||
});
|
||||
done = restore(done, req, 'baseUrl', 'next', 'params');
|
||||
|
||||
// setup next layer
|
||||
req.next = next;
|
||||
|
||||
// for options requests, respond with a default if nothing else responds
|
||||
if (method === 'options') {
|
||||
if (req.method === 'OPTIONS') {
|
||||
done = wrap(done, function(old, err) {
|
||||
if (err || options.length === 0) return old(err);
|
||||
|
||||
@@ -151,35 +158,38 @@ proto.handle = function(req, res, done) {
|
||||
});
|
||||
}
|
||||
|
||||
// setup basic req values
|
||||
req.baseUrl = parentUrl;
|
||||
req.originalUrl = req.originalUrl || req.url;
|
||||
|
||||
next();
|
||||
|
||||
function next(err) {
|
||||
if (err === 'route') {
|
||||
err = undefined;
|
||||
}
|
||||
var layerError = err === 'route'
|
||||
? null
|
||||
: err;
|
||||
|
||||
var layer = stack[idx++];
|
||||
var layerPath;
|
||||
|
||||
if (!layer) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (slashAdded) {
|
||||
req.url = req.url.substr(1);
|
||||
slashAdded = false;
|
||||
}
|
||||
|
||||
req.baseUrl = parentUrl;
|
||||
req.url = protohost + removed + req.url.substr(protohost.length);
|
||||
req.originalUrl = req.originalUrl || req.url;
|
||||
removed = '';
|
||||
if (removed.length !== 0) {
|
||||
req.baseUrl = parentUrl;
|
||||
req.url = protohost + removed + req.url.substr(protohost.length);
|
||||
removed = '';
|
||||
}
|
||||
|
||||
try {
|
||||
var path = parseUrl(req).pathname;
|
||||
if (undefined == path) path = '/';
|
||||
if (!layer) {
|
||||
return done(layerError);
|
||||
}
|
||||
|
||||
if (!layer.match(path)) return next(err);
|
||||
self.match_layer(layer, req, res, function (err, path) {
|
||||
if (err || path === undefined) {
|
||||
return next(layerError || err);
|
||||
}
|
||||
|
||||
// route object and not middleware
|
||||
var route = layer.route;
|
||||
@@ -187,46 +197,56 @@ proto.handle = function(req, res, done) {
|
||||
// if final route, then we support options
|
||||
if (route) {
|
||||
// we don't run any routes with error first
|
||||
if (err) {
|
||||
return next(err);
|
||||
if (layerError) {
|
||||
return next(layerError);
|
||||
}
|
||||
|
||||
req.route = route;
|
||||
var method = req.method;
|
||||
var has_method = route._handles_method(method);
|
||||
|
||||
// we can now dispatch to the route
|
||||
if (method === 'options' && !route.methods['options']) {
|
||||
// build up automatic options response
|
||||
if (!has_method && method === 'OPTIONS') {
|
||||
options.push.apply(options, route._options());
|
||||
}
|
||||
|
||||
// don't even bother
|
||||
if (!has_method && method !== 'HEAD') {
|
||||
return next();
|
||||
}
|
||||
|
||||
// we can now dispatch to the route
|
||||
req.route = route;
|
||||
}
|
||||
|
||||
// Capture one-time layer values
|
||||
req.params = layer.params;
|
||||
layerPath = layer.path;
|
||||
req.params = self.mergeParams
|
||||
? mergeParams(layer.params, parentParams)
|
||||
: layer.params;
|
||||
var layerPath = layer.path;
|
||||
|
||||
// this should be done for the layer
|
||||
return self.process_params(layer, paramcalled, req, res, function(err) {
|
||||
self.process_params(layer, paramcalled, req, res, function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
return next(layerError || err);
|
||||
}
|
||||
|
||||
if (route) {
|
||||
return layer.handle(req, res, next);
|
||||
return layer.handle_request(req, res, next);
|
||||
}
|
||||
|
||||
trim_prefix();
|
||||
trim_prefix(layer, layerError, layerPath, path);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
function trim_prefix(layer, layerError, layerPath, path) {
|
||||
var c = path[layerPath.length];
|
||||
if (c && '/' !== c && '.' !== c) return next(layerError);
|
||||
|
||||
function trim_prefix() {
|
||||
var c = path[layerPath.length];
|
||||
if (c && '/' != c && '.' != c) return next(err);
|
||||
|
||||
// Trim off the part of the url that matches the route
|
||||
// middleware (.use stuff) needs to have the path stripped
|
||||
debug('trim prefix (%s) from url %s', removed, req.url);
|
||||
// Trim off the part of the url that matches the route
|
||||
// middleware (.use stuff) needs to have the path stripped
|
||||
if (layerPath.length !== 0) {
|
||||
debug('trim prefix (%s) from url %s', layerPath, req.url);
|
||||
removed = layerPath;
|
||||
req.url = protohost + req.url.substr(protohost.length + removed.length);
|
||||
|
||||
@@ -237,34 +257,42 @@ proto.handle = function(req, res, done) {
|
||||
}
|
||||
|
||||
// Setup base URL (no trailing slash)
|
||||
if (removed.length && removed.substr(-1) === '/') {
|
||||
req.baseUrl = parentUrl + removed.substring(0, removed.length - 1);
|
||||
} else {
|
||||
req.baseUrl = parentUrl + removed;
|
||||
}
|
||||
req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
|
||||
? removed.substring(0, removed.length - 1)
|
||||
: removed);
|
||||
}
|
||||
|
||||
debug('%s %s : %s', layer.handle.name || 'anonymous', layerPath, req.originalUrl);
|
||||
var arity = layer.handle.length;
|
||||
try {
|
||||
if (err && arity === 4) {
|
||||
layer.handle(err, req, res, next);
|
||||
} else if (!err && arity < 4) {
|
||||
layer.handle(req, res, next);
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
|
||||
|
||||
if (layerError) {
|
||||
layer.handle_error(layerError, req, res, next);
|
||||
} else {
|
||||
layer.handle_request(req, res, next);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function wrap(old, fn) {
|
||||
return function () {
|
||||
var args = [old].concat(slice.call(arguments));
|
||||
fn.apply(this, args);
|
||||
};
|
||||
/**
|
||||
* Match request to a layer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
proto.match_layer = function match_layer(layer, req, res, done) {
|
||||
var error = null;
|
||||
var path;
|
||||
|
||||
try {
|
||||
path = parseUrl(req).pathname;
|
||||
|
||||
if (!layer.match(path)) {
|
||||
path = undefined;
|
||||
}
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
done(error, path);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -320,29 +348,44 @@ proto.process_params = function(layer, called, req, res, done) {
|
||||
}
|
||||
|
||||
// param previously called with same value or error occurred
|
||||
if (paramCalled && (paramCalled.err || paramCalled.val === paramVal)) {
|
||||
return param(paramCalled.err);
|
||||
if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) {
|
||||
// restore value
|
||||
req.params[name] = paramCalled.value;
|
||||
|
||||
// next param
|
||||
return param(paramCalled.error);
|
||||
}
|
||||
|
||||
called[name] = paramCalled = { val: paramVal };
|
||||
called[name] = paramCalled = {
|
||||
error: null,
|
||||
match: paramVal,
|
||||
value: paramVal
|
||||
};
|
||||
|
||||
try {
|
||||
return paramCallback();
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
paramCallback();
|
||||
}
|
||||
|
||||
// single param callbacks
|
||||
function paramCallback(err) {
|
||||
if (err && paramCalled) {
|
||||
var fn = paramCallbacks[paramIndex++];
|
||||
|
||||
// store updated value
|
||||
paramCalled.value = req.params[key.name];
|
||||
|
||||
if (err) {
|
||||
// store error
|
||||
paramCalled.err = err;
|
||||
paramCalled.error = err;
|
||||
param(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var fn = paramCallbacks[paramIndex++];
|
||||
if (err || !fn) return param(err);
|
||||
fn(req, res, paramCallback, paramVal, key.name);
|
||||
if (!fn) return param();
|
||||
|
||||
try {
|
||||
fn(req, res, paramCallback, paramVal, key.name);
|
||||
} catch (e) {
|
||||
paramCallback(e);
|
||||
}
|
||||
}
|
||||
|
||||
param();
|
||||
@@ -360,40 +403,55 @@ proto.process_params = function(layer, called, req, res, done) {
|
||||
* handlers can operate without any code changes regardless of the "prefix"
|
||||
* pathname.
|
||||
*
|
||||
* @param {String|Function} route
|
||||
* @param {Function} fn
|
||||
* @return {app} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
proto.use = function(route, fn){
|
||||
// default route to '/'
|
||||
if ('string' != typeof route) {
|
||||
fn = route;
|
||||
route = '/';
|
||||
}
|
||||
proto.use = function use(fn) {
|
||||
var offset = 0;
|
||||
var path = '/';
|
||||
var self = this;
|
||||
|
||||
// default path to '/'
|
||||
// disambiguate router.use([fn])
|
||||
if (typeof fn !== 'function') {
|
||||
var type = {}.toString.call(fn);
|
||||
var msg = 'Router.use() requires callback functions but got a ' + type;
|
||||
throw new Error(msg);
|
||||
var arg = fn;
|
||||
|
||||
while (Array.isArray(arg) && arg.length !== 0) {
|
||||
arg = arg[0];
|
||||
}
|
||||
|
||||
// first arg is the path
|
||||
if (typeof arg !== 'function') {
|
||||
offset = 1;
|
||||
path = fn;
|
||||
}
|
||||
}
|
||||
|
||||
// strip trailing slash
|
||||
if ('/' == route[route.length - 1]) {
|
||||
route = route.slice(0, -1);
|
||||
var callbacks = utils.flatten(slice.call(arguments, offset));
|
||||
|
||||
if (callbacks.length === 0) {
|
||||
throw new TypeError('Router.use() requires middleware functions');
|
||||
}
|
||||
|
||||
var layer = new Layer(route, {
|
||||
sensitive: this.caseSensitive,
|
||||
strict: this.strict,
|
||||
end: false
|
||||
}, fn);
|
||||
callbacks.forEach(function (fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
|
||||
}
|
||||
|
||||
// add the middleware
|
||||
debug('use %s %s', route || '/', fn.name || 'anonymous');
|
||||
// add the middleware
|
||||
debug('use %s %s', path, fn.name || '<anonymous>');
|
||||
|
||||
var layer = new Layer(path, {
|
||||
sensitive: self.caseSensitive,
|
||||
strict: false,
|
||||
end: false
|
||||
}, fn);
|
||||
|
||||
layer.route = undefined;
|
||||
|
||||
self.stack.push(layer);
|
||||
});
|
||||
|
||||
this.stack.push(layer);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -429,7 +487,90 @@ proto.route = function(path){
|
||||
methods.concat('all').forEach(function(method){
|
||||
proto[method] = function(path){
|
||||
var route = this.route(path)
|
||||
route[method].apply(route, [].slice.call(arguments, 1));
|
||||
route[method].apply(route, slice.call(arguments, 1));
|
||||
return this;
|
||||
};
|
||||
});
|
||||
|
||||
// get type for error message
|
||||
function gettype(obj) {
|
||||
var type = typeof obj;
|
||||
|
||||
if (type !== 'object') {
|
||||
return type;
|
||||
}
|
||||
|
||||
// inspect [[Class]] for objects
|
||||
return toString.call(obj)
|
||||
.replace(objectRegExp, '$1');
|
||||
}
|
||||
|
||||
// merge params with parent params
|
||||
function mergeParams(params, parent) {
|
||||
if (typeof parent !== 'object' || !parent) {
|
||||
return params;
|
||||
}
|
||||
|
||||
// make copy of parent for base
|
||||
var obj = mixin({}, parent);
|
||||
|
||||
// simple non-numeric merging
|
||||
if (!(0 in params) || !(0 in parent)) {
|
||||
return mixin(obj, params);
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
var o = 0;
|
||||
|
||||
// determine numeric gaps
|
||||
while (i === o || o in parent) {
|
||||
if (i in params) i++;
|
||||
if (o in parent) o++;
|
||||
}
|
||||
|
||||
// offset numeric indices in params before merge
|
||||
for (i--; i >= 0; i--) {
|
||||
params[i + o] = params[i];
|
||||
|
||||
// create holes for the merge when necessary
|
||||
if (i < o) {
|
||||
delete params[i];
|
||||
}
|
||||
}
|
||||
|
||||
return mixin(parent, params);
|
||||
}
|
||||
|
||||
// restore obj props after function
|
||||
function restore(fn, obj) {
|
||||
var props = new Array(arguments.length - 2);
|
||||
var vals = new Array(arguments.length - 2);
|
||||
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
props[i] = arguments[i + 2];
|
||||
vals[i] = obj[props[i]];
|
||||
}
|
||||
|
||||
return function(err){
|
||||
// restore vals
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
obj[props[i]] = vals[i];
|
||||
}
|
||||
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// wrap a function
|
||||
function wrap(old, fn) {
|
||||
return function proxy() {
|
||||
var args = new Array(arguments.length + 1);
|
||||
|
||||
args[0] = old;
|
||||
for (var i = 0, len = arguments.length; i < len; i++) {
|
||||
args[i + 1] = arguments[i];
|
||||
}
|
||||
|
||||
fn.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,10 +18,67 @@ function Layer(path, options, fn) {
|
||||
|
||||
debug('new %s', path);
|
||||
options = options || {};
|
||||
this.regexp = pathRegexp(path, this.keys = [], options);
|
||||
|
||||
this.handle = fn;
|
||||
this.name = fn.name || '<anonymous>';
|
||||
this.params = undefined;
|
||||
this.path = undefined;
|
||||
this.regexp = pathRegexp(path, this.keys = [], options);
|
||||
|
||||
if (path === '/' && options.end === false) {
|
||||
this.regexp.fast_slash = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
try {
|
||||
fn(error, req, res, next);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the request for the layer.
|
||||
*
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {function} next
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Layer.prototype.handle_request = function handle(req, res, next) {
|
||||
var fn = this.handle;
|
||||
|
||||
if (fn.length > 3) {
|
||||
// not a standard request handler
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
fn(req, res, next);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this route matches `path`, if so
|
||||
* populate `.params`.
|
||||
@@ -31,30 +88,35 @@ function Layer(path, options, fn) {
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Layer.prototype.match = function(path){
|
||||
var keys = this.keys;
|
||||
var params = this.params = {};
|
||||
Layer.prototype.match = function match(path) {
|
||||
if (this.regexp.fast_slash) {
|
||||
// fast path non-ending match for / (everything matches)
|
||||
this.params = {};
|
||||
this.path = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
var m = this.regexp.exec(path);
|
||||
|
||||
if (!m) {
|
||||
this.params = undefined;
|
||||
this.path = undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
// store values
|
||||
this.params = {};
|
||||
this.path = m[0];
|
||||
|
||||
var keys = this.keys;
|
||||
var params = this.params;
|
||||
var n = 0;
|
||||
var key;
|
||||
var val;
|
||||
|
||||
if (!m) return false;
|
||||
|
||||
this.path = m[0];
|
||||
|
||||
for (var i = 1, len = m.length; i < len; ++i) {
|
||||
key = keys[i - 1];
|
||||
|
||||
try {
|
||||
val = 'string' == typeof m[i]
|
||||
? decodeURIComponent(m[i])
|
||||
: m[i];
|
||||
} catch(e) {
|
||||
var err = new Error("Failed to decode param '" + m[i] + "'");
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
val = decode_param(m[i]);
|
||||
|
||||
if (key) {
|
||||
params[key.name] = val;
|
||||
@@ -65,3 +127,25 @@ Layer.prototype.match = function(path){
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
56
lib/router/match.js
Normal file
56
lib/router/match.js
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
var debug = require('debug')('express:router:route');
|
||||
var Layer = require('./layer');
|
||||
var methods = require('methods');
|
||||
var utils = require('../utils');
|
||||
|
||||
@@ -22,12 +23,30 @@ module.exports = Route;
|
||||
function Route(path) {
|
||||
debug('new %s', path);
|
||||
this.path = path;
|
||||
this.stack = undefined;
|
||||
this.stack = [];
|
||||
|
||||
// route handlers for various http methods
|
||||
this.methods = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Route.prototype._handles_method = function _handles_method(method) {
|
||||
if (this.methods._all) {
|
||||
return true;
|
||||
}
|
||||
|
||||
method = method.toLowerCase();
|
||||
|
||||
if (method === 'head' && !this.methods['head']) {
|
||||
method = 'get';
|
||||
}
|
||||
|
||||
return Boolean(this.methods[method]);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Array} supported HTTP methods
|
||||
* @api private
|
||||
@@ -46,28 +65,22 @@ Route.prototype._options = function(){
|
||||
*/
|
||||
|
||||
Route.prototype.dispatch = function(req, res, done){
|
||||
var self = this;
|
||||
var method = req.method.toLowerCase();
|
||||
var idx = 0;
|
||||
var stack = this.stack;
|
||||
if (stack.length === 0) {
|
||||
return done();
|
||||
}
|
||||
|
||||
var method = req.method.toLowerCase();
|
||||
if (method === 'head' && !this.methods['head']) {
|
||||
method = 'get';
|
||||
}
|
||||
|
||||
req.route = self;
|
||||
req.route = this;
|
||||
|
||||
// single middleware route case
|
||||
if (typeof this.stack === 'function') {
|
||||
this.stack(req, res, done);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
|
||||
var stack = self.stack;
|
||||
if (!stack) {
|
||||
return done();
|
||||
}
|
||||
|
||||
var idx = 0;
|
||||
(function next_layer(err) {
|
||||
function next(err) {
|
||||
if (err && err === 'route') {
|
||||
return done();
|
||||
}
|
||||
@@ -78,33 +91,15 @@ Route.prototype.dispatch = function(req, res, done){
|
||||
}
|
||||
|
||||
if (layer.method && layer.method !== method) {
|
||||
return next_layer(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var arity = layer.handle.length;
|
||||
if (err) {
|
||||
if (arity < 4) {
|
||||
return next_layer(err);
|
||||
}
|
||||
|
||||
try {
|
||||
layer.handle(err, req, res, next_layer);
|
||||
} catch (err) {
|
||||
next_layer(err);
|
||||
}
|
||||
return;
|
||||
layer.handle_error(err, req, res, next);
|
||||
} else {
|
||||
layer.handle_request(req, res, next);
|
||||
}
|
||||
|
||||
if (arity > 3) {
|
||||
return next_layer();
|
||||
}
|
||||
|
||||
try {
|
||||
layer.handle(req, res, next_layer);
|
||||
} catch (err) {
|
||||
next_layer(err);
|
||||
}
|
||||
})();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -145,15 +140,11 @@ Route.prototype.all = function(){
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
if (!self.stack) {
|
||||
self.stack = fn;
|
||||
}
|
||||
else if (typeof self.stack === 'function') {
|
||||
self.stack = [{ handle: self.stack }, { handle: fn }];
|
||||
}
|
||||
else {
|
||||
self.stack.push({ handle: fn });
|
||||
}
|
||||
var layer = Layer('/', {}, fn);
|
||||
layer.method = undefined;
|
||||
|
||||
self.methods._all = true;
|
||||
self.stack.push(layer);
|
||||
});
|
||||
|
||||
return self;
|
||||
@@ -173,18 +164,11 @@ methods.forEach(function(method){
|
||||
|
||||
debug('%s %s', method, self.path);
|
||||
|
||||
if (!self.methods[method]) {
|
||||
self.methods[method] = true;
|
||||
}
|
||||
var layer = Layer('/', {}, fn);
|
||||
layer.method = method;
|
||||
|
||||
if (!self.stack) {
|
||||
self.stack = [];
|
||||
}
|
||||
else if (typeof self.stack === 'function') {
|
||||
self.stack = [{ handle: self.stack }];
|
||||
}
|
||||
|
||||
self.stack.push({ method: method, handle: fn });
|
||||
self.methods[method] = true;
|
||||
self.stack.push(layer);
|
||||
});
|
||||
return self;
|
||||
};
|
||||
|
||||
133
lib/utils.js
133
lib/utils.js
@@ -3,40 +3,12 @@
|
||||
*/
|
||||
|
||||
var mime = require('send').mime;
|
||||
var crc32 = require('buffer-crc32');
|
||||
var crypto = require('crypto');
|
||||
var basename = require('path').basename;
|
||||
var deprecate = require('util').deprecate;
|
||||
var etag = require('etag');
|
||||
var proxyaddr = require('proxy-addr');
|
||||
|
||||
/**
|
||||
* Simple detection of charset parameter in content-type
|
||||
*/
|
||||
var charsetRegExp = /;\s*charset\s*=/;
|
||||
|
||||
/**
|
||||
* Deprecate function, like core `util.deprecate`,
|
||||
* but with NODE_ENV and color support.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {String} msg
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.deprecate = function(fn, msg){
|
||||
if (process.env.NODE_ENV === 'test') return fn;
|
||||
|
||||
// prepend module name
|
||||
msg = 'express: ' + msg;
|
||||
|
||||
if (process.stderr.isTTY) {
|
||||
// colorize
|
||||
msg = '\x1b[31;1m' + msg + '\x1b[0m';
|
||||
}
|
||||
|
||||
return deprecate(fn, msg);
|
||||
};
|
||||
var qs = require('qs');
|
||||
var querystring = require('querystring');
|
||||
var typer = require('media-typer');
|
||||
|
||||
/**
|
||||
* Return strong ETag for `body`.
|
||||
@@ -47,17 +19,12 @@ exports.deprecate = function(fn, msg){
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.etag = function etag(body, encoding){
|
||||
if (body.length === 0) {
|
||||
// fast-path empty body
|
||||
return '"1B2M2Y8AsgTpgAmY7PhCfg=="'
|
||||
}
|
||||
exports.etag = function (body, encoding) {
|
||||
var buf = !Buffer.isBuffer(body)
|
||||
? new Buffer(body, encoding)
|
||||
: body
|
||||
|
||||
var hash = crypto
|
||||
.createHash('md5')
|
||||
.update(body, encoding)
|
||||
.digest('base64')
|
||||
return '"' + hash + '"'
|
||||
return etag(buf, {weak: false})
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -70,16 +37,11 @@ exports.etag = function etag(body, encoding){
|
||||
*/
|
||||
|
||||
exports.wetag = function wetag(body, encoding){
|
||||
if (body.length === 0) {
|
||||
// fast-path empty body
|
||||
return 'W/"0-0"'
|
||||
}
|
||||
var buf = !Buffer.isBuffer(body)
|
||||
? new Buffer(body, encoding)
|
||||
: body
|
||||
|
||||
var buf = Buffer.isBuffer(body)
|
||||
? body
|
||||
: new Buffer(body, encoding)
|
||||
var len = buf.length
|
||||
return 'W/"' + len.toString(16) + '-' + crc32.unsigned(buf) + '"'
|
||||
return etag(buf, {weak: true})
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -164,7 +126,7 @@ exports.contentDisposition = function(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="' + encodeURI(filename) + '"; filename*=UTF-8\'\'' + encodeURI(filename)
|
||||
: 'attachment; filename="' + filename + '"';
|
||||
}
|
||||
|
||||
@@ -231,6 +193,41 @@ exports.compileETag = function(val) {
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile "query parser" value to function.
|
||||
*
|
||||
* @param {String|Function} val
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.compileQueryParser = function compileQueryParser(val) {
|
||||
var fn;
|
||||
|
||||
if (typeof val === 'function') {
|
||||
return val;
|
||||
}
|
||||
|
||||
switch (val) {
|
||||
case true:
|
||||
fn = querystring.parse;
|
||||
break;
|
||||
case false:
|
||||
fn = newObject;
|
||||
break;
|
||||
case 'extended':
|
||||
fn = qs.parse;
|
||||
break;
|
||||
case 'simple':
|
||||
fn = querystring.parse;
|
||||
break;
|
||||
default:
|
||||
throw new TypeError('unknown value for query parser function: ' + val);
|
||||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile "proxy trust" value to function.
|
||||
*
|
||||
@@ -272,21 +269,23 @@ exports.compileTrust = function(val) {
|
||||
exports.setCharset = function(type, charset){
|
||||
if (!type || !charset) return type;
|
||||
|
||||
var exists = charsetRegExp.test(type);
|
||||
// parse type
|
||||
var parsed = typer.parse(type);
|
||||
|
||||
// removing existing charset
|
||||
if (exists) {
|
||||
var parts = type.split(';');
|
||||
// set charset
|
||||
parsed.parameters.charset = charset;
|
||||
|
||||
for (var i = 1; i < parts.length; i++) {
|
||||
if (charsetRegExp.test(';' + parts[i])) {
|
||||
parts.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
type = parts.join(';');
|
||||
}
|
||||
|
||||
return type + '; charset=' + charset;
|
||||
// format type
|
||||
return typer.format(parsed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return new empty objet.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function newObject() {
|
||||
return {};
|
||||
}
|
||||
|
||||
71
package.json
71
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "4.4.2",
|
||||
"description": "Fast, unopinionated, minimalist web framework",
|
||||
"version": "4.9.3",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
|
||||
@@ -9,7 +9,8 @@
|
||||
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"Guillermo Rauch <rauchg@gmail.com>",
|
||||
"Jonathan Ong <me@jongleberry.com>",
|
||||
"Roman Shtylman <shtylman+expressjs@gmail.com"
|
||||
"Roman Shtylman <shtylman+expressjs@gmail.com>",
|
||||
"Young Jae Sim <hanul@hanul.me>"
|
||||
],
|
||||
"keywords": [
|
||||
"express",
|
||||
@@ -22,55 +23,61 @@
|
||||
"app",
|
||||
"api"
|
||||
],
|
||||
"repository": "visionmedia/express",
|
||||
"repository": "strongloop/express",
|
||||
"license": "MIT",
|
||||
"homepage": "http://expressjs.com/",
|
||||
"dependencies": {
|
||||
"accepts": "1.0.2",
|
||||
"buffer-crc32": "0.2.1",
|
||||
"debug": "1.0.1",
|
||||
"accepts": "~1.1.0",
|
||||
"cookie-signature": "1.0.5",
|
||||
"debug": "~2.0.0",
|
||||
"depd": "0.4.5",
|
||||
"escape-html": "1.0.1",
|
||||
"methods": "1.0.1",
|
||||
"parseurl": "1.0.1",
|
||||
"proxy-addr": "1.0.1",
|
||||
"range-parser": "1.0.0",
|
||||
"send": "0.4.2",
|
||||
"serve-static": "1.2.2",
|
||||
"type-is": "1.2.1",
|
||||
"vary": "0.1.0",
|
||||
"etag": "~1.3.1",
|
||||
"finalhandler": "0.2.0",
|
||||
"fresh": "0.2.4",
|
||||
"media-typer": "0.3.0",
|
||||
"methods": "1.1.0",
|
||||
"on-finished": "~2.1.0",
|
||||
"parseurl": "~1.3.0",
|
||||
"path-to-regexp": "0.1.3",
|
||||
"proxy-addr": "~1.0.2",
|
||||
"qs": "2.2.3",
|
||||
"range-parser": "~1.0.2",
|
||||
"send": "0.9.2",
|
||||
"serve-static": "~1.6.2",
|
||||
"type-is": "~1.5.1",
|
||||
"vary": "~1.0.0",
|
||||
"cookie": "0.1.2",
|
||||
"fresh": "0.2.2",
|
||||
"cookie-signature": "1.0.3",
|
||||
"merge-descriptors": "0.0.2",
|
||||
"utils-merge": "1.0.0",
|
||||
"qs": "0.6.6",
|
||||
"path-to-regexp": "0.1.2"
|
||||
"utils-merge": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"after": "0.8.1",
|
||||
"istanbul": "0.2.10",
|
||||
"mocha": "~1.20.1",
|
||||
"istanbul": "0.3.2",
|
||||
"mocha": "~1.21.4",
|
||||
"should": "~4.0.4",
|
||||
"supertest": "~0.13.0",
|
||||
"connect-redis": "~2.0.0",
|
||||
"ejs": "~1.0.0",
|
||||
"jade": "~1.3.1",
|
||||
"marked": "0.3.2",
|
||||
"multiparty": "~3.2.4",
|
||||
"hjs": "~0.0.6",
|
||||
"body-parser": "1.3.0",
|
||||
"cookie-parser": "1.1.0",
|
||||
"express-session": "1.2.1",
|
||||
"method-override": "2.0.2",
|
||||
"morgan": "1.1.1",
|
||||
"vhost": "2.0.0"
|
||||
"body-parser": "~1.8.2",
|
||||
"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",
|
||||
"vhost": "~3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "npm prune",
|
||||
"test": "mocha --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
|
||||
"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/",
|
||||
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/"
|
||||
}
|
||||
}
|
||||
|
||||
156
test/Route.js
156
test/Route.js
@@ -1,4 +1,6 @@
|
||||
|
||||
var after = require('after');
|
||||
var should = require('should');
|
||||
var express = require('../')
|
||||
, Route = express.Route
|
||||
, methods = require('methods')
|
||||
@@ -8,167 +10,182 @@ describe('Route', function(){
|
||||
|
||||
describe('.all', function(){
|
||||
it('should add handler', function(done){
|
||||
var req = { method: 'GET', url: '/' };
|
||||
var route = new Route('/foo');
|
||||
|
||||
route.all(function(req, res, next) {
|
||||
assert.equal(req.a, 1);
|
||||
assert.equal(res.b, 2);
|
||||
req.called = true;
|
||||
next();
|
||||
});
|
||||
|
||||
route.dispatch({ a:1, method: 'GET' }, { b:2 }, done);
|
||||
route.dispatch(req, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
should(req.called).be.ok;
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('should handle VERBS', function(done) {
|
||||
var route = new Route('/foo');
|
||||
|
||||
var count = 0;
|
||||
var route = new Route('/foo');
|
||||
var cb = after(methods.length, function (err) {
|
||||
if (err) return done(err);
|
||||
count.should.equal(methods.length);
|
||||
done();
|
||||
});
|
||||
|
||||
route.all(function(req, res, next) {
|
||||
count++;
|
||||
next();
|
||||
});
|
||||
|
||||
methods.forEach(function testMethod(method) {
|
||||
route.dispatch({ method: method }, {});
|
||||
var req = { method: method, url: '/' };
|
||||
route.dispatch(req, {}, cb);
|
||||
});
|
||||
|
||||
assert.equal(count, methods.length);
|
||||
done();
|
||||
})
|
||||
|
||||
it('should stack', function(done) {
|
||||
var req = { count: 0, method: 'GET', url: '/' };
|
||||
var route = new Route('/foo');
|
||||
|
||||
var count = 0;
|
||||
route.all(function(req, res, next) {
|
||||
count++;
|
||||
req.count++;
|
||||
next();
|
||||
});
|
||||
|
||||
route.all(function(req, res, next) {
|
||||
count++;
|
||||
req.count++;
|
||||
next();
|
||||
});
|
||||
|
||||
route.dispatch({ method: 'GET' }, {}, function(err) {
|
||||
assert.ifError(err);
|
||||
count++;
|
||||
route.dispatch(req, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
req.count.should.equal(2);
|
||||
done();
|
||||
});
|
||||
|
||||
assert.equal(count, 3);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
describe('.VERB', function(){
|
||||
it('should support .get', function(done){
|
||||
var req = { method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
var count = 0;
|
||||
route.get(function(req, res, next) {
|
||||
count++;
|
||||
req.called = true;
|
||||
next();
|
||||
})
|
||||
|
||||
route.dispatch({ method: 'GET' }, {});
|
||||
assert(count);
|
||||
done();
|
||||
route.dispatch(req, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
should(req.called).be.ok;
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('should limit to just .VERB', function(done){
|
||||
var req = { method: 'POST', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
route.get(function(req, res, next) {
|
||||
assert(false);
|
||||
done();
|
||||
throw new Error('not me!');
|
||||
})
|
||||
|
||||
route.post(function(req, res, next) {
|
||||
assert(true);
|
||||
req.called = true;
|
||||
next();
|
||||
})
|
||||
|
||||
route.dispatch({ method: 'post' }, {});
|
||||
done();
|
||||
route.dispatch(req, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
should(req.called).be.true;
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('should allow fallthrough', function(done){
|
||||
var req = { order: '', method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
var order = '';
|
||||
route.get(function(req, res, next) {
|
||||
order += 'a';
|
||||
req.order += 'a';
|
||||
next();
|
||||
})
|
||||
|
||||
route.all(function(req, res, next) {
|
||||
order += 'b';
|
||||
req.order += 'b';
|
||||
next();
|
||||
});
|
||||
|
||||
route.get(function(req, res, next) {
|
||||
order += 'c';
|
||||
req.order += 'c';
|
||||
next();
|
||||
})
|
||||
|
||||
route.dispatch({ method: 'get' }, {});
|
||||
assert.equal(order, 'abc');
|
||||
done();
|
||||
route.dispatch(req, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
req.order.should.equal('abc');
|
||||
done();
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe('errors', function(){
|
||||
it('should handle errors via arity 4 functions', function(done){
|
||||
var req = { order: '', method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
var order = '';
|
||||
route.all(function(req, res, next){
|
||||
next(new Error('foobar'));
|
||||
});
|
||||
|
||||
route.all(function(req, res, next){
|
||||
order += '0';
|
||||
req.order += '0';
|
||||
next();
|
||||
});
|
||||
|
||||
route.all(function(err, req, res, next){
|
||||
order += 'a';
|
||||
req.order += 'a';
|
||||
next(err);
|
||||
});
|
||||
|
||||
route.all(function(err, req, res, next){
|
||||
assert.equal(err.message, 'foobar');
|
||||
assert.equal(order, 'a');
|
||||
route.dispatch(req, {}, function (err) {
|
||||
should(err).be.ok;
|
||||
should(err.message).equal('foobar');
|
||||
req.order.should.equal('a');
|
||||
done();
|
||||
});
|
||||
|
||||
route.dispatch({ method: 'get' }, {});
|
||||
})
|
||||
|
||||
it('should handle throw', function(done) {
|
||||
var req = { order: '', method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
var order = '';
|
||||
route.all(function(req, res, next){
|
||||
throw new Error('foobar');
|
||||
});
|
||||
|
||||
route.all(function(req, res, next){
|
||||
order += '0';
|
||||
req.order += '0';
|
||||
next();
|
||||
});
|
||||
|
||||
route.all(function(err, req, res, next){
|
||||
order += 'a';
|
||||
req.order += 'a';
|
||||
next(err);
|
||||
});
|
||||
|
||||
route.all(function(err, req, res, next){
|
||||
assert.equal(err.message, 'foobar');
|
||||
assert.equal(order, 'a');
|
||||
route.dispatch(req, {}, function (err) {
|
||||
should(err).be.ok;
|
||||
should(err.message).equal('foobar');
|
||||
req.order.should.equal('a');
|
||||
done();
|
||||
});
|
||||
|
||||
route.dispatch({ method: 'get' }, {});
|
||||
});
|
||||
|
||||
it('should handle throwing inside error handlers', function(done) {
|
||||
var req = { method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
route.get(function(req, res, next){
|
||||
@@ -180,11 +197,42 @@ describe('Route', function(){
|
||||
});
|
||||
|
||||
route.get(function(err, req, res, next){
|
||||
assert.equal(err.message, 'oops');
|
||||
done();
|
||||
req.message = err.message;
|
||||
next();
|
||||
});
|
||||
|
||||
route.dispatch({ url: '/', method: 'GET' }, {}, done);
|
||||
route.dispatch(req, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
should(req.message).equal('oops');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle throw in .all', function(done) {
|
||||
var req = { method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
route.all(function(req, res, next){
|
||||
throw new Error('boom!');
|
||||
});
|
||||
|
||||
route.dispatch(req, {}, function(err){
|
||||
should(err).be.ok;
|
||||
err.message.should.equal('boom!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle single error handler', function(done) {
|
||||
var req = { method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
route.all(function(err, req, res, next){
|
||||
// this should not execute
|
||||
true.should.be.false;
|
||||
});
|
||||
|
||||
route.dispatch(req, {}, done);
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
142
test/Router.js
142
test/Router.js
@@ -174,6 +174,111 @@ describe('Router', function(){
|
||||
});
|
||||
})
|
||||
|
||||
describe('FQDN', function () {
|
||||
it('should not obscure FQDNs', function (done) {
|
||||
var request = { hit: 0, url: 'http://example.com/foo', method: 'GET' };
|
||||
var router = new Router();
|
||||
|
||||
router.use(function (req, res, next) {
|
||||
assert.equal(req.hit++, 0);
|
||||
assert.equal(req.url, 'http://example.com/foo');
|
||||
next();
|
||||
});
|
||||
|
||||
router.handle(request, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
assert.equal(request.hit, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore FQDN in search', function (done) {
|
||||
var request = { hit: 0, url: '/proxy?url=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, '/?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();
|
||||
|
||||
router.use('/blog', function (req, res, next) {
|
||||
assert.equal(req.hit++, 0);
|
||||
assert.equal(req.url, 'http://example.com/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 with multiple handlers', function (done) {
|
||||
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
|
||||
var router = new Router();
|
||||
|
||||
router.use(function (req, res, next) {
|
||||
assert.equal(req.hit++, 0);
|
||||
assert.equal(req.url, 'http://example.com/blog/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.use('/blog', function (req, res, next) {
|
||||
assert.equal(req.hit++, 1);
|
||||
assert.equal(req.url, 'http://example.com/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.handle(request, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
assert.equal(request.hit, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should adjust FQDN req.url with multiple routed handlers', function (done) {
|
||||
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
|
||||
var router = new Router();
|
||||
|
||||
router.use('/blog', function (req, res, next) {
|
||||
assert.equal(req.hit++, 0);
|
||||
assert.equal(req.url, 'http://example.com/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.use('/blog', function (req, res, next) {
|
||||
assert.equal(req.hit++, 1);
|
||||
assert.equal(req.url, 'http://example.com/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.use(function (req, res, next) {
|
||||
assert.equal(req.hit++, 2);
|
||||
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, 3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
describe('.all', function() {
|
||||
it('should support using .all to capture all http verbs', function(done){
|
||||
var router = new Router();
|
||||
@@ -192,6 +297,43 @@ describe('Router', function(){
|
||||
})
|
||||
})
|
||||
|
||||
describe('.use', function() {
|
||||
it('should require arguments', function(){
|
||||
var router = new Router();
|
||||
router.use.bind(router).should.throw(/requires middleware function/)
|
||||
})
|
||||
|
||||
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/)
|
||||
})
|
||||
|
||||
it('should accept array of middleware', function(done){
|
||||
var count = 0;
|
||||
var router = new Router();
|
||||
|
||||
function fn1(req, res, next){
|
||||
assert.equal(++count, 1);
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next){
|
||||
assert.equal(++count, 2);
|
||||
next();
|
||||
}
|
||||
|
||||
router.use([fn1, fn2], function(req, res){
|
||||
assert.equal(++count, 3);
|
||||
done();
|
||||
});
|
||||
|
||||
router.handle({ url: '/foo', method: 'GET' }, {}, function(){});
|
||||
})
|
||||
})
|
||||
|
||||
describe('.param', function() {
|
||||
it('should call param function when routing VERBS', function(done) {
|
||||
var router = new Router();
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('cookies', function(){
|
||||
it('should respond to cookie', function(done){
|
||||
request(app)
|
||||
.post('/')
|
||||
.type('urlencoded')
|
||||
.send({ remember: 1 })
|
||||
.expect(302, function(err, res){
|
||||
if (err) return done(err)
|
||||
@@ -37,6 +38,7 @@ describe('cookies', function(){
|
||||
it('should clear cookie', function(done){
|
||||
request(app)
|
||||
.post('/')
|
||||
.type('urlencoded')
|
||||
.send({ remember: 1 })
|
||||
.expect(302, function(err, res){
|
||||
if (err) return done(err)
|
||||
@@ -53,6 +55,7 @@ describe('cookies', function(){
|
||||
it('should set a cookie', function(done){
|
||||
request(app)
|
||||
.post('/')
|
||||
.type('urlencoded')
|
||||
.send({ remember: 1 })
|
||||
.expect(302, function(err, res){
|
||||
res.headers.should.have.property('set-cookie')
|
||||
|
||||
@@ -33,6 +33,7 @@ describe('mvc', function(){
|
||||
it('should update the pet', function(done){
|
||||
request(app)
|
||||
.put('/pet/3')
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send({ pet: { name: 'Boots' } })
|
||||
.end(function(err, res){
|
||||
if (err) return done(err);
|
||||
@@ -102,6 +103,7 @@ describe('mvc', function(){
|
||||
it('should update the user', function(done){
|
||||
request(app)
|
||||
.put('/user/1')
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send({ user: { name: 'Tobo' }})
|
||||
.end(function(err, res){
|
||||
if (err) return done(err);
|
||||
@@ -116,6 +118,7 @@ describe('mvc', function(){
|
||||
it('should create a pet for user', function(done){
|
||||
request(app)
|
||||
.post('/user/2/pet')
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send({ pet: { name: 'Snickers' }})
|
||||
.expect('Location', '/user/2')
|
||||
.expect(302, function(err, res){
|
||||
|
||||
97
test/acceptance/route-separation.js
Normal file
97
test/acceptance/route-separation.js
Normal file
@@ -0,0 +1,97 @@
|
||||
|
||||
var app = require('../../examples/route-separation')
|
||||
var request = require('supertest')
|
||||
|
||||
describe('route-separation', function () {
|
||||
describe('GET /', function () {
|
||||
it('should respond with index', function (done) {
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, /Route Separation Example/, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users', function () {
|
||||
it('should list users', function (done) {
|
||||
request(app)
|
||||
.get('/users')
|
||||
.expect(/TJ/)
|
||||
.expect(/Tobi/)
|
||||
.expect(200, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /user/:id', function () {
|
||||
it('should get a user', function (done) {
|
||||
request(app)
|
||||
.get('/user/0')
|
||||
.expect(200, /Viewing user TJ/, done)
|
||||
})
|
||||
|
||||
it('should 404 on missing user', function (done) {
|
||||
request(app)
|
||||
.get('/user/10')
|
||||
.expect(404, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /user/:id/view', function () {
|
||||
it('should get a user', function (done) {
|
||||
request(app)
|
||||
.get('/user/0/view')
|
||||
.expect(200, /Viewing user TJ/, done)
|
||||
})
|
||||
|
||||
it('should 404 on missing user', function (done) {
|
||||
request(app)
|
||||
.get('/user/10/view')
|
||||
.expect(404, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /user/:id/edit', function () {
|
||||
it('should get a user to edit', function (done) {
|
||||
request(app)
|
||||
.get('/user/0/edit')
|
||||
.expect(200, /Editing user TJ/, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /user/:id/edit', function () {
|
||||
it('should edit a user', function (done) {
|
||||
request(app)
|
||||
.put('/user/0/edit')
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send({ user: { name: 'TJ', email: 'tj-invalid@vision-media.ca' } })
|
||||
.expect(302, function (err) {
|
||||
if (err) return done(err)
|
||||
request(app)
|
||||
.get('/user/0')
|
||||
.expect(200, /tj-invalid@vision-media\.ca/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /user/:id/edit?_method=PUT', function () {
|
||||
it('should edit a user', function (done) {
|
||||
request(app)
|
||||
.post('/user/1/edit?_method=PUT')
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send({ user: { name: 'Tobi', email: 'tobi-invalid@vision-media.ca' } })
|
||||
.expect(302, function (err) {
|
||||
if (err) return done(err)
|
||||
request(app)
|
||||
.get('/user/1')
|
||||
.expect(200, /tobi-invalid@vision-media\.ca/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /posts', function () {
|
||||
it('should get a list of posts', function (done) {
|
||||
request(app)
|
||||
.get('/posts')
|
||||
.expect(200, /Posts/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -53,7 +53,7 @@ describe('web-service', function(){
|
||||
.get('/api/repos?api-key=foo')
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(/"name":"express"/)
|
||||
.expect(/"url":"http:\/\/github.com\/visionmedia\/express"/)
|
||||
.expect(/"url":"http:\/\/github.com\/strongloop\/express"/)
|
||||
.expect(200, done)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,6 +34,8 @@ describe('HEAD', function(){
|
||||
.get('/tobi')
|
||||
.expect(200, function(err, res){
|
||||
if (err) return done(err);
|
||||
delete headers.date;
|
||||
delete res.headers.date;
|
||||
assert.deepEqual(res.headers, headers);
|
||||
done();
|
||||
});
|
||||
|
||||
29
test/app.js
29
test/app.js
@@ -1,6 +1,7 @@
|
||||
|
||||
var express = require('../')
|
||||
, assert = require('assert');
|
||||
var assert = require('assert')
|
||||
var express = require('..')
|
||||
var request = require('supertest')
|
||||
|
||||
describe('app', function(){
|
||||
it('should inherit from event emitter', function(done){
|
||||
@@ -8,6 +9,17 @@ describe('app', function(){
|
||||
app.on('foo', done);
|
||||
app.emit('foo');
|
||||
})
|
||||
|
||||
it('should be callable', function(){
|
||||
var app = express();
|
||||
assert(typeof app, 'function');
|
||||
})
|
||||
|
||||
it('should 404 without routes', function(done){
|
||||
request(express())
|
||||
.get('/')
|
||||
.expect(404, done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('app.parent', function(){
|
||||
@@ -27,16 +39,19 @@ describe('app.parent', function(){
|
||||
|
||||
describe('app.mountpath', function(){
|
||||
it('should return the mounted path', function(){
|
||||
var app = express()
|
||||
, blog = express()
|
||||
, blogAdmin = express();
|
||||
var admin = express();
|
||||
var app = express();
|
||||
var blog = express();
|
||||
var fallback = express();
|
||||
|
||||
app.use('/blog', blog);
|
||||
blog.use('/admin', blogAdmin);
|
||||
app.use(fallback);
|
||||
blog.use('/admin', admin);
|
||||
|
||||
admin.mountpath.should.equal('/admin');
|
||||
app.mountpath.should.equal('/');
|
||||
blog.mountpath.should.equal('/blog');
|
||||
blogAdmin.mountpath.should.equal('/admin');
|
||||
fallback.mountpath.should.equal('/');
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -16,6 +16,24 @@ describe('OPTIONS', function(){
|
||||
.expect('Allow', 'GET,PUT', done);
|
||||
})
|
||||
|
||||
it('should not be affected by app.all', function(done){
|
||||
var app = express();
|
||||
|
||||
app.get('/', function(){});
|
||||
app.get('/users', function(req, res){});
|
||||
app.put('/users', function(req, res){});
|
||||
app.all('/users', function(req, res, next){
|
||||
res.setHeader('x-hit', '1');
|
||||
next();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.options('/users')
|
||||
.expect('x-hit', '1')
|
||||
.expect('allow', 'GET,PUT')
|
||||
.expect(200, 'GET,PUT', done);
|
||||
})
|
||||
|
||||
it('should not respond if the path is not defined', function(done){
|
||||
var app = express();
|
||||
|
||||
|
||||
@@ -157,6 +157,51 @@ describe('app', function(){
|
||||
.expect('2 2 foo,bob', done);
|
||||
})
|
||||
|
||||
it('should support altering req.params across routes', function(done) {
|
||||
var app = express();
|
||||
|
||||
app.param('user', function(req, res, next, user) {
|
||||
req.params.user = 'loki';
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/:user', function(req, res, next) {
|
||||
next('route');
|
||||
});
|
||||
app.get('/:user', function(req, res, next) {
|
||||
res.send(req.params.user);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/bob')
|
||||
.expect('loki', done);
|
||||
})
|
||||
|
||||
it('should not invoke without route handler', function(done) {
|
||||
var app = express();
|
||||
|
||||
app.param('thing', function(req, res, next, thing) {
|
||||
req.thing = thing;
|
||||
next();
|
||||
});
|
||||
|
||||
app.param('user', function(req, res, next, user) {
|
||||
next(new Error('invalid invokation'));
|
||||
});
|
||||
|
||||
app.post('/:user', function(req, res, next) {
|
||||
res.send(req.params.user);
|
||||
});
|
||||
|
||||
app.get('/:thing', function(req, res, next) {
|
||||
res.send(req.thing);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/bob')
|
||||
.expect(200, 'bob', done);
|
||||
})
|
||||
|
||||
it('should work with encoded values', function(done){
|
||||
var app = express();
|
||||
|
||||
@@ -192,6 +237,27 @@ describe('app', function(){
|
||||
.expect(500, done);
|
||||
})
|
||||
|
||||
it('should catch thrown secondary error', function(done){
|
||||
var app = express();
|
||||
|
||||
app.param('id', function(req, res, next, val){
|
||||
process.nextTick(next);
|
||||
});
|
||||
|
||||
app.param('id', function(req, res, next, id){
|
||||
throw new Error('err!');
|
||||
});
|
||||
|
||||
app.get('/user/:id', function(req, res){
|
||||
var id = req.params.id;
|
||||
res.send('' + id);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/123')
|
||||
.expect(500, done);
|
||||
})
|
||||
|
||||
it('should defer to next route', function(done){
|
||||
var app = express();
|
||||
|
||||
|
||||
@@ -1,11 +1,39 @@
|
||||
|
||||
var after = require('after');
|
||||
var express = require('../')
|
||||
, request = require('supertest')
|
||||
, assert = require('assert')
|
||||
, methods = require('methods');
|
||||
|
||||
describe('app.router', function(){
|
||||
describe('methods supported', function(){
|
||||
it('should restore req.params after leaving router', function(done){
|
||||
var app = express();
|
||||
var router = new express.Router();
|
||||
|
||||
function handler1(req, res, next){
|
||||
res.setHeader('x-user-id', req.params.id);
|
||||
next()
|
||||
}
|
||||
|
||||
function handler2(req, res){
|
||||
res.send(req.params.id);
|
||||
}
|
||||
|
||||
router.use(function(req, res, next){
|
||||
res.setHeader('x-router', req.params.id);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/:id', handler1, router, handler2);
|
||||
|
||||
request(app)
|
||||
.get('/user/1')
|
||||
.expect('x-router', 'undefined')
|
||||
.expect('x-user-id', '1')
|
||||
.expect(200, '1', done);
|
||||
})
|
||||
|
||||
describe('methods', function(){
|
||||
methods.concat('del').forEach(function(method){
|
||||
if (method === 'connect') return;
|
||||
|
||||
@@ -31,6 +59,35 @@ describe('app.router', function(){
|
||||
app[method].bind(app, '/', 3).should.throw(/Number/);
|
||||
})
|
||||
});
|
||||
|
||||
it('should re-route when method is altered', function (done) {
|
||||
var app = express();
|
||||
var cb = after(3, done);
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
if (req.method !== 'POST') return next();
|
||||
req.method = 'DELETE';
|
||||
res.setHeader('X-Method-Altered', '1');
|
||||
next();
|
||||
});
|
||||
|
||||
app.delete('/', function (req, res) {
|
||||
res.end('deleted everything');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, 'Cannot GET /\n', cb);
|
||||
|
||||
request(app)
|
||||
.delete('/')
|
||||
.expect(200, 'deleted everything', cb);
|
||||
|
||||
request(app)
|
||||
.post('/')
|
||||
.expect('X-Method-Altered', '1')
|
||||
.expect(200, 'deleted everything', cb);
|
||||
});
|
||||
})
|
||||
|
||||
describe('decode querystring', function(){
|
||||
@@ -183,6 +240,106 @@ describe('app.router', function(){
|
||||
})
|
||||
})
|
||||
|
||||
describe('params', function(){
|
||||
it('should overwrite existing req.params by default', function(done){
|
||||
var app = express();
|
||||
var router = new express.Router();
|
||||
|
||||
router.get('/:action', function(req, res){
|
||||
res.send(req.params);
|
||||
});
|
||||
|
||||
app.use('/user/:user', router);
|
||||
|
||||
request(app)
|
||||
.get('/user/1/get')
|
||||
.expect(200, '{"action":"get"}', done);
|
||||
})
|
||||
|
||||
it('should allow merging existing req.params', function(done){
|
||||
var app = express();
|
||||
var router = new express.Router({ mergeParams: true });
|
||||
|
||||
router.get('/:action', function(req, res){
|
||||
var keys = Object.keys(req.params).sort();
|
||||
res.send(keys.map(function(k){ return [k, req.params[k]] }));
|
||||
});
|
||||
|
||||
app.use('/user/:user', router);
|
||||
|
||||
request(app)
|
||||
.get('/user/tj/get')
|
||||
.expect(200, '[["action","get"],["user","tj"]]', done);
|
||||
})
|
||||
|
||||
it('should use params from router', function(done){
|
||||
var app = express();
|
||||
var router = new express.Router({ mergeParams: true });
|
||||
|
||||
router.get('/:thing', function(req, res){
|
||||
var keys = Object.keys(req.params).sort();
|
||||
res.send(keys.map(function(k){ return [k, req.params[k]] }));
|
||||
});
|
||||
|
||||
app.use('/user/:thing', router);
|
||||
|
||||
request(app)
|
||||
.get('/user/tj/get')
|
||||
.expect(200, '[["thing","get"]]', done);
|
||||
})
|
||||
|
||||
it('should merge numeric indices req.params', function(done){
|
||||
var app = express();
|
||||
var router = new express.Router({ mergeParams: true });
|
||||
|
||||
router.get('/*.*', function(req, res){
|
||||
var keys = Object.keys(req.params).sort();
|
||||
res.send(keys.map(function(k){ return [k, req.params[k]] }));
|
||||
});
|
||||
|
||||
app.use('/user/id:(\\d+)', router);
|
||||
|
||||
request(app)
|
||||
.get('/user/id:10/profile.json')
|
||||
.expect(200, '[["0","10"],["1","profile"],["2","json"]]', done);
|
||||
})
|
||||
|
||||
it('should merge numeric indices req.params when more in parent', function(done){
|
||||
var app = express();
|
||||
var router = new express.Router({ mergeParams: true });
|
||||
|
||||
router.get('/*', function(req, res){
|
||||
var keys = Object.keys(req.params).sort();
|
||||
res.send(keys.map(function(k){ return [k, req.params[k]] }));
|
||||
});
|
||||
|
||||
app.use('/user/id:(\\d+)/name:(\\w+)', router);
|
||||
|
||||
request(app)
|
||||
.get('/user/id:10/name:tj/profile')
|
||||
.expect(200, '[["0","10"],["1","tj"],["2","profile"]]', done);
|
||||
})
|
||||
|
||||
it('should ignore invalid incoming req.params', function(done){
|
||||
var app = express();
|
||||
var router = new express.Router({ mergeParams: true });
|
||||
|
||||
router.get('/:name', function(req, res){
|
||||
var keys = Object.keys(req.params).sort();
|
||||
res.send(keys.map(function(k){ return [k, req.params[k]] }));
|
||||
});
|
||||
|
||||
app.use('/user/', function (req, res, next) {
|
||||
req.params = 3; // wat?
|
||||
router(req, res, next);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/tj')
|
||||
.expect(200, '[["name","tj"]]', done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('trailing slashes', function(){
|
||||
it('should be optional by default', function(done){
|
||||
var app = express();
|
||||
@@ -211,6 +368,46 @@ describe('app.router', function(){
|
||||
.expect('tj', done);
|
||||
})
|
||||
|
||||
it('should pass-though middleware', function(done){
|
||||
var app = express();
|
||||
|
||||
app.enable('strict routing');
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
res.setHeader('x-middleware', 'true');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/', function(req, res){
|
||||
res.end('tj');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/')
|
||||
.expect('x-middleware', 'true')
|
||||
.expect(200, 'tj', done);
|
||||
})
|
||||
|
||||
it('should pass-though mounted middleware', function(done){
|
||||
var app = express();
|
||||
|
||||
app.enable('strict routing');
|
||||
|
||||
app.use('/user/', function (req, res, next) {
|
||||
res.setHeader('x-middleware', 'true');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/test/', function(req, res){
|
||||
res.end('tj');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/test/')
|
||||
.expect('x-middleware', 'true')
|
||||
.expect(200, 'tj', done);
|
||||
})
|
||||
|
||||
it('should match no slashes', function(done){
|
||||
var app = express();
|
||||
|
||||
@@ -225,6 +422,48 @@ describe('app.router', function(){
|
||||
.expect('tj', done);
|
||||
})
|
||||
|
||||
it('should match middleware when omitting the trailing slash', function(done){
|
||||
var app = express();
|
||||
|
||||
app.enable('strict routing');
|
||||
|
||||
app.use('/user/', function(req, res){
|
||||
res.end('tj');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user')
|
||||
.expect(200, 'tj', done);
|
||||
})
|
||||
|
||||
it('should match middleware', function(done){
|
||||
var app = express();
|
||||
|
||||
app.enable('strict routing');
|
||||
|
||||
app.use('/user', function(req, res){
|
||||
res.end('tj');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user')
|
||||
.expect(200, 'tj', done);
|
||||
})
|
||||
|
||||
it('should match middleware when adding the trailing slash', function(done){
|
||||
var app = express();
|
||||
|
||||
app.enable('strict routing');
|
||||
|
||||
app.use('/user', function(req, res){
|
||||
res.end('tj');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/')
|
||||
.expect(200, 'tj', done);
|
||||
})
|
||||
|
||||
it('should fail when omitting the trailing slash', function(done){
|
||||
var app = express();
|
||||
|
||||
@@ -391,7 +630,7 @@ describe('app.router', function(){
|
||||
.expect('', done);
|
||||
})
|
||||
|
||||
it('should require a preceeding /', function(done){
|
||||
it('should require a preceding /', function(done){
|
||||
var app = express();
|
||||
|
||||
app.get('/file/*', function(req, res){
|
||||
@@ -597,6 +836,32 @@ describe('app.router', function(){
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('should call handler in same route, if exists', function(done){
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
next(new Error('boom!'));
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.send('foo here');
|
||||
}
|
||||
|
||||
function fn3(err, req, res, next) {
|
||||
res.send('route go ' + err.message);
|
||||
}
|
||||
|
||||
app.get('/foo', fn1, fn2, fn3);
|
||||
|
||||
app.use(function (err, req, res, next) {
|
||||
res.end('error!');
|
||||
})
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('route go boom!', done)
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow rewriting of the url', function(done){
|
||||
|
||||
454
test/app.use.js
454
test/app.use.js
@@ -1,6 +1,7 @@
|
||||
|
||||
var express = require('../')
|
||||
, request = require('supertest');
|
||||
var after = require('after');
|
||||
var express = require('..');
|
||||
var request = require('supertest');
|
||||
|
||||
describe('app', function(){
|
||||
it('should emit "mount" when mounted', function(done){
|
||||
@@ -15,11 +16,6 @@ describe('app', function(){
|
||||
app.use(blog);
|
||||
})
|
||||
|
||||
it('should reject numbers', function(){
|
||||
var app = express();
|
||||
app.use.bind(app, 3).should.throw(/Number/);
|
||||
})
|
||||
|
||||
describe('.use(app)', function(){
|
||||
it('should mount the app', function(done){
|
||||
var blog = express()
|
||||
@@ -83,5 +79,449 @@ describe('app', function(){
|
||||
.get('/post/once-upon-a-time')
|
||||
.expect('success', done);
|
||||
})
|
||||
|
||||
it('should support mounted app anywhere', function(done){
|
||||
var cb = after(3, done);
|
||||
var blog = express()
|
||||
, other = express()
|
||||
, app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
blog.get('/', function(req, res){
|
||||
res.end('success');
|
||||
});
|
||||
|
||||
blog.once('mount', function (parent) {
|
||||
parent.should.equal(app);
|
||||
cb();
|
||||
});
|
||||
other.once('mount', function (parent) {
|
||||
parent.should.equal(app);
|
||||
cb();
|
||||
});
|
||||
|
||||
app.use('/post/:article', fn1, other, fn2, blog);
|
||||
|
||||
request(app)
|
||||
.get('/post/once-upon-a-time')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('success', cb);
|
||||
})
|
||||
})
|
||||
|
||||
describe('.use(middleware)', function(){
|
||||
it('should accept multiple arguments', function (done) {
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
app.use(fn1, fn2, function fn3(req, res) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should invoke middleware for all requests', function (done) {
|
||||
var app = express();
|
||||
var cb = after(3, done);
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.send('saw ' + req.method + ' ' + req.url);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'saw GET /', cb);
|
||||
|
||||
request(app)
|
||||
.options('/')
|
||||
.expect(200, 'saw OPTIONS /', cb);
|
||||
|
||||
request(app)
|
||||
.post('/foo')
|
||||
.expect(200, 'saw POST /foo', cb);
|
||||
})
|
||||
|
||||
it('should accept array of middleware', function (done) {
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn3(req, res, next) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.end();
|
||||
}
|
||||
|
||||
app.use([fn1, fn2, fn3]);
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should accept multiple arrays of middleware', function (done) {
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn3(req, res, next) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.end();
|
||||
}
|
||||
|
||||
app.use([fn1, fn2], [fn3]);
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should accept nested arrays of middleware', function (done) {
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn3(req, res, next) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.end();
|
||||
}
|
||||
|
||||
app.use([[fn1], fn2], [fn3]);
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('.use(path, middleware)', function(){
|
||||
it('should reject missing functions', function () {
|
||||
var app = express();
|
||||
app.use.bind(app, '/').should.throw(/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/);
|
||||
})
|
||||
|
||||
it('should strip path from req.url', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.use('/foo', function (req, res) {
|
||||
res.send('saw ' + req.method + ' ' + req.url);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/foo/bar')
|
||||
.expect(200, 'saw GET /bar', done);
|
||||
})
|
||||
|
||||
it('should accept multiple arguments', function (done) {
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
app.use('/foo', fn1, fn2, function fn3(req, res) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should invoke middleware for all requests starting with path', function (done) {
|
||||
var app = express();
|
||||
var cb = after(3, done);
|
||||
|
||||
app.use('/foo', function (req, res) {
|
||||
res.send('saw ' + req.method + ' ' + req.url);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, cb);
|
||||
|
||||
request(app)
|
||||
.post('/foo')
|
||||
.expect(200, 'saw POST /', cb);
|
||||
|
||||
request(app)
|
||||
.post('/foo/bar')
|
||||
.expect(200, 'saw POST /bar', cb);
|
||||
})
|
||||
|
||||
it('should work if path has trailing slash', function (done) {
|
||||
var app = express();
|
||||
var cb = after(3, done);
|
||||
|
||||
app.use('/foo/', function (req, res) {
|
||||
res.send('saw ' + req.method + ' ' + req.url);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, cb);
|
||||
|
||||
request(app)
|
||||
.post('/foo')
|
||||
.expect(200, 'saw POST /', cb);
|
||||
|
||||
request(app)
|
||||
.post('/foo/bar')
|
||||
.expect(200, 'saw POST /bar', cb);
|
||||
})
|
||||
|
||||
it('should accept array of middleware', function (done) {
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn3(req, res, next) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.end();
|
||||
}
|
||||
|
||||
app.use('/foo', [fn1, fn2, fn3]);
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should accept multiple arrays of middleware', function (done) {
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn3(req, res, next) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.end();
|
||||
}
|
||||
|
||||
app.use('/foo', [fn1, fn2], [fn3]);
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should accept nested arrays of middleware', function (done) {
|
||||
var app = express();
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn3(req, res, next) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.end();
|
||||
}
|
||||
|
||||
app.use('/foo', [fn1, [fn2]], [fn3]);
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should support array of paths', function (done) {
|
||||
var app = express();
|
||||
var cb = after(3, done);
|
||||
|
||||
app.use(['/foo/', '/bar'], function (req, res) {
|
||||
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, cb);
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect(200, 'saw GET / through /foo', cb);
|
||||
|
||||
request(app)
|
||||
.get('/bar')
|
||||
.expect(200, 'saw GET / through /bar', cb);
|
||||
})
|
||||
|
||||
it('should support array of paths with middleware array', function (done) {
|
||||
var app = express();
|
||||
var cb = after(2, done);
|
||||
|
||||
function fn1(req, res, next) {
|
||||
res.setHeader('x-fn-1', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn2(req, res, next) {
|
||||
res.setHeader('x-fn-2', 'hit');
|
||||
next();
|
||||
}
|
||||
|
||||
function fn3(req, res, next) {
|
||||
res.setHeader('x-fn-3', 'hit');
|
||||
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
|
||||
}
|
||||
|
||||
app.use(['/foo/', '/bar'], [[fn1], fn2], [fn3]);
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, 'saw GET / through /foo', cb);
|
||||
|
||||
request(app)
|
||||
.get('/bar')
|
||||
.expect('x-fn-1', 'hit')
|
||||
.expect('x-fn-2', 'hit')
|
||||
.expect('x-fn-3', 'hit')
|
||||
.expect(200, 'saw GET / through /bar', cb);
|
||||
})
|
||||
|
||||
it('should support regexp path', function (done) {
|
||||
var app = express();
|
||||
var cb = after(4, done);
|
||||
|
||||
app.use(/^\/[a-z]oo/, function (req, res) {
|
||||
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, cb);
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect(200, 'saw GET / through /foo', cb);
|
||||
|
||||
request(app)
|
||||
.get('/zoo/bear')
|
||||
.expect(200, 'saw GET /bear through /zoo/bear', cb);
|
||||
|
||||
request(app)
|
||||
.get('/get/zoo')
|
||||
.expect(404, cb);
|
||||
})
|
||||
|
||||
it('should support empty string path', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.use('', function (req, res) {
|
||||
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'saw GET / through /', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
1
test/fixtures/% of dogs.txt
vendored
Normal file
1
test/fixtures/% of dogs.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
20%
|
||||
1
test/fixtures/blog/index.html
vendored
Normal file
1
test/fixtures/blog/index.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<b>index</b>
|
||||
@@ -9,7 +9,7 @@ describe('req', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res, next){
|
||||
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
|
||||
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
|
||||
});
|
||||
|
||||
request(app)
|
||||
@@ -23,7 +23,7 @@ describe('req', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res, next){
|
||||
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
|
||||
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
|
||||
});
|
||||
|
||||
request(app)
|
||||
@@ -36,7 +36,7 @@ describe('req', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res, next){
|
||||
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
|
||||
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
|
||||
});
|
||||
|
||||
request(app)
|
||||
|
||||
49
test/req.acceptsCharsets.js
Normal file
49
test/req.acceptsCharsets.js
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
var express = require('../')
|
||||
, request = require('supertest');
|
||||
|
||||
describe('req', function(){
|
||||
describe('.acceptsCharsets(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.acceptsCharsets('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.acceptsCharsets('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.acceptsCharsets('utf-8') ? 'yes' : 'no');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept-Charset', 'foo, bar')
|
||||
.expect('no', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,7 @@ var express = require('../')
|
||||
, request = require('supertest');
|
||||
|
||||
describe('req', function(){
|
||||
describe('.acceptsEncodings', function(){
|
||||
describe('.acceptsEncoding', function(){
|
||||
it('should be true if encoding accpeted', function(done){
|
||||
var app = express();
|
||||
|
||||
|
||||
36
test/req.acceptsEncodings.js
Normal file
36
test/req.acceptsEncodings.js
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
var express = require('../')
|
||||
, request = require('supertest');
|
||||
|
||||
describe('req', function(){
|
||||
describe('.acceptsEncodingss', function(){
|
||||
it('should be true if encoding accpeted', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.acceptsEncodings('gzip').should.be.ok;
|
||||
req.acceptsEncodings('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.acceptsEncodings('bogus').should.not.be.ok;
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept-Encoding', ' gzip, deflate')
|
||||
.expect(200, done);
|
||||
})
|
||||
})
|
||||
})
|
||||
53
test/req.acceptsLanguages.js
Normal file
53
test/req.acceptsLanguages.js
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
var express = require('../')
|
||||
, request = require('supertest');
|
||||
|
||||
describe('req', function(){
|
||||
describe('.acceptsLanguages', function(){
|
||||
it('should be true if language accpeted', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.acceptsLanguages('en-us').should.be.ok;
|
||||
req.acceptsLanguages('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.acceptsLanguages('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.acceptsLanguages('en').should.be.ok;
|
||||
req.acceptsLanguages('es').should.be.ok;
|
||||
req.acceptsLanguages('jp').should.be.ok;
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, done);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
138
test/req.hostname.js
Normal file
138
test/req.hostname.js
Normal file
@@ -0,0 +1,138 @@
|
||||
|
||||
var express = require('../')
|
||||
, request = require('supertest')
|
||||
, assert = require('assert');
|
||||
|
||||
describe('req', function(){
|
||||
describe('.hostname', function(){
|
||||
it('should return the Host when present', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.hostname);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.post('/')
|
||||
.set('Host', 'example.com')
|
||||
.expect('example.com', done);
|
||||
})
|
||||
|
||||
it('should strip port number', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.hostname);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.post('/')
|
||||
.set('Host', 'example.com:3000')
|
||||
.expect('example.com', done);
|
||||
})
|
||||
|
||||
it('should return undefined otherwise', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.headers.host = null;
|
||||
res.end(String(req.hostname));
|
||||
});
|
||||
|
||||
request(app)
|
||||
.post('/')
|
||||
.expect('undefined', done);
|
||||
})
|
||||
|
||||
it('should work with IPv6 Host', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.hostname);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.post('/')
|
||||
.set('Host', '[::1]')
|
||||
.expect('[::1]', done);
|
||||
})
|
||||
|
||||
it('should work with IPv6 Host and port', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.hostname);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.post('/')
|
||||
.set('Host', '[::1]:3000')
|
||||
.expect('[::1]', done);
|
||||
})
|
||||
|
||||
describe('when "trust proxy" is enabled', function(){
|
||||
it('should respect X-Forwarded-Host', function(done){
|
||||
var app = express();
|
||||
|
||||
app.enable('trust proxy');
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.hostname);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'localhost')
|
||||
.set('X-Forwarded-Host', 'example.com:3000')
|
||||
.expect('example.com', done);
|
||||
})
|
||||
|
||||
it('should ignore X-Forwarded-Host if socket addr not trusted', function(done){
|
||||
var app = express();
|
||||
|
||||
app.set('trust proxy', '10.0.0.1');
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.hostname);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'localhost')
|
||||
.set('X-Forwarded-Host', 'example.com')
|
||||
.expect('localhost', done);
|
||||
})
|
||||
|
||||
it('should default to Host', function(done){
|
||||
var app = express();
|
||||
|
||||
app.enable('trust proxy');
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.hostname);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'example.com')
|
||||
.expect('example.com', done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('when "trust proxy" is disabled', function(){
|
||||
it('should ignore X-Forwarded-Host', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.hostname);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'localhost')
|
||||
.set('X-Forwarded-Host', 'evil')
|
||||
.expect('localhost', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -45,10 +45,9 @@ describe('req', function(){
|
||||
res.send(req.ip);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('X-Forwarded-For', 'client, p1, p2')
|
||||
.expect('127.0.0.1', done);
|
||||
var test = request(app).get('/')
|
||||
test.set('X-Forwarded-For', 'client, p1, p2')
|
||||
test.expect(200, getExpectedClientAddress(test._server), done);
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -63,10 +62,19 @@ describe('req', function(){
|
||||
res.send(req.ip);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('127.0.0.1', done);
|
||||
var test = request(app).get('/')
|
||||
test.expect(200, getExpectedClientAddress(test._server), done);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Get the local client address depending on AF_NET of server
|
||||
*/
|
||||
|
||||
function getExpectedClientAddress(server) {
|
||||
return server.address().address === '::'
|
||||
? '::ffff:127.0.0.1'
|
||||
: '127.0.0.1';
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('req', function(){
|
||||
it('should check req.body', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(bodyParser());
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(function(req, res){
|
||||
res.end(req.param('name'));
|
||||
|
||||
@@ -32,6 +32,21 @@ describe('req', function(){
|
||||
.expect('https', done);
|
||||
})
|
||||
|
||||
it('should default to the socket addr if X-Forwarded-Proto not present', function(done){
|
||||
var app = express();
|
||||
|
||||
app.enable('trust proxy');
|
||||
|
||||
app.use(function(req, res){
|
||||
req.connection.encrypted = true;
|
||||
res.end(req.protocol);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('https', done);
|
||||
})
|
||||
|
||||
it('should ignore X-Forwarded-Proto if socket addr not trusted', function(done){
|
||||
var app = express();
|
||||
|
||||
|
||||
@@ -5,33 +5,91 @@ var express = require('../')
|
||||
describe('req', function(){
|
||||
describe('.query', function(){
|
||||
it('should default to {}', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.query.should.eql({});
|
||||
res.end();
|
||||
});
|
||||
var app = createApp();
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.end(function(res){
|
||||
done();
|
||||
});
|
||||
})
|
||||
.expect(200, '{}', done);
|
||||
});
|
||||
|
||||
it('should contain the parsed query-string', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.query.should.eql({ user: { name: 'tj' }});
|
||||
res.end();
|
||||
});
|
||||
it('should default to parse complex keys', function (done) {
|
||||
var app = createApp();
|
||||
|
||||
request(app)
|
||||
.get('/?user[name]=tj')
|
||||
.end(function(res){
|
||||
done();
|
||||
.expect(200, '{"user":{"name":"tj"}}', done);
|
||||
});
|
||||
|
||||
describe('when "query parser" is extended', function () {
|
||||
it('should parse complex keys', function (done) {
|
||||
var app = createApp('extended');
|
||||
|
||||
request(app)
|
||||
.get('/?user[name]=tj')
|
||||
.expect(200, '{"user":{"name":"tj"}}', done);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('when "query parser" is simple', function () {
|
||||
it('should not parse complex keys', function (done) {
|
||||
var app = createApp('simple');
|
||||
|
||||
request(app)
|
||||
.get('/?user[name]=tj')
|
||||
.expect(200, '{"user[name]":"tj"}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser" is a function', function () {
|
||||
it('should parse using function', function (done) {
|
||||
var app = createApp(function (str) {
|
||||
return {'length': (str || '').length};
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/?user[name]=tj')
|
||||
.expect(200, '{"length":17}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser" disabled', function () {
|
||||
it('should not parse query', function (done) {
|
||||
var app = createApp(false);
|
||||
|
||||
request(app)
|
||||
.get('/?user[name]=tj')
|
||||
.expect(200, '{}', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "query parser" disabled', function () {
|
||||
it('should not parse complex keys', function (done) {
|
||||
var app = createApp(true);
|
||||
|
||||
request(app)
|
||||
.get('/?user[name]=tj')
|
||||
.expect(200, '{"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/);
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
function createApp(setting) {
|
||||
var app = express();
|
||||
|
||||
if (setting !== undefined) {
|
||||
app.set('query parser', setting);
|
||||
}
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.send(req.query);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,33 @@ describe('req', function(){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'tobi.ferrets.example.com')
|
||||
.expect(["ferrets","tobi"], done);
|
||||
.expect(200, ['ferrets', 'tobi'], done);
|
||||
})
|
||||
|
||||
it('should work with IPv4 address', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.send(req.subdomains);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', '127.0.0.1')
|
||||
.expect(200, [], done);
|
||||
})
|
||||
|
||||
it('should work with IPv6 address', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.send(req.subdomains);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', '[::1]')
|
||||
.expect(200, [], done);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,7 +56,7 @@ describe('req', function(){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'example.com')
|
||||
.expect([], done);
|
||||
.expect(200, [], done);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -45,7 +71,23 @@ describe('req', function(){
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect([], done);
|
||||
.expect(200, [], done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('with trusted X-Forwarded-Host', function () {
|
||||
it('should return an array', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.set('trust proxy', true);
|
||||
app.use(function (req, res) {
|
||||
res.send(req.subdomains);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('X-Forwarded-Host', 'tobi.ferrets.example.com')
|
||||
.expect(200, ['ferrets', 'tobi'], done);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -62,7 +104,35 @@ describe('req', function(){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'tobi.ferrets.sub.example.com')
|
||||
.expect(["com","example","sub","ferrets","tobi"], done);
|
||||
.expect(200, ['com', 'example', 'sub', 'ferrets', 'tobi'], done);
|
||||
})
|
||||
|
||||
it('should return an array with the whole IPv4', function (done) {
|
||||
var app = express();
|
||||
app.set('subdomain offset', 0);
|
||||
|
||||
app.use(function(req, res){
|
||||
res.send(req.subdomains);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', '127.0.0.1')
|
||||
.expect(200, ['127.0.0.1'], done);
|
||||
})
|
||||
|
||||
it('should return an array with the whole IPv6', function (done) {
|
||||
var app = express();
|
||||
app.set('subdomain offset', 0);
|
||||
|
||||
app.use(function(req, res){
|
||||
res.send(req.subdomains);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', '[::1]')
|
||||
.expect(200, ['[::1]'], done);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -78,7 +148,7 @@ describe('req', function(){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'tobi.ferrets.sub.example.com')
|
||||
.expect(["ferrets","tobi"], done);
|
||||
.expect(200, ['ferrets', 'tobi'], done);
|
||||
})
|
||||
})
|
||||
|
||||
@@ -94,7 +164,7 @@ describe('req', function(){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'sub.example.com')
|
||||
.expect([], done);
|
||||
.expect(200, [], done);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('res', function(){
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Disposition', 'attachment;' +
|
||||
' filename=%E6%97%A5%E6%9C%AC%E8%AA%9E.txt;' +
|
||||
' 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);
|
||||
})
|
||||
|
||||
@@ -14,11 +14,8 @@ describe('res', function(){
|
||||
|
||||
request(app)
|
||||
.get('/?callback=something')
|
||||
.end(function(err, res){
|
||||
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
|
||||
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
|
||||
done();
|
||||
})
|
||||
.expect('Content-Type', 'text/javascript; charset=utf-8')
|
||||
.expect(200, /something\(\{"count":1\}\);/, done);
|
||||
})
|
||||
|
||||
it('should use first callback parameter with jsonp', function(done){
|
||||
@@ -29,12 +26,9 @@ describe('res', function(){
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/?callback=something&callback=somethingelse')
|
||||
.end(function(err, res){
|
||||
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
|
||||
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
|
||||
done();
|
||||
})
|
||||
.get('/?callback=something&callback=somethingelse')
|
||||
.expect('Content-Type', 'text/javascript; charset=utf-8')
|
||||
.expect(200, /something\(\{"count":1\}\);/, done);
|
||||
})
|
||||
|
||||
it('should ignore object callback parameter with jsonp', function(done){
|
||||
@@ -61,11 +55,8 @@ describe('res', function(){
|
||||
|
||||
request(app)
|
||||
.get('/?clb=something')
|
||||
.end(function(err, res){
|
||||
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
|
||||
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
|
||||
done();
|
||||
})
|
||||
.expect('Content-Type', 'text/javascript; charset=utf-8')
|
||||
.expect(200, /something\(\{"count":1\}\);/, done);
|
||||
})
|
||||
|
||||
it('should allow []', function(done){
|
||||
@@ -77,11 +68,8 @@ describe('res', function(){
|
||||
|
||||
request(app)
|
||||
.get('/?callback=callbacks[123]')
|
||||
.end(function(err, res){
|
||||
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
|
||||
res.text.should.equal('typeof callbacks[123] === \'function\' && callbacks[123]({"count":1});');
|
||||
done();
|
||||
})
|
||||
.expect('Content-Type', 'text/javascript; charset=utf-8')
|
||||
.expect(200, /callbacks\[123\]\(\{"count":1\}\);/, done);
|
||||
})
|
||||
|
||||
it('should disallow arbitrary js', function(done){
|
||||
@@ -93,11 +81,8 @@ describe('res', function(){
|
||||
|
||||
request(app)
|
||||
.get('/?callback=foo;bar()')
|
||||
.end(function(err, res){
|
||||
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
|
||||
res.text.should.equal('typeof foobar === \'function\' && foobar({});');
|
||||
done();
|
||||
})
|
||||
.expect('Content-Type', 'text/javascript; charset=utf-8')
|
||||
.expect(200, /foobar\(\{\}\);/, done);
|
||||
})
|
||||
|
||||
it('should escape utf whitespace', function(done){
|
||||
@@ -109,13 +94,37 @@ describe('res', function(){
|
||||
|
||||
request(app)
|
||||
.get('/?callback=foo')
|
||||
.end(function(err, res){
|
||||
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
|
||||
res.text.should.equal('typeof foo === \'function\' && foo({"str":"\\u2028 \\u2029 woot"});');
|
||||
done();
|
||||
});
|
||||
.expect('Content-Type', 'text/javascript; charset=utf-8')
|
||||
.expect(200, /foo\(\{"str":"\\u2028 \\u2029 woot"\}\);/, done);
|
||||
});
|
||||
|
||||
it('should not escape utf whitespace for json fallback', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.jsonp({ str: '\u2028 \u2029 woot' });
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(200, '{"str":"\u2028 \u2029 woot"}', done);
|
||||
});
|
||||
|
||||
it('should include security header and prologue', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.jsonp({ count: 1 });
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/?callback=something')
|
||||
.expect('Content-Type', 'text/javascript; charset=utf-8')
|
||||
.expect('X-Content-Type-Options', 'nosniff')
|
||||
.expect(200, /^\/\*\*\//, done);
|
||||
})
|
||||
|
||||
it('should not override previous Content-Types with no callback', function(done){
|
||||
var app = express();
|
||||
|
||||
@@ -127,7 +136,11 @@ describe('res', function(){
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/vnd.example+json; charset=utf-8')
|
||||
.expect(200, '{"hello":"world"}', done);
|
||||
.expect(200, '{"hello":"world"}', function (err, res) {
|
||||
if (err) return done(err);
|
||||
res.headers.should.not.have.property('x-content-type-options');
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('should override previous Content-Types with callback', function(done){
|
||||
@@ -141,6 +154,7 @@ describe('res', function(){
|
||||
request(app)
|
||||
.get('/?callback=cb')
|
||||
.expect('Content-Type', 'text/javascript; charset=utf-8')
|
||||
.expect('X-Content-Type-Options', 'nosniff')
|
||||
.expect(200, /cb\(\{"hello":"world"\}\);$/, done);
|
||||
})
|
||||
|
||||
|
||||
@@ -81,6 +81,21 @@ describe('res', function(){
|
||||
})
|
||||
})
|
||||
|
||||
describe('.send(code, number)', function(){
|
||||
it('should send number as json', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.send(200, 0.123);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(200, '0.123', done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('.send(String)', function(){
|
||||
it('should send as html', function(done){
|
||||
var app = express();
|
||||
@@ -103,13 +118,13 @@ describe('res', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
var str = Array(1024 * 2).join('-');
|
||||
var str = Array(1000).join('-');
|
||||
res.send(str);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('ETag', 'W/"7ff-2796319984"')
|
||||
.expect('ETag', 'W/"3e7-8084ccd1"')
|
||||
.end(done);
|
||||
})
|
||||
|
||||
@@ -117,7 +132,7 @@ describe('res', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
var str = Array(1024 * 2).join('-');
|
||||
var str = Array(1000).join('-');
|
||||
res.send(str);
|
||||
});
|
||||
|
||||
@@ -192,13 +207,13 @@ describe('res', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
var str = Array(1024 * 2).join('-');
|
||||
var str = Array(1000).join('-');
|
||||
res.send(new Buffer(str));
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('ETag', 'W/"7ff-2796319984"')
|
||||
.expect('ETag', 'W/"3e7-8084ccd1"')
|
||||
.end(done);
|
||||
})
|
||||
|
||||
@@ -306,15 +321,17 @@ describe('res', function(){
|
||||
|
||||
it('should respond with 304 Not Modified when fresh', function(done){
|
||||
var app = express();
|
||||
var etag = '"asdf"';
|
||||
|
||||
app.use(function(req, res){
|
||||
var str = Array(1024 * 2).join('-');
|
||||
var str = Array(1000).join('-');
|
||||
res.set('ETag', etag);
|
||||
res.send(str);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('If-None-Match', 'W/"7ff-2796319984"')
|
||||
.set('If-None-Match', etag)
|
||||
.expect(304, done);
|
||||
})
|
||||
|
||||
@@ -360,7 +377,7 @@ describe('res', function(){
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('etag', 'W/"c-1525560792"', done)
|
||||
.expect('etag', 'W/"c-5aee35d8"', done)
|
||||
})
|
||||
|
||||
it('should send ETag for empty string response', function(done){
|
||||
@@ -381,7 +398,7 @@ describe('res', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
var str = Array(1024 * 2).join('-');
|
||||
var str = Array(1000).join('-');
|
||||
res.send(str);
|
||||
});
|
||||
|
||||
@@ -389,7 +406,7 @@ describe('res', function(){
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('etag', 'W/"7ff-2796319984"', done)
|
||||
.expect('etag', 'W/"3e7-8084ccd1"', done)
|
||||
});
|
||||
|
||||
it('should not override ETag when manually set', function(done){
|
||||
@@ -430,7 +447,7 @@ describe('res', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
var str = Array(1024 * 2).join('-');
|
||||
var str = Array(1000).join('-');
|
||||
res.send(str);
|
||||
});
|
||||
|
||||
@@ -488,7 +505,7 @@ describe('res', function(){
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('etag', 'W/"d-1486392595"', done)
|
||||
.expect('etag', 'W/"d-58988d13"', done)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -497,8 +514,10 @@ describe('res', function(){
|
||||
var app = express()
|
||||
|
||||
app.set('etag', function(body, encoding){
|
||||
body.should.equal('hello, world!')
|
||||
encoding.should.equal('utf8')
|
||||
var chunk = !Buffer.isBuffer(body)
|
||||
? new Buffer(body, encoding)
|
||||
: body;
|
||||
chunk.toString().should.equal('hello, world!')
|
||||
return '"custom"'
|
||||
});
|
||||
|
||||
|
||||
597
test/res.sendFile.js
Normal file
597
test/res.sendFile.js
Normal file
@@ -0,0 +1,597 @@
|
||||
|
||||
var after = require('after');
|
||||
var express = require('../')
|
||||
, request = require('supertest')
|
||||
, assert = require('assert');
|
||||
var onFinished = require('on-finished');
|
||||
var path = require('path');
|
||||
var should = require('should');
|
||||
var fixtures = path.join(__dirname, 'fixtures');
|
||||
|
||||
describe('res', function(){
|
||||
describe('.sendFile(path)', function () {
|
||||
it('should error missing path', function (done) {
|
||||
var app = createApp();
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(500, /path.*required/, done);
|
||||
});
|
||||
|
||||
it('should transfer a file', function (done) {
|
||||
var app = createApp(path.resolve(fixtures, 'name.txt'));
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'tobi', done);
|
||||
});
|
||||
|
||||
it('should transfer a file with special characters in string', function (done) {
|
||||
var app = createApp(path.resolve(fixtures, '% of dogs.txt'));
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, '20%', done);
|
||||
});
|
||||
|
||||
it('should 404 for directory', function (done) {
|
||||
var app = createApp(path.resolve(fixtures, 'blog'));
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, done);
|
||||
});
|
||||
|
||||
it('should 404 when not found', function (done) {
|
||||
var app = createApp(path.resolve(fixtures, 'does-no-exist'));
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.statusCode = 200;
|
||||
res.send('no!');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, done);
|
||||
});
|
||||
|
||||
it('should not override manual content-types', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.contentType('application/x-bogus');
|
||||
res.sendFile(path.resolve(fixtures, 'name.txt'));
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Content-Type', 'application/x-bogus')
|
||||
.end(done);
|
||||
})
|
||||
|
||||
describe('with "dotfiles" option', function () {
|
||||
it('should not serve dotfiles by default', function (done) {
|
||||
var app = createApp(path.resolve(__dirname, 'fixtures/.name'));
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, done);
|
||||
});
|
||||
|
||||
it('should accept dotfiles option', function(done){
|
||||
var app = createApp(path.resolve(__dirname, 'fixtures/.name'), { dotfiles: 'allow' });
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'tobi', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with "headers" option', function () {
|
||||
it('should accept headers option', function (done) {
|
||||
var headers = {
|
||||
'x-success': 'sent',
|
||||
'x-other': 'done'
|
||||
};
|
||||
var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), { headers: headers });
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('x-success', 'sent')
|
||||
.expect('x-other', 'done')
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('should ignore headers option on 404', function (done) {
|
||||
var headers = { 'x-success': 'sent' };
|
||||
var app = createApp(path.resolve(__dirname, 'fixtures/does-not-exist'), { headers: headers });
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, function (err, res) {
|
||||
if (err) return done(err);
|
||||
res.headers.should.not.have.property('x-success');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with "root" option', function () {
|
||||
it('should not transfer relative with without', function (done) {
|
||||
var app = createApp('test/fixtures/name.txt');
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(500, /must be absolute/, done);
|
||||
})
|
||||
|
||||
it('should serve relative to "root"', function (done) {
|
||||
var app = createApp('name.txt', {root: fixtures});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'tobi', done);
|
||||
})
|
||||
|
||||
it('should disallow requesting out of "root"', function (done) {
|
||||
var app = createApp('foo/../../user.html', {root: fixtures});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(403, done);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.sendFile(path, fn)', function () {
|
||||
it('should invoke the callback when complete', function (done) {
|
||||
var cb = after(2, done);
|
||||
var app = createApp(path.resolve(fixtures, 'name.txt'), cb);
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, cb);
|
||||
})
|
||||
|
||||
it('should invoke the callback when 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'), function (err) {
|
||||
should(err).be.ok;
|
||||
err.code.should.equal('ECONNABORT');
|
||||
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(path.resolve(fixtures, 'name.txt'), function (err) {
|
||||
should(err).be.ok;
|
||||
err.code.should.equal('ECONNABORT');
|
||||
cb();
|
||||
});
|
||||
});
|
||||
test.abort();
|
||||
});
|
||||
|
||||
var test = request(app).get('/');
|
||||
test.expect(200, cb);
|
||||
})
|
||||
|
||||
it('should invoke the callback on 404', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {
|
||||
should(err).be.ok;
|
||||
err.status.should.equal(404);
|
||||
res.send('got it');
|
||||
});
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'got it', done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('.sendfile(path, fn)', function(){
|
||||
it('should invoke the callback when complete', function(done){
|
||||
var app = express();
|
||||
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('ECONNABORT');
|
||||
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('ECONNABORT');
|
||||
cb();
|
||||
});
|
||||
});
|
||||
test.abort();
|
||||
});
|
||||
|
||||
var test = request(app).get('/');
|
||||
test.expect(200, cb);
|
||||
})
|
||||
|
||||
it('should invoke the callback on 404', function(done){
|
||||
var app = express()
|
||||
, calls = 0;
|
||||
|
||||
app.use(function(req, res){
|
||||
res.sendfile('test/fixtures/nope.html', function(err){
|
||||
++calls;
|
||||
assert(!res.headersSent);
|
||||
res.send(err.message);
|
||||
});
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.end(function(err, res){
|
||||
assert(1 == calls, 'called too many times');
|
||||
res.text.should.startWith("ENOENT, stat");
|
||||
res.statusCode.should.equal(200);
|
||||
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(404, function (err, res) {
|
||||
if (err) return done(err);
|
||||
res.headers.should.not.have.property('x-success');
|
||||
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, '<b>index</b>', 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);
|
||||
});
|
||||
|
||||
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('/')
|
||||
.end(function(err, res){
|
||||
res.text.should.equal('<p>{{user.name}}</p>');
|
||||
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
|
||||
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('/')
|
||||
.end(function(err, res){
|
||||
res.text.should.equal('<p>{{user.name}}</p>');
|
||||
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
|
||||
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('/')
|
||||
.end(function(err, res){
|
||||
res.text.should.equal('<p>{{user.name}}</p>');
|
||||
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
|
||||
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);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function createApp(path, options, fn) {
|
||||
var app = express();
|
||||
|
||||
app.use(function (req, res) {
|
||||
res.sendFile(path, options, fn);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
32
test/res.sendStatus.js
Normal file
32
test/res.sendStatus.js
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
var assert = require('assert')
|
||||
var express = require('..')
|
||||
var request = require('supertest')
|
||||
|
||||
describe('res', function () {
|
||||
describe('.sendStatus(statusCode)', function () {
|
||||
it('should send the status code and message as body', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.sendStatus(201);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(201, 'Created', done);
|
||||
})
|
||||
|
||||
it('should work with unknown code', function (done) {
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.sendStatus(599);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(599, '599', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,262 +0,0 @@
|
||||
|
||||
var express = require('../')
|
||||
, request = require('supertest')
|
||||
, assert = require('assert');
|
||||
|
||||
describe('res', function(){
|
||||
describe('.sendfile(path, fn)', function(){
|
||||
it('should invoke the callback when complete', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.sendfile('test/fixtures/user.html', done)
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.end(function(){});
|
||||
})
|
||||
|
||||
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 on 404', function(done){
|
||||
var app = express()
|
||||
, calls = 0;
|
||||
|
||||
app.use(function(req, res){
|
||||
res.sendfile('test/fixtures/nope.html', function(err){
|
||||
++calls;
|
||||
assert(!res.headersSent);
|
||||
res.send(err.message);
|
||||
});
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.end(function(err, res){
|
||||
assert(1 == calls, 'called too many times');
|
||||
res.text.should.startWith("ENOENT, stat");
|
||||
res.statusCode.should.equal(200);
|
||||
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 hidden files', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.sendfile('test/fixtures/.name');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(404, done);
|
||||
})
|
||||
|
||||
it('should accept hidden option', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.sendfile('test/fixtures/.name', { hidden: true });
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, 'tobi', done);
|
||||
})
|
||||
|
||||
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('/')
|
||||
.end(function(err, res){
|
||||
res.text.should.equal('<p>{{user.name}}</p>');
|
||||
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
|
||||
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('/')
|
||||
.end(function(err, res){
|
||||
res.text.should.equal('<p>{{user.name}}</p>');
|
||||
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
|
||||
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('/')
|
||||
.end(function(err, res){
|
||||
res.text.should.equal('<p>{{user.name}}</p>');
|
||||
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
|
||||
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);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,2 +1,3 @@
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.NO_DEPRECATION = 'express';
|
||||
|
||||
@@ -2,28 +2,6 @@
|
||||
var utils = require('../lib/utils')
|
||||
, assert = require('assert');
|
||||
|
||||
describe('utils.deprecate(fn, msg)', function(){
|
||||
var env
|
||||
before(function(){
|
||||
env = process.env.NODE_ENV
|
||||
})
|
||||
after(function(){
|
||||
process.env.NODE_ENV = env
|
||||
})
|
||||
|
||||
it('should pass-through fn in test environment', function(){
|
||||
var fn = function(){}
|
||||
process.env.NODE_ENV = 'test'
|
||||
utils.deprecate(fn).should.equal(fn)
|
||||
})
|
||||
|
||||
it('should return new fn in other environment', function(){
|
||||
var fn = function(){}
|
||||
process.env.NODE_ENV = ''
|
||||
utils.deprecate(fn).should.not.equal(fn)
|
||||
})
|
||||
})
|
||||
|
||||
describe('utils.etag(body, encoding)', function(){
|
||||
it('should support strings', function(){
|
||||
utils.etag('express!')
|
||||
@@ -50,18 +28,18 @@ describe('utils.etag(body, encoding)', function(){
|
||||
describe('utils.wetag(body, encoding)', function(){
|
||||
it('should support strings', function(){
|
||||
utils.wetag('express!')
|
||||
.should.eql('W/"8-3098196679"')
|
||||
.should.eql('W/"8-b8aabac7"')
|
||||
})
|
||||
|
||||
it('should support utf8 strings', function(){
|
||||
utils.wetag('express❤', 'utf8')
|
||||
.should.eql('W/"a-1751845617"')
|
||||
.should.eql('W/"a-686b0af1"')
|
||||
})
|
||||
|
||||
it('should support buffer', function(){
|
||||
var buf = new Buffer('express!')
|
||||
utils.wetag(buf)
|
||||
.should.eql('W/"8-3098196679"');
|
||||
.should.eql('W/"8-b8aabac7"');
|
||||
})
|
||||
|
||||
it('should support empty string', function(){
|
||||
|
||||
Reference in New Issue
Block a user