mirror of
https://github.com/expressjs/express.git
synced 2026-02-26 08:45:36 +00:00
Compare commits
478 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bb013ec95 | ||
|
|
6c0031bfd8 | ||
|
|
ab6c189504 | ||
|
|
a12ae729bd | ||
|
|
d53a0cd91e | ||
|
|
eabd4564aa | ||
|
|
d40dc651f3 | ||
|
|
68290ee87a | ||
|
|
6614352563 | ||
|
|
0e5f2f84ea | ||
|
|
b1d0c19ca1 | ||
|
|
dfa7ee4732 | ||
|
|
e9539fc780 | ||
|
|
5f7a37ee51 | ||
|
|
ff3a368b2f | ||
|
|
ccc45a74f8 | ||
|
|
cd9d2ec6a9 | ||
|
|
ce7bbae007 | ||
|
|
6a5dd52deb | ||
|
|
6c2f7fb48d | ||
|
|
4dd970578a | ||
|
|
88dfd36eaa | ||
|
|
5759b3e9f5 | ||
|
|
c939a771c0 | ||
|
|
af1043844f | ||
|
|
dd763ec5b8 | ||
|
|
9c2c21aaaf | ||
|
|
366000184f | ||
|
|
4d1ee23f84 | ||
|
|
6f31218ecc | ||
|
|
6cd4859035 | ||
|
|
4f1cd4f73c | ||
|
|
f15bba7309 | ||
|
|
6f0302fb78 | ||
|
|
0e5613363f | ||
|
|
7a7f18c20b | ||
|
|
b766aad112 | ||
|
|
7488e27609 | ||
|
|
bc9d854763 | ||
|
|
2e20a85810 | ||
|
|
a706408208 | ||
|
|
6f91416020 | ||
|
|
2c5ed88c90 | ||
|
|
6d39d0f8a8 | ||
|
|
159ea67713 | ||
|
|
33959ed350 | ||
|
|
be478d348c | ||
|
|
b0e4e641f9 | ||
|
|
94f10c26cb | ||
|
|
efd2dfb8c8 | ||
|
|
3f2454e3df | ||
|
|
61da3c7d0e | ||
|
|
2c140961ab | ||
|
|
6aa4a450ed | ||
|
|
9f292d873e | ||
|
|
ef3e95ca73 | ||
|
|
f45bd632df | ||
|
|
cc18da5cdf | ||
|
|
5603f86edd | ||
|
|
daadf6033b | ||
|
|
590c919204 | ||
|
|
3bcba79e2d | ||
|
|
b8c8ecebb7 | ||
|
|
43e2cd79cb | ||
|
|
653270bb43 | ||
|
|
734bdf5ca1 | ||
|
|
341c1919d9 | ||
|
|
8e46af1b1d | ||
|
|
e4fc09423e | ||
|
|
0300b61fdd | ||
|
|
b09afad7b1 | ||
|
|
0e0b259556 | ||
|
|
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 | ||
|
|
7daae1912b | ||
|
|
3205f68510 | ||
|
|
898dcfac8b | ||
|
|
b1efa19f97 | ||
|
|
b45fd70f99 | ||
|
|
7f6c7a19c6 | ||
|
|
142462d539 | ||
|
|
f881784e9b | ||
|
|
5af625903f | ||
|
|
dc94f305cc | ||
|
|
8060a49c6c | ||
|
|
4d3e0d88a2 | ||
|
|
253ce4837a | ||
|
|
ad05eb8222 | ||
|
|
21393c244c | ||
|
|
4279e6ef45 | ||
|
|
3db6dd752f | ||
|
|
fcbe68eeb5 | ||
|
|
5019f38e29 | ||
|
|
9bf1247716 | ||
|
|
2fd31f6ea6 | ||
|
|
9cf7bba8f0 | ||
|
|
2e257d1cf7 | ||
|
|
980a15d847 | ||
|
|
56831d7799 | ||
|
|
6d65ae5ba6 | ||
|
|
c919b4a573 | ||
|
|
fe6f392c2d | ||
|
|
bffb71d4c8 | ||
|
|
3b34a537ee | ||
|
|
ad79ce9c4b | ||
|
|
721f6388c3 | ||
|
|
402ec83157 | ||
|
|
298ac11018 | ||
|
|
bb6e207336 | ||
|
|
f433b7c7cf | ||
|
|
a94278abd1 | ||
|
|
f9ec70edd0 | ||
|
|
9e5a758e7c | ||
|
|
492e933796 | ||
|
|
8ccd9d0eb5 | ||
|
|
16fdc11ccb | ||
|
|
a7cd5a2553 | ||
|
|
9e6b881f85 | ||
|
|
95fa49147b | ||
|
|
f665c57c5c | ||
|
|
9024d24e81 | ||
|
|
f92a7ad0a3 | ||
|
|
db4448dda8 | ||
|
|
0dc5836d5e | ||
|
|
8751d7ecf8 | ||
|
|
c21226aa7c | ||
|
|
3e358458f4 | ||
|
|
766b3aecf7 | ||
|
|
8ab96ab80d | ||
|
|
1f2e00ef8d | ||
|
|
b49453cf0d | ||
|
|
faffcb889c | ||
|
|
311e83e591 | ||
|
|
3c0ec59432 | ||
|
|
d4a2843500 | ||
|
|
fb2d918056 | ||
|
|
e7ad49bbbe | ||
|
|
ad9a414fae | ||
|
|
c18c2a8e68 | ||
|
|
1d54868c12 | ||
|
|
dfefea5e9d | ||
|
|
3fbab91231 | ||
|
|
f7e73e2da0 | ||
|
|
867728b5ab | ||
|
|
87e02c30e7 | ||
|
|
c3470c9c96 | ||
|
|
7f049164b7 | ||
|
|
4e12a72873 | ||
|
|
91e0c27252 | ||
|
|
db4a061ed6 | ||
|
|
f6bbeafd26 | ||
|
|
f14e39d451 | ||
|
|
084f5d891b | ||
|
|
b0f72e13d9 | ||
|
|
8d7d80ef9d | ||
|
|
cf5de082b5 | ||
|
|
1944451082 | ||
|
|
602e5a8200 | ||
|
|
83b8b7acb7 | ||
|
|
e660f19507 | ||
|
|
ff412b927d | ||
|
|
392ef1eb06 | ||
|
|
dcecdc9be6 | ||
|
|
ed69b68892 | ||
|
|
42b982e13c | ||
|
|
e7e2592357 | ||
|
|
739586f96a | ||
|
|
4c0f1f53d3 | ||
|
|
359f12791a | ||
|
|
9354ab62dd | ||
|
|
23ff74bb3f | ||
|
|
98d17e2293 | ||
|
|
ababa6ae5b | ||
|
|
097cd0c242 | ||
|
|
b91cd66fc5 | ||
|
|
787d630157 | ||
|
|
1f938c560a | ||
|
|
a96924a555 | ||
|
|
33dc6629ff | ||
|
|
1b3fb0af8c | ||
|
|
12da523ff7 | ||
|
|
0f49d80623 | ||
|
|
1717516a78 | ||
|
|
328c6d3060 | ||
|
|
566720be15 | ||
|
|
65f13c3cc6 | ||
|
|
31b2e2d7b4 | ||
|
|
8fe8d74056 | ||
|
|
fcc4742056 | ||
|
|
d98e2e7498 | ||
|
|
d37ffa1149 |
41
.gitignore
vendored
41
.gitignore
vendored
@@ -1,19 +1,26 @@
|
||||
coverage.html
|
||||
.DS_Store
|
||||
lib-cov
|
||||
*.seed
|
||||
# OS X
|
||||
.DS_Store*
|
||||
Icon?
|
||||
._*
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Linux
|
||||
.directory
|
||||
*~
|
||||
|
||||
|
||||
# npm
|
||||
node_modules
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.swp
|
||||
*.swo
|
||||
*.gz
|
||||
|
||||
|
||||
# Coveralls
|
||||
coverage
|
||||
|
||||
# Benchmarking
|
||||
benchmarks/graphs
|
||||
testing
|
||||
node_modules/
|
||||
testing
|
||||
.coverage_data
|
||||
cover_html
|
||||
test.js
|
||||
.idea
|
||||
|
||||
11
.npmignore
11
.npmignore
@@ -1,11 +0,0 @@
|
||||
.git*
|
||||
benchmarks/
|
||||
docs/
|
||||
examples/
|
||||
support/
|
||||
test/
|
||||
testing.js
|
||||
.DS_Store
|
||||
.travis.yml
|
||||
coverage.html
|
||||
lib-cov
|
||||
@@ -6,3 +6,5 @@ matrix:
|
||||
allow_failures:
|
||||
- node_js: "0.11"
|
||||
fast_finish: true
|
||||
script: "npm run-script test-travis"
|
||||
after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls"
|
||||
|
||||
25
Contributing.md
Normal file
25
Contributing.md
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
## Website Issues
|
||||
|
||||
Issues for the expressjs.com website go here https://github.com/strongloop/expressjs.com
|
||||
|
||||
## PRs and Code contributions
|
||||
|
||||
* Tests must pass.
|
||||
* Follow existing coding style.
|
||||
* If you fix a bug, add a test.
|
||||
|
||||
|
||||
## Issues which are questions
|
||||
|
||||
We will typically close any vague issues or questions that are specific to some app you are writing. Please double check the docs and other references before being trigger happy with posting a question issue.
|
||||
|
||||
Things that will help get your question issue looked at:
|
||||
|
||||
* Full and runnable JS code.
|
||||
* Clear description of the problem or unexpected behavior.
|
||||
* Clear description of the expected result.
|
||||
* Steps you have taken to debug it yourself.
|
||||
|
||||
If you post a question and do not outline the above items or make it easy for us to understand and reproduce your issue, it will be closed.
|
||||
|
||||
1042
History.md
1042
History.md
File diff suppressed because it is too large
Load Diff
34
Makefile
34
Makefile
@@ -1,34 +0,0 @@
|
||||
|
||||
MOCHA_OPTS= --check-leaks
|
||||
REPORTER = dot
|
||||
|
||||
check: test
|
||||
|
||||
test: test-unit test-acceptance
|
||||
|
||||
test-unit:
|
||||
@NODE_ENV=test ./node_modules/.bin/mocha \
|
||||
--reporter $(REPORTER) \
|
||||
--globals setImmediate,clearImmediate \
|
||||
$(MOCHA_OPTS)
|
||||
|
||||
test-acceptance:
|
||||
@NODE_ENV=test ./node_modules/.bin/mocha \
|
||||
--reporter $(REPORTER) \
|
||||
--bail \
|
||||
test/acceptance/*.js
|
||||
|
||||
test-cov: lib-cov
|
||||
@EXPRESS_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html
|
||||
|
||||
lib-cov:
|
||||
@jscoverage lib lib-cov
|
||||
|
||||
bench:
|
||||
@$(MAKE) -C benchmarks
|
||||
|
||||
clean:
|
||||
rm -f coverage.html
|
||||
rm -fr lib-cov
|
||||
|
||||
.PHONY: test test-unit test-acceptance bench clean
|
||||
159
Readme.md
159
Readme.md
@@ -1,55 +1,78 @@
|
||||
[](http://expressjs.com/)
|
||||
[](http://expressjs.com/)
|
||||
|
||||
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
|
||||
|
||||
[](https://travis-ci.org/visionmedia/express) [](https://www.gittip.com/visionmedia/) [](http://badge.fury.io/js/express)
|
||||
[![NPM Version][npm-image]][npm-url]
|
||||
[![NPM Downloads][downloads-image]][downloads-url]
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
|
||||
```js
|
||||
var express = require('express');
|
||||
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).
|
||||
### Installation
|
||||
|
||||
## Installation
|
||||
|
||||
$ 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
|
||||
|
||||
Create the app:
|
||||
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
|
||||
Install dependencies:
|
||||
|
||||
$ npm install
|
||||
|
||||
Start the server:
|
||||
|
||||
$ npm start
|
||||
```bash
|
||||
$ npm install express
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* Robust routing
|
||||
* Focus on high performance
|
||||
* Super-high test coverage
|
||||
* HTTP helpers (redirection, caching, etc)
|
||||
* View system supporting 14+ template engines
|
||||
* Content negotiation
|
||||
* Focus on high performance
|
||||
* Executable for generating applications quickly
|
||||
* High test coverage
|
||||
|
||||
## Docs & Community
|
||||
|
||||
* [Website and Documentation](http://expressjs.com/) - [[website repo](https://github.com/strongloop/expressjs.com)]
|
||||
* [#express](https://webchat.freenode.net/?channels=express) on freenode IRC
|
||||
* [Github Organization](https://github.com/expressjs) for Official Middleware & Modules
|
||||
* Visit the [Wiki](https://github.com/strongloop/express/wiki)
|
||||
* [Google Group](https://groups.google.com/group/express-js) for discussion
|
||||
* [Русскоязычная документация](http://jsman.ru/express/)
|
||||
* [한국어 문서](http://expressjs.kr) - [[website repo](https://github.com/Hanul/expressjs.kr)]
|
||||
|
||||
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/strongloop/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/strongloop/express/wiki/New-features-in-4.x).
|
||||
|
||||
## Quick Start
|
||||
|
||||
The quickest way to get started with express is to utilize the executable [`express(1)`](https://github.com/expressjs/generator) to generate an application as shown below:
|
||||
|
||||
Install the executable. The executable's major version will match Express's:
|
||||
|
||||
```bash
|
||||
$ npm install -g express-generator@4
|
||||
```
|
||||
|
||||
Create the app:
|
||||
|
||||
```bash
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Start the server:
|
||||
|
||||
```bash
|
||||
$ npm start
|
||||
```
|
||||
|
||||
## Philosophy
|
||||
|
||||
@@ -58,51 +81,55 @@ 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/tj/consolidate.js),
|
||||
you can quickly craft your perfect framework.
|
||||
|
||||
## More Information
|
||||
## Examples
|
||||
|
||||
* [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)
|
||||
* [Русскоязычная документация](http://jsman.ru/express/)
|
||||
* Run express examples [online](https://runnable.com/express)
|
||||
To view the examples, clone the Express repo & install the dependancies:
|
||||
|
||||
## Viewing Examples
|
||||
```bash
|
||||
$ git clone git://github.com/strongloop/express.git --depth 1
|
||||
$ cd express
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
|
||||
Then run whichever example you want:
|
||||
|
||||
$ git clone git://github.com/visionmedia/express.git --depth 1
|
||||
$ cd express
|
||||
$ npm install
|
||||
```bash
|
||||
$ node examples/content-negotiation
|
||||
```
|
||||
|
||||
Then run whichever tests you want:
|
||||
## Tests
|
||||
|
||||
$ node examples/content-negotiation
|
||||
To run the test suite, first install the dependancies, then run `npm test`:
|
||||
|
||||
You can also view live examples here:
|
||||
```bash
|
||||
$ npm install
|
||||
$ npm test
|
||||
```
|
||||
|
||||
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
|
||||
### People
|
||||
|
||||
## Running Tests
|
||||
The original author of Express is [TJ Holowaychuk](https://github.com/tj) [![TJ's Gratipay][gratipay-image-visionmedia]][gratipay-url-visionmedia]
|
||||
|
||||
To run the test suite, first invoke the following command within the repo, installing the development dependencies:
|
||||
The current lead maintainer is [Douglas Christopher Wilson](https://github.com/dougwilson) [![Doug's Gratipay][gratipay-image-dougwilson]][gratipay-url-dougwilson]
|
||||
|
||||
$ npm install
|
||||
[List of all contributors](https://github.com/strongloop/express/graphs/contributors)
|
||||
|
||||
Then run the tests:
|
||||
### License
|
||||
|
||||
$ make test
|
||||
[MIT](LICENSE)
|
||||
|
||||
## Contributors
|
||||
|
||||
Author: [TJ Holowaychuk](http://github.com/visionmedia)
|
||||
Lead Maintainer: [Roman Shtylman](https://github.com/defunctzombie)
|
||||
Contributors: https://github.com/visionmedia/express/graphs/contributors
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
[npm-image]: https://img.shields.io/npm/v/express.svg?style=flat
|
||||
[npm-url]: https://npmjs.org/package/express
|
||||
[downloads-image]: https://img.shields.io/npm/dm/express.svg?style=flat
|
||||
[downloads-url]: https://npmjs.org/package/express
|
||||
[travis-image]: https://img.shields.io/travis/strongloop/express.svg?style=flat
|
||||
[travis-url]: https://travis-ci.org/strongloop/express
|
||||
[coveralls-image]: https://img.shields.io/coveralls/strongloop/express.svg?style=flat
|
||||
[coveralls-url]: https://coveralls.io/r/strongloop/express?branch=master
|
||||
[gratipay-image-visionmedia]: https://img.shields.io/gratipay/visionmedia.svg?style=flat
|
||||
[gratipay-url-visionmedia]: https://gratipay/visionmedia/
|
||||
[gratipay-image-dougwilson]: https://img.shields.io/gratipay/dougwilson.svg?style=flat
|
||||
[gratipay-url-dougwilson]: https://gratipay/dougwilson/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,7 +80,7 @@ function restrict(req, res, next) {
|
||||
}
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.redirect('login');
|
||||
res.redirect('/login');
|
||||
});
|
||||
|
||||
app.get('/restricted', restrict, function(req, res){
|
||||
@@ -116,11 +118,12 @@ app.post('/login', function(req, res){
|
||||
req.session.error = 'Authentication failed, please check your '
|
||||
+ ' username and password.'
|
||||
+ ' (use "tj" and "foobar")';
|
||||
res.redirect('login');
|
||||
res.redirect('/login');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
// check out https://github.com/visionmedia/node-pwd
|
||||
// check out https://github.com/tj/node-pwd
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
|
||||
@@ -24,5 +24,8 @@ app.get('/', function(req, res){
|
||||
res.render('pets', { pets: pets });
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express listening on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ function format(path) {
|
||||
|
||||
app.get('/users', format('./users'));
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('listening on port 3000');
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -23,7 +19,8 @@ function count(req, res) {
|
||||
res.send('viewed ' + n + ' times\n');
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express server listening on port 3000');
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ var cookieParser = require('cookie-parser');
|
||||
var bodyParser = require('body-parser');
|
||||
|
||||
// custom log format
|
||||
if ('test' != process.env.NODE_ENV)
|
||||
app.use(logger(':method :url'));
|
||||
if ('test' != process.env.NODE_ENV) app.use(logger(':method :url'));
|
||||
|
||||
// parses request cookies, populating
|
||||
// req.cookies and req.signedCookies
|
||||
@@ -18,8 +17,8 @@ if ('test' != process.env.NODE_ENV)
|
||||
// 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) {
|
||||
@@ -42,7 +41,8 @@ app.post('/', function(req, res){
|
||||
res.redirect('back');
|
||||
});
|
||||
|
||||
if (!module.parent){
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
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.
|
||||
@@ -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>');
|
||||
});
|
||||
|
||||
@@ -19,25 +20,16 @@ app.get('/files/:file(*)', function(req, res, next){
|
||||
var file = req.params.file;
|
||||
var path = __dirname + '/files/' + file;
|
||||
|
||||
res.download(path);
|
||||
});
|
||||
|
||||
// error handling middleware. Because it's
|
||||
// below our routes, you will be able to
|
||||
// "intercept" errors, otherwise Connect
|
||||
// will respond with 500 "Internal Server Error".
|
||||
app.use(function(err, req, res, next){
|
||||
// special-case 404s,
|
||||
// remember you could
|
||||
// render a 404 template here
|
||||
if (404 == err.status) {
|
||||
res.download(path, function(err){
|
||||
if (!err) return; // file sent
|
||||
if (err && err.status !== 404) return next(err); // non-404 error
|
||||
// file for download not found
|
||||
res.statusCode = 404;
|
||||
res.send('Cant find that file, sorry!');
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
@@ -43,7 +43,8 @@ app.get('/', function(req, res){
|
||||
});
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@ app.enable('verbose errors');
|
||||
|
||||
// disable them in production
|
||||
// use $ NODE_ENV=production node examples/error-pages
|
||||
if ('production' == app.settings.env) {
|
||||
app.disable('verbose errors');
|
||||
}
|
||||
if ('production' == app.settings.env) app.disable('verbose errors');
|
||||
|
||||
silent || app.use(logger('dev'));
|
||||
|
||||
@@ -99,7 +97,8 @@ app.use(function(err, req, res, next){
|
||||
});
|
||||
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
silent || console.log('Express started on port 3000');
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
@@ -40,6 +41,7 @@ app.get('/next', function(req, res, next){
|
||||
// from app.get() etc
|
||||
app.use(error);
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
|
||||
@@ -57,5 +57,8 @@ app.get('/user', function(req, res){
|
||||
res.render('page');
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('app listening on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -6,5 +6,8 @@ app.get('/', function(req, res){
|
||||
res.send('Hello World');
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -44,5 +44,8 @@ app.use(function(err, req, res, next) {
|
||||
res.send(err.stack);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ app.get('/fail', function(req, res){
|
||||
res.render('missing', { title: 'Markdown Example' });
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
|
||||
@@ -53,7 +53,8 @@ app.post('/', function(req, res, next){
|
||||
form.parse(req);
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(4000);
|
||||
console.log('Express started on port 3000');
|
||||
console.log('Express started on port 4000');
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ var db = require('../../db');
|
||||
|
||||
exports.before = function(req, res, next){
|
||||
var pet = db.pets[req.params.pet_id];
|
||||
if (!pet) return next(new Error('Pet not found'));
|
||||
if (!pet) return next('route');
|
||||
req.pet = pet;
|
||||
next();
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
link(rel='stylesheet', href='/style.css')
|
||||
h1= pet.name
|
||||
form(action='/pet/#{pet.id}', method='post')
|
||||
input(type='hidden', name='_method', value='put')
|
||||
form(action='/pet/#{pet.id}?_method=put', method='post')
|
||||
label= 'Name: '
|
||||
input(type='text', name='pet[name]', value=pet.name)
|
||||
input(type='submit', value='Update')
|
||||
|
||||
@@ -11,7 +11,7 @@ exports.create = function(req, res, next){
|
||||
var id = req.params.user_id;
|
||||
var user = db.users[id];
|
||||
var body = req.body;
|
||||
if (!user) return next(new Error('User not found'));
|
||||
if (!user) return next('route');
|
||||
var pet = { name: body.pet.name };
|
||||
pet.id = db.pets.push(pet) - 1;
|
||||
user.pets.push(pet);
|
||||
|
||||
@@ -11,7 +11,7 @@ exports.before = function(req, res, next){
|
||||
process.nextTick(function(){
|
||||
req.user = db.users[id];
|
||||
// cant find that user
|
||||
if (!req.user) return next(new Error('User not found'));
|
||||
if (!req.user) return next('route');
|
||||
// found it, move on to the routes
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
link(rel='stylesheet', href='/style.css')
|
||||
h1= user.name
|
||||
form(action='/user/#{user.id}', method='post')
|
||||
input(type='hidden', name='_method', value='put')
|
||||
form(action='/user/#{user.id}?_method=put', method='post')
|
||||
label= 'Name: '
|
||||
input(type='text', name='user[name]', value='#{user.name}')
|
||||
input(type='submit', value='Update')
|
||||
|
||||
@@ -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,14 +37,17 @@ 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 }));
|
||||
|
||||
// override methods (put, delete)
|
||||
app.use(methodOverride());
|
||||
// allow overriding methods in query (?_method=put)
|
||||
app.use(methodOverride('_method'));
|
||||
|
||||
// expose the "messages" local variable when views are rendered
|
||||
app.use(function(req, res, next){
|
||||
@@ -73,16 +75,9 @@ app.use(function(req, res, next){
|
||||
// load controllers
|
||||
require('./lib/boot')(app, { verbose: !module.parent });
|
||||
|
||||
// assume "not found" in the error msgs
|
||||
// is a 404. this is somewhat silly, but
|
||||
// valid, you can do whatever you like, set
|
||||
// properties, use instanceof etc.
|
||||
app.use(function(err, req, res, next){
|
||||
// treat as 404
|
||||
if (~err.message.indexOf('not found')) return next();
|
||||
|
||||
// log it
|
||||
console.error(err.stack);
|
||||
if (!module.parent) console.error(err.stack);
|
||||
|
||||
// error page
|
||||
res.status(500).render('5xx');
|
||||
@@ -93,7 +88,8 @@ app.use(function(req, res, next){
|
||||
res.status(404).render('404', { url: req.originalUrl });
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('\n listening on port 3000\n');
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ module.exports = function(parent, options){
|
||||
var name = obj.name || name;
|
||||
var prefix = obj.prefix || '';
|
||||
var app = express();
|
||||
var handler;
|
||||
var method;
|
||||
var path;
|
||||
|
||||
@@ -20,16 +21,6 @@ module.exports = function(parent, options){
|
||||
if (obj.engine) app.set('view engine', obj.engine);
|
||||
app.set('views', __dirname + '/../controllers/' + name + '/views');
|
||||
|
||||
// before middleware support
|
||||
if (obj.before) {
|
||||
path = '/' + name + '/:' + name + '_id';
|
||||
app.all(path, obj.before);
|
||||
verbose && console.log(' ALL %s -> before', path);
|
||||
path = '/' + name + '/:' + name + '_id/*';
|
||||
app.all(path, obj.before);
|
||||
verbose && console.log(' ALL %s -> before', path);
|
||||
}
|
||||
|
||||
// generate routes based
|
||||
// on the exported methods
|
||||
for (var key in obj) {
|
||||
@@ -62,15 +53,25 @@ module.exports = function(parent, options){
|
||||
path = '/';
|
||||
break;
|
||||
default:
|
||||
/* istanbul ignore next */
|
||||
throw new Error('unrecognized route: ' + name + '.' + key);
|
||||
}
|
||||
|
||||
// setup
|
||||
handler = obj[key];
|
||||
path = prefix + path;
|
||||
app[method](path, obj[key]);
|
||||
verbose && console.log(' %s %s -> %s', method.toUpperCase(), path, key);
|
||||
|
||||
// before middleware support
|
||||
if (obj.before) {
|
||||
app[method](path, obj.before, handler);
|
||||
verbose && console.log(' %s %s -> before -> %s', method.toUpperCase(), path, key);
|
||||
} else {
|
||||
app[method](path, obj[key]);
|
||||
verbose && console.log(' %s %s -> %s', method.toUpperCase(), path, key);
|
||||
}
|
||||
}
|
||||
|
||||
// mount the app
|
||||
parent.use(app);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -49,5 +49,8 @@ app.get('/', function(req, res, next){
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('listening on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ var users = [
|
||||
// Convert :to and :from to integers
|
||||
|
||||
app.param(['to', 'from'], function(req, res, next, num, name){
|
||||
req.params[name] = num = parseInt(num, 10);
|
||||
if( isNaN(num) ){
|
||||
req.params[name] = parseInt(num, 10);
|
||||
if( isNaN(req.params[name]) ){
|
||||
next(new Error('failed to parseInt '+num));
|
||||
} else {
|
||||
next();
|
||||
@@ -63,7 +63,8 @@ app.get('/users/:from-:to', function(req, res, next){
|
||||
res.send('users ' + names.slice(from, to).join(', '));
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,10 @@ app.resource = function(path, obj) {
|
||||
obj.range(req, res, a, b, format);
|
||||
});
|
||||
this.get(path + '/:id', obj.show);
|
||||
this.delete(path + '/:id', obj.destroy);
|
||||
this.delete(path + '/:id', function(req, res){
|
||||
var id = parseInt(req.params.id, 10);
|
||||
obj.destroy(req, res, id);
|
||||
});
|
||||
};
|
||||
|
||||
// Fake records
|
||||
@@ -40,8 +43,7 @@ var User = {
|
||||
show: function(req, res){
|
||||
res.send(users[req.params.id] || { error: 'Cannot find user' });
|
||||
},
|
||||
destroy: function(req, res){
|
||||
var id = req.params.id;
|
||||
destroy: function(req, res, id){
|
||||
var destroyed = id in users;
|
||||
delete users[id];
|
||||
res.send(destroyed ? 'destroyed' : 'Cannot find user');
|
||||
@@ -84,7 +86,8 @@ app.get('/', function(req, res){
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
}
|
||||
@@ -65,4 +65,8 @@ app.map({
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -81,5 +81,8 @@ app.delete('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
|
||||
res.send('Deleted user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -7,17 +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
|
||||
@@ -26,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);
|
||||
@@ -37,5 +46,8 @@ app.put('/user/:id/edit', user.update);
|
||||
|
||||
app.get('/posts', post.list);
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,8 +3,7 @@ extends ../layout
|
||||
block content
|
||||
h1 Editing #{user.name}
|
||||
#user
|
||||
form(method="post")
|
||||
input(type="hidden", value="put", name="_method")
|
||||
form(action="?_method=put", method="post")
|
||||
p Name:
|
||||
input(type="text", value= user.name, name="user[name]")
|
||||
p Email:
|
||||
|
||||
@@ -47,15 +47,18 @@ 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');
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('app listening on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -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 = '';
|
||||
@@ -27,5 +25,8 @@ app.get('/', function(req, res){
|
||||
res.send(body + '<p>viewed <strong>' + req.session.views + '</strong> times.</p>');
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -18,7 +18,7 @@ edit /etc/hosts:
|
||||
|
||||
var main = express();
|
||||
|
||||
main.use(logger('dev'));
|
||||
if (!module.parent) main.use(logger('dev'));
|
||||
|
||||
main.get('/', function(req, res){
|
||||
res.send('Hello from main app!');
|
||||
@@ -32,17 +32,20 @@ main.get('/:sub', function(req, res){
|
||||
|
||||
var redirect = express();
|
||||
|
||||
redirect.all('*', function(req, res){
|
||||
console.log(req.subdomains);
|
||||
res.redirect('http://example.com:3000/' + req.subdomains[0]);
|
||||
redirect.use(function(req, res){
|
||||
if (!module.parent) console.log(req.vhost);
|
||||
res.redirect('http://example.com:3000/' + req.vhost[0]);
|
||||
});
|
||||
|
||||
// Vhost app
|
||||
|
||||
var app = express();
|
||||
var app = module.exports = express();
|
||||
|
||||
app.use(vhost('*.example.com', redirect)); // Serves all subdomains via Redirect app
|
||||
app.use(vhost('example.com', main)); // Serves top level domain via Main server app
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -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,10 +36,11 @@ 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');
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
|
||||
@@ -145,5 +145,8 @@ app.all('/api/*', function(req, res, next){
|
||||
|
||||
*/
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Application listening on port 3000');
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -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,17 +93,20 @@ 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 */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express server listening on port 3000');
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
4
index.js
4
index.js
@@ -1,4 +1,2 @@
|
||||
|
||||
module.exports = process.env.EXPRESS_COV
|
||||
? require('./lib-cov/express')
|
||||
: require('./lib/express');
|
||||
module.exports = require('./lib/express');
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var mixin = require('utils-merge');
|
||||
var escapeHtml = require('escape-html');
|
||||
var finalhandler = require('finalhandler');
|
||||
var flatten = require('./utils').flatten;
|
||||
var Router = require('./router');
|
||||
var methods = require('methods');
|
||||
var middleware = require('./middleware/init');
|
||||
@@ -11,7 +11,13 @@ var query = require('./middleware/query');
|
||||
var debug = require('debug')('express:application');
|
||||
var View = require('./view');
|
||||
var http = require('http');
|
||||
var deprecate = require('./utils').deprecate;
|
||||
var compileETag = require('./utils').compileETag;
|
||||
var compileQueryParser = require('./utils').compileQueryParser;
|
||||
var compileTrust = require('./utils').compileTrust;
|
||||
var deprecate = require('depd')('express');
|
||||
var merge = require('utils-merge');
|
||||
var resolve = require('path').resolve;
|
||||
var slice = Array.prototype.slice;
|
||||
|
||||
/**
|
||||
* Application prototype.
|
||||
@@ -45,10 +51,12 @@ app.init = function(){
|
||||
app.defaultConfiguration = function(){
|
||||
// default settings
|
||||
this.enable('x-powered-by');
|
||||
this.enable('etag');
|
||||
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);
|
||||
|
||||
debug('booting in %s mode', env);
|
||||
|
||||
@@ -71,7 +79,7 @@ app.defaultConfiguration = function(){
|
||||
|
||||
// default configuration
|
||||
this.set('view', View);
|
||||
this.set('views', process.cwd() + '/views');
|
||||
this.set('views', resolve('views'));
|
||||
this.set('jsonp callback name', 'callback');
|
||||
|
||||
if (env === 'production') {
|
||||
@@ -100,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));
|
||||
}
|
||||
};
|
||||
@@ -115,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);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -163,43 +148,62 @@ app.handle = function(req, res, done) {
|
||||
* If the _fn_ parameter is an express app, then it will be
|
||||
* mounted at the _route_ specified.
|
||||
*
|
||||
* @param {String|Function|Server} route
|
||||
* @param {Function|Server} fn
|
||||
* @return {app} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.use = function(route, fn){
|
||||
var mount_app;
|
||||
app.use = function use(fn) {
|
||||
var offset = 0;
|
||||
var path = '/';
|
||||
|
||||
// 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 = this;
|
||||
|
||||
// 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', this);
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -242,7 +246,7 @@ app.route = function(path){
|
||||
* so if you're using ".ejs" extensions you dont need to do anything.
|
||||
*
|
||||
* Some template engines do not follow this convention, the
|
||||
* [Consolidate.js](https://github.com/visionmedia/consolidate.js)
|
||||
* [Consolidate.js](https://github.com/tj/consolidate.js)
|
||||
* library was created to map all of node's popular template
|
||||
* engines to follow this convention, thus allowing them to
|
||||
* work seamlessly within Express.
|
||||
@@ -273,17 +277,16 @@ app.engine = function(ext, fn){
|
||||
*/
|
||||
|
||||
app.param = function(name, fn){
|
||||
var self = this;
|
||||
self.lazyrouter();
|
||||
this.lazyrouter();
|
||||
|
||||
if (Array.isArray(name)) {
|
||||
name.forEach(function(key) {
|
||||
self.param(key, fn);
|
||||
});
|
||||
this.param(key, fn);
|
||||
}, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
self._router.param(name, fn);
|
||||
this._router.param(name, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -303,12 +306,31 @@ app.param = function(name, fn){
|
||||
*/
|
||||
|
||||
app.set = function(setting, val){
|
||||
if (1 == arguments.length) {
|
||||
if (arguments.length === 1) {
|
||||
// app.get(setting)
|
||||
return this.settings[setting];
|
||||
} else {
|
||||
this.settings[setting] = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
// set value
|
||||
this.settings[setting] = val;
|
||||
|
||||
// trigger matched settings
|
||||
switch (setting) {
|
||||
case 'etag':
|
||||
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));
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -404,7 +426,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;
|
||||
};
|
||||
});
|
||||
@@ -423,7 +445,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);
|
||||
});
|
||||
@@ -433,7 +455,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`
|
||||
@@ -464,13 +486,15 @@ app.render = function(name, options, fn){
|
||||
}
|
||||
|
||||
// merge app.locals
|
||||
mixin(opts, this.locals);
|
||||
merge(opts, this.locals);
|
||||
|
||||
// merge options._locals
|
||||
if (options._locals) mixin(opts, options._locals);
|
||||
if (options._locals) {
|
||||
merge(opts, options._locals);
|
||||
}
|
||||
|
||||
// merge options
|
||||
mixin(opts, options);
|
||||
merge(opts, options);
|
||||
|
||||
// set .cache unless explicitly provided
|
||||
opts.cache = null == opts.cache
|
||||
@@ -489,7 +513,10 @@ app.render = function(name, options, fn){
|
||||
});
|
||||
|
||||
if (!view.path) {
|
||||
var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"');
|
||||
var dirs = Array.isArray(view.root) && view.root.length > 1
|
||||
? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
|
||||
: 'directory "' + view.root + '"'
|
||||
var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
|
||||
err.view = view;
|
||||
return fn(err);
|
||||
}
|
||||
@@ -531,3 +558,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());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var mixin = require('utils-merge');
|
||||
var mixin = require('merge-descriptors');
|
||||
var proto = require('./application');
|
||||
var Route = require('./router/route');
|
||||
var Router = require('./router');
|
||||
|
||||
@@ -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();
|
||||
|
||||
155
lib/request.js
155
lib/request.js
@@ -3,11 +3,14 @@
|
||||
*/
|
||||
|
||||
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');
|
||||
var parseRange = require('range-parser');
|
||||
var parse = require('parseurl');
|
||||
var proxyaddr = require('proxy-addr');
|
||||
|
||||
/**
|
||||
* Request prototype.
|
||||
@@ -105,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`.
|
||||
@@ -239,20 +244,30 @@ req.is = function(types){
|
||||
/**
|
||||
* Return the protocol string "http" or "https"
|
||||
* when requested with TLS. When the "trust proxy"
|
||||
* setting is enabled the "X-Forwarded-Proto" header
|
||||
* field will be trusted. If you're running behind
|
||||
* a reverse proxy that supplies https for you this
|
||||
* may be enabled.
|
||||
* setting trusts the socket address, the
|
||||
* "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.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('protocol', function(){
|
||||
var trustProxy = this.app.get('trust proxy');
|
||||
if (this.connection.encrypted) return 'https';
|
||||
if (!trustProxy) return 'http';
|
||||
var proto = this.get('X-Forwarded-Proto') || 'http';
|
||||
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 proto;
|
||||
}
|
||||
|
||||
// Note: X-Forwarded-Proto is normally only ever a
|
||||
// single value, but this is to be safe.
|
||||
proto = this.get('X-Forwarded-Proto') || proto;
|
||||
return proto.split(/\s*,\s*/)[0];
|
||||
});
|
||||
|
||||
@@ -265,41 +280,41 @@ req.__defineGetter__('protocol', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('secure', function(){
|
||||
defineGetter(req, 'secure', function secure(){
|
||||
return 'https' == this.protocol;
|
||||
});
|
||||
|
||||
/**
|
||||
* Return the remote address, or when
|
||||
* "trust proxy" is `true` return
|
||||
* the upstream addr.
|
||||
* Return the remote address from the trusted proxy.
|
||||
*
|
||||
* The is the remote address on the socket unless
|
||||
* "trust proxy" is set.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('ip', function(){
|
||||
return this.ips[0] || this.connection.remoteAddress;
|
||||
defineGetter(req, 'ip', function ip(){
|
||||
var trust = this.app.get('trust proxy fn');
|
||||
return proxyaddr(this, trust);
|
||||
});
|
||||
|
||||
/**
|
||||
* When "trust proxy" is `true`, parse
|
||||
* the "X-Forwarded-For" ip address list.
|
||||
* When "trust proxy" is set, trusted proxy addresses + client.
|
||||
*
|
||||
* For example if the value were "client, proxy1, proxy2"
|
||||
* you would receive the array `["client", "proxy1", "proxy2"]`
|
||||
* where "proxy2" is the furthest down-stream.
|
||||
* where "proxy2" is the furthest down-stream and "proxy1" and
|
||||
* "proxy2" were trusted.
|
||||
*
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('ips', function(){
|
||||
var trustProxy = this.app.get('trust proxy');
|
||||
var val = this.get('X-Forwarded-For');
|
||||
return trustProxy && val
|
||||
? val.split(/ *, */)
|
||||
: [];
|
||||
defineGetter(req, 'ips', function ips() {
|
||||
var trust = this.app.get('trust proxy fn');
|
||||
var addrs = proxyaddr.all(this, trust);
|
||||
return addrs.slice(1).reverse();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -317,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);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -332,31 +352,48 @@ 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
|
||||
* be trusted.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('host', function(){
|
||||
var trustProxy = this.app.get('trust proxy');
|
||||
var host = trustProxy && this.get('X-Forwarded-Host');
|
||||
host = host || this.get('Host');
|
||||
defineGetter(req, 'hostname', function hostname(){
|
||||
var trust = this.app.get('trust proxy fn');
|
||||
var host = this.get('X-Forwarded-Host');
|
||||
|
||||
if (!host || !trust(this.connection.remoteAddress)) {
|
||||
host = this.get('Host');
|
||||
}
|
||||
|
||||
if (!host) return;
|
||||
|
||||
// IPv6 literal support
|
||||
var offset = host[0] === '['
|
||||
? host.indexOf(']') + 1
|
||||
: 0;
|
||||
var index = host.indexOf(':', offset);
|
||||
|
||||
return ~index
|
||||
? host.substring(0, index)
|
||||
: 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
|
||||
@@ -366,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;
|
||||
|
||||
@@ -390,7 +427,7 @@ req.__defineGetter__('fresh', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('stale', function(){
|
||||
defineGetter(req, 'stale', function stale(){
|
||||
return !this.fresh;
|
||||
});
|
||||
|
||||
@@ -401,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
|
||||
});
|
||||
};
|
||||
|
||||
562
lib/response.js
562
lib/response.js
@@ -2,22 +2,25 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http');
|
||||
var path = require('path');
|
||||
var mixin = require('utils-merge');
|
||||
var contentDisposition = require('content-disposition');
|
||||
var deprecate = require('depd')('express');
|
||||
var escapeHtml = require('escape-html');
|
||||
var http = require('http');
|
||||
var isAbsolute = require('./utils').isAbsolute;
|
||||
var onFinished = require('on-finished');
|
||||
var path = require('path');
|
||||
var merge = require('utils-merge');
|
||||
var sign = require('cookie-signature').sign;
|
||||
var normalizeType = require('./utils').normalizeType;
|
||||
var normalizeTypes = require('./utils').normalizeTypes;
|
||||
var contentDisposition = require('./utils').contentDisposition;
|
||||
var deprecate = require('./utils').deprecate;
|
||||
var etag = require('./utils').etag;
|
||||
var setCharset = require('./utils').setCharset;
|
||||
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');
|
||||
|
||||
/**
|
||||
* Response prototype.
|
||||
@@ -71,69 +74,100 @@ 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 {Mixed} body or status
|
||||
* @param {Mixed} body
|
||||
* @return {ServerResponse}
|
||||
* @param {string|number|boolean|object|Buffer} body
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.send = function(body){
|
||||
var req = this.req;
|
||||
var head = 'HEAD' == req.method;
|
||||
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.sendStatus(status) 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;
|
||||
}
|
||||
|
||||
// populate Content-Length
|
||||
if (undefined !== body && !this.get('Content-Length')) {
|
||||
this.set('Content-Length', len = Buffer.isBuffer(body)
|
||||
? body.length
|
||||
: Buffer.byteLength(body));
|
||||
// write strings in utf-8
|
||||
if (typeof chunk === 'string') {
|
||||
encoding = 'utf8';
|
||||
type = this.get('Content-Type');
|
||||
|
||||
// reflect this in content-type
|
||||
if (typeof type === 'string') {
|
||||
this.set('Content-Type', setCharset(type, 'utf-8'));
|
||||
}
|
||||
}
|
||||
|
||||
// populate Content-Length
|
||||
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
|
||||
// TODO: W/ support
|
||||
if (app.settings.etag && len && ('GET' == req.method || 'HEAD' == req.method)) {
|
||||
if (!this.get('ETag')) {
|
||||
this.set('ETag', etag(body));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,11 +179,17 @@ 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);
|
||||
this.end(chunk, encoding);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -160,27 +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 {Mixed} obj or status
|
||||
* @param {Mixed} obj
|
||||
* @return {ServerResponse}
|
||||
* @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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,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.
|
||||
*
|
||||
@@ -209,27 +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 {Mixed} obj or status
|
||||
* @param {Mixed} obj
|
||||
* @return {ServerResponse}
|
||||
* @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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,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)) {
|
||||
@@ -251,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`.
|
||||
@@ -277,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`.
|
||||
*
|
||||
@@ -303,72 +443,40 @@ var jsonpNumDeprecated = deprecate(res.json,
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Object|Function} options or fn
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -379,22 +487,27 @@ res.sendfile = function(path, options, fn){
|
||||
*
|
||||
* This method uses `res.sendfile()`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {String|Function} filename or fn
|
||||
* @param {Function} 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);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -513,9 +626,13 @@ res.format = function(obj){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.attachment = function(filename){
|
||||
if (filename) this.type(extname(filename));
|
||||
res.attachment = function attachment(filename) {
|
||||
if (filename) {
|
||||
this.type(extname(filename));
|
||||
}
|
||||
|
||||
this.set('Content-Disposition', contentDisposition(filename));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -538,8 +655,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)) {
|
||||
@@ -572,14 +689,14 @@ res.get = function(field){
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Object} options
|
||||
* @param {ServerResponse} for chaining
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.clearCookie = function(name, options){
|
||||
var opts = { expires: new Date(1), path: '/' };
|
||||
return this.cookie(name, '', options
|
||||
? mixin(opts, options)
|
||||
? merge(opts, options)
|
||||
: opts);
|
||||
};
|
||||
|
||||
@@ -603,11 +720,12 @@ res.clearCookie = function(name, options){
|
||||
* @param {String} name
|
||||
* @param {String|Object} val
|
||||
* @param {Options} options
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.cookie = function(name, val, options){
|
||||
options = mixin({}, options);
|
||||
options = merge({}, options);
|
||||
var secret = this.req.secret;
|
||||
var signed = options.signed;
|
||||
if (signed && !secret) throw new Error('cookieParser("secret") required for signed cookies');
|
||||
@@ -648,6 +766,7 @@ res.cookie = function(name, val, options){
|
||||
* res.location('../login');
|
||||
*
|
||||
* @param {String} url
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -675,41 +794,39 @@ 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(url, 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({
|
||||
text: function(){
|
||||
body = statusCodes[status] + '. Redirecting to ' + encodeURI(url);
|
||||
body = statusCodes[status] + '. Redirecting to ' + encodeURI(address);
|
||||
},
|
||||
|
||||
html: function(){
|
||||
var u = escapeHtml(url);
|
||||
var u = escapeHtml(address);
|
||||
body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
|
||||
},
|
||||
|
||||
@@ -721,7 +838,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);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -729,36 +851,19 @@ 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){
|
||||
var self = this;
|
||||
|
||||
// nothing
|
||||
if (!field) return this;
|
||||
|
||||
// array
|
||||
if (Array.isArray(field)) {
|
||||
field.forEach(function(field){
|
||||
self.vary(field);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var vary = this.get('Vary');
|
||||
|
||||
// append
|
||||
if (vary) {
|
||||
vary = vary.split(/ *, */);
|
||||
if (!~vary.indexOf(field)) vary.push(field);
|
||||
this.set('Vary', vary.join(', '));
|
||||
// checks for back-compat
|
||||
if (!field || (Array.isArray(field) && !field.length)) {
|
||||
deprecate('res.vary(): Provide a field name');
|
||||
return this;
|
||||
}
|
||||
|
||||
// set
|
||||
this.set('Vary', field);
|
||||
vary(this, field);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -772,9 +877,6 @@ res.vary = function(field){
|
||||
* - `cache` boolean hinting to the engine it should cache
|
||||
* - `filename` filename of the view being rendered
|
||||
*
|
||||
* @param {String} view
|
||||
* @param {Object|Function} options or callback function
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -801,3 +903,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,15 +124,14 @@ 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('://');
|
||||
var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://');
|
||||
var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
|
||||
var idx = 0;
|
||||
var removed = '';
|
||||
var slashAdded = false;
|
||||
var paramcalled = {};
|
||||
|
||||
// store options for OPTIONS request
|
||||
// only used if OPTIONS request
|
||||
@@ -130,16 +140,16 @@ proto.handle = function(req, res, done) {
|
||||
// middleware and routes
|
||||
var stack = self.stack;
|
||||
|
||||
// request-level next
|
||||
var parent = req.next;
|
||||
done = wrap(done, function(old, err) {
|
||||
req.next = parent;
|
||||
old(err);
|
||||
});
|
||||
// manage inter-router variables
|
||||
var parentParams = req.params;
|
||||
var parentUrl = req.baseUrl || '';
|
||||
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);
|
||||
|
||||
@@ -148,32 +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++];
|
||||
if (!layer) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (slashAdded) {
|
||||
req.url = req.url.substr(1);
|
||||
slashAdded = false;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -181,88 +197,115 @@ 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;
|
||||
}
|
||||
|
||||
req.params = layer.params;
|
||||
// Capture one-time layer values
|
||||
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, 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[layer.path.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);
|
||||
removed = layer.path;
|
||||
// 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);
|
||||
|
||||
// Ensure leading slash
|
||||
if (!fqdn && '/' != req.url[0]) {
|
||||
if (!fqdn && req.url[0] !== '/') {
|
||||
req.url = '/' + req.url;
|
||||
slashAdded = true;
|
||||
}
|
||||
|
||||
debug('%s %s : %s', layer.handle.name || 'anonymous', layer.path, req.originalUrl);
|
||||
var arity = layer.handle.length;
|
||||
if (err) {
|
||||
if (arity === 4) {
|
||||
layer.handle(err, req, res, next);
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
} else if (arity < 4) {
|
||||
layer.handle(req, res, next);
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
// Setup base URL (no trailing slash)
|
||||
req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
|
||||
? removed.substring(0, removed.length - 1)
|
||||
: removed);
|
||||
}
|
||||
}
|
||||
|
||||
function wrap(old, fn) {
|
||||
return function () {
|
||||
var args = [old].concat(slice.call(arguments));
|
||||
fn.apply(this, args);
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Process any parameters for the route.
|
||||
* Match request to a layer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
proto.process_params = function(route, req, res, done) {
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Process any parameters for the layer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
proto.process_params = function(layer, called, req, res, done) {
|
||||
var params = this.params;
|
||||
|
||||
// captured parameters from the route, keys and values
|
||||
var keys = route.keys;
|
||||
// captured parameters from the layer, keys and values
|
||||
var keys = layer.keys;
|
||||
|
||||
// fast track
|
||||
if (!keys || keys.length === 0) {
|
||||
@@ -270,10 +313,12 @@ proto.process_params = function(route, req, res, done) {
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
var name;
|
||||
var paramIndex = 0;
|
||||
var key;
|
||||
var paramVal;
|
||||
var paramCallbacks;
|
||||
var paramCalled;
|
||||
|
||||
// process params in order
|
||||
// param callbacks can be async
|
||||
@@ -288,27 +333,59 @@ proto.process_params = function(route, req, res, done) {
|
||||
|
||||
paramIndex = 0;
|
||||
key = keys[i++];
|
||||
paramVal = key && req.params[key.name];
|
||||
paramCallbacks = key && params[key.name];
|
||||
|
||||
try {
|
||||
if (paramCallbacks && undefined !== paramVal) {
|
||||
return paramCallback();
|
||||
} else if (key) {
|
||||
return param();
|
||||
}
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
if (!key) {
|
||||
return done();
|
||||
}
|
||||
|
||||
done();
|
||||
name = key.name;
|
||||
paramVal = req.params[name];
|
||||
paramCallbacks = params[name];
|
||||
paramCalled = called[name];
|
||||
|
||||
if (paramVal === undefined || !paramCallbacks) {
|
||||
return param();
|
||||
}
|
||||
|
||||
// param previously called with same value or error occurred
|
||||
if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) {
|
||||
// restore value
|
||||
req.params[name] = paramCalled.value;
|
||||
|
||||
// next param
|
||||
return param(paramCalled.error);
|
||||
}
|
||||
|
||||
called[name] = paramCalled = {
|
||||
error: null,
|
||||
match: paramVal,
|
||||
value: paramVal
|
||||
};
|
||||
|
||||
paramCallback();
|
||||
}
|
||||
|
||||
// single param callbacks
|
||||
function paramCallback(err) {
|
||||
var fn = paramCallbacks[paramIndex++];
|
||||
if (err || !fn) return param(err);
|
||||
fn(req, res, paramCallback, paramVal, key.name);
|
||||
|
||||
// store updated value
|
||||
paramCalled.value = req.params[key.name];
|
||||
|
||||
if (err) {
|
||||
// store error
|
||||
paramCalled.error = err;
|
||||
param(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fn) return param();
|
||||
|
||||
try {
|
||||
fn(req, res, paramCallback, paramVal, key.name);
|
||||
} catch (e) {
|
||||
paramCallback(e);
|
||||
}
|
||||
}
|
||||
|
||||
param();
|
||||
@@ -326,40 +403,54 @@ proto.process_params = function(route, 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 = '/';
|
||||
|
||||
// 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: this.caseSensitive,
|
||||
strict: false,
|
||||
end: false
|
||||
}, fn);
|
||||
|
||||
layer.route = undefined;
|
||||
|
||||
this.stack.push(layer);
|
||||
}, this);
|
||||
|
||||
this.stack.push(layer);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -395,7 +486,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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
var pathRegexp = require('path-to-regexp');
|
||||
var debug = require('debug')('express:router:layer');
|
||||
|
||||
/**
|
||||
* Module variables.
|
||||
*/
|
||||
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Expose `Layer`.
|
||||
*/
|
||||
@@ -18,10 +24,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,37 +94,73 @@ 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 (path == null) {
|
||||
// no path, nothing matches
|
||||
this.params = undefined;
|
||||
this.path = undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.regexp.fast_slash) {
|
||||
// fast path non-ending match for / (everything matches)
|
||||
this.params = {};
|
||||
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 prop;
|
||||
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];
|
||||
prop = key
|
||||
? key.name
|
||||
: n++;
|
||||
val = decode_param(m[i]);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (key) {
|
||||
params[key.name] = val;
|
||||
} else {
|
||||
params[n++] = val;
|
||||
if (val !== undefined || !(hasOwnProperty.call(params, prop))) {
|
||||
params[prop] = val;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
})();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -136,7 +131,6 @@ Route.prototype.dispatch = function(req, res, done){
|
||||
*/
|
||||
|
||||
Route.prototype.all = function(){
|
||||
var self = this;
|
||||
var callbacks = utils.flatten([].slice.call(arguments));
|
||||
callbacks.forEach(function(fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
@@ -145,23 +139,18 @@ 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;
|
||||
|
||||
return self;
|
||||
this.methods._all = true;
|
||||
this.stack.push(layer);
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
methods.forEach(function(method){
|
||||
Route.prototype[method] = function(){
|
||||
var self = this;
|
||||
var callbacks = utils.flatten([].slice.call(arguments));
|
||||
|
||||
callbacks.forEach(function(fn) {
|
||||
@@ -171,21 +160,14 @@ methods.forEach(function(method){
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
debug('%s %s', method, self.path);
|
||||
debug('%s %s', method, this.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 });
|
||||
});
|
||||
return self;
|
||||
this.methods[method] = true;
|
||||
this.stack.push(layer);
|
||||
}, this);
|
||||
return this;
|
||||
};
|
||||
});
|
||||
|
||||
197
lib/utils.js
197
lib/utils.js
@@ -2,36 +2,48 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var contentDisposition = require('content-disposition');
|
||||
var deprecate = require('depd')('express');
|
||||
var mime = require('send').mime;
|
||||
var crc32 = require('buffer-crc32');
|
||||
var basename = require('path').basename;
|
||||
var deprecate = require('util').deprecate;
|
||||
var etag = require('etag');
|
||||
var proxyaddr = require('proxy-addr');
|
||||
var qs = require('qs');
|
||||
var querystring = require('querystring');
|
||||
var typer = require('media-typer');
|
||||
|
||||
/**
|
||||
* Deprecate function, like core `util.deprecate`
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {String} msg
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.deprecate = function(fn, msg){
|
||||
return 'test' !== process.env.NODE_ENV
|
||||
? deprecate(fn, 'express: ' + msg)
|
||||
: fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return ETag for `body`.
|
||||
* Return strong ETag for `body`.
|
||||
*
|
||||
* @param {String|Buffer} body
|
||||
* @param {String} [encoding]
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.etag = function(body){
|
||||
return '"' + crc32.signed(body) + '"';
|
||||
exports.etag = function (body, encoding) {
|
||||
var buf = !Buffer.isBuffer(body)
|
||||
? new Buffer(body, encoding)
|
||||
: body;
|
||||
|
||||
return etag(buf, {weak: false});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return weak ETag for `body`.
|
||||
*
|
||||
* @param {String|Buffer} body
|
||||
* @param {String} [encoding]
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.wetag = function wetag(body, encoding){
|
||||
var buf = !Buffer.isBuffer(body)
|
||||
? new Buffer(body, encoding)
|
||||
: body;
|
||||
|
||||
return etag(buf, {weak: true});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -110,18 +122,8 @@ exports.normalizeTypes = function(types){
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.contentDisposition = function(filename){
|
||||
var ret = 'attachment';
|
||||
if (filename) {
|
||||
filename = basename(filename);
|
||||
// if filename contains non-ascii characters, add a utf-8 version ala RFC 5987
|
||||
ret = /[^\040-\176]/.test(filename)
|
||||
? 'attachment; filename=' + encodeURI(filename) + '; filename*=UTF-8\'\'' + encodeURI(filename)
|
||||
: 'attachment; filename="' + filename + '"';
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
exports.contentDisposition = deprecate.function(contentDisposition,
|
||||
'utils.contentDisposition: use content-disposition npm module instead');
|
||||
|
||||
/**
|
||||
* Parse accept params `str` returning an
|
||||
@@ -148,3 +150,134 @@ function acceptParams(str, index) {
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile "etag" value to function.
|
||||
*
|
||||
* @param {Boolean|String|Function} val
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.compileETag = function(val) {
|
||||
var fn;
|
||||
|
||||
if (typeof val === 'function') {
|
||||
return val;
|
||||
}
|
||||
|
||||
switch (val) {
|
||||
case true:
|
||||
fn = exports.wetag;
|
||||
break;
|
||||
case false:
|
||||
break;
|
||||
case 'strong':
|
||||
fn = exports.etag;
|
||||
break;
|
||||
case 'weak':
|
||||
fn = exports.wetag;
|
||||
break;
|
||||
default:
|
||||
throw new TypeError('unknown value for etag 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.
|
||||
*
|
||||
* @param {Boolean|String|Number|Array|Function} val
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.compileTrust = function(val) {
|
||||
if (typeof val === 'function') return val;
|
||||
|
||||
if (val === true) {
|
||||
// Support plain true/false
|
||||
return function(){ return true };
|
||||
}
|
||||
|
||||
if (typeof val === 'number') {
|
||||
// Support trusting hop count
|
||||
return function(a, i){ return i < val };
|
||||
}
|
||||
|
||||
if (typeof val === 'string') {
|
||||
// Support comma-separated values
|
||||
val = val.split(/ *, */);
|
||||
}
|
||||
|
||||
return proxyaddr.compile(val || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the charset in a given Content-Type string.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} charset
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.setCharset = function(type, charset){
|
||||
if (!type || !charset) return type;
|
||||
|
||||
// parse type
|
||||
var parsed = typer.parse(type);
|
||||
|
||||
// set charset
|
||||
parsed.parameters.charset = charset;
|
||||
|
||||
// format type
|
||||
return typer.format(parsed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return new empty objet.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function newObject() {
|
||||
return {};
|
||||
}
|
||||
|
||||
89
lib/view.js
89
lib/view.js
@@ -2,14 +2,21 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var debug = require('debug')('express:view');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var utils = require('./utils');
|
||||
|
||||
/**
|
||||
* Module variables.
|
||||
* @private
|
||||
*/
|
||||
|
||||
var dirname = path.dirname;
|
||||
var basename = path.basename;
|
||||
var extname = path.extname;
|
||||
var exists = fs.existsSync || path.existsSync;
|
||||
var join = path.join;
|
||||
var resolve = path.resolve;
|
||||
|
||||
/**
|
||||
* Expose `View`.
|
||||
@@ -45,23 +52,32 @@ function View(name, options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup view by the given `path`
|
||||
* Lookup view by the given `name`
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {String} name
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
View.prototype.lookup = function(path){
|
||||
var ext = this.ext;
|
||||
View.prototype.lookup = function lookup(name) {
|
||||
var path;
|
||||
var roots = [].concat(this.root);
|
||||
|
||||
// <path>.<engine>
|
||||
if (!utils.isAbsolute(path)) path = join(this.root, path);
|
||||
if (exists(path)) return path;
|
||||
debug('lookup "%s"', name);
|
||||
|
||||
// <path>/index.<engine>
|
||||
path = join(dirname(path), basename(path, ext), 'index' + ext);
|
||||
if (exists(path)) return path;
|
||||
for (var i = 0; i < roots.length && !path; i++) {
|
||||
var root = roots[i];
|
||||
|
||||
// resolve the path
|
||||
var loc = resolve(root, name);
|
||||
var dir = dirname(loc);
|
||||
var file = basename(loc);
|
||||
|
||||
// resolve the file
|
||||
path = this.resolve(dir, file);
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -72,6 +88,55 @@ View.prototype.lookup = function(path){
|
||||
* @api private
|
||||
*/
|
||||
|
||||
View.prototype.render = function(options, fn){
|
||||
View.prototype.render = function render(options, fn) {
|
||||
debug('render "%s"', this.path);
|
||||
this.engine(this.path, options, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the file within the given directory.
|
||||
*
|
||||
* @param {string} dir
|
||||
* @param {string} file
|
||||
* @private
|
||||
*/
|
||||
|
||||
View.prototype.resolve = function resolve(dir, file) {
|
||||
var ext = this.ext;
|
||||
var path;
|
||||
var stat;
|
||||
|
||||
// <path>.<ext>
|
||||
path = join(dir, file);
|
||||
stat = tryStat(path);
|
||||
|
||||
if (stat && stat.isFile()) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// <path>/index.<ext>
|
||||
path = join(dir, basename(file, ext), 'index' + ext);
|
||||
stat = tryStat(path);
|
||||
|
||||
if (stat && stat.isFile()) {
|
||||
return path;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a stat, maybe.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {fs.Stats}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function tryStat(path) {
|
||||
debug('stat "%s"', path);
|
||||
|
||||
try {
|
||||
return fs.statSync(path);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
140
package.json
140
package.json
@@ -1,74 +1,20 @@
|
||||
{
|
||||
"name": "express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "4.2.0",
|
||||
"description": "Fast, unopinionated, minimalist web framework",
|
||||
"version": "4.10.1",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "TJ Holowaychuk",
|
||||
"email": "tj@vision-media.ca"
|
||||
},
|
||||
{
|
||||
"name": "Aaron Heckmann",
|
||||
"email": "aaron.heckmann+github@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Ciaran Jessup",
|
||||
"email": "ciaranj@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Douglas Christopher Wilson",
|
||||
"email": "doug@somethingdoug.com"
|
||||
},
|
||||
{
|
||||
"name": "Guillermo Rauch",
|
||||
"email": "rauchg@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Ong",
|
||||
"email": "me@jongleberry.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Shtylman",
|
||||
"email": "shtylman+expressjs@gmail.com"
|
||||
}
|
||||
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
|
||||
"Ciaran Jessup <ciaranj@gmail.com>",
|
||||
"Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"Guillermo Rauch <rauchg@gmail.com>",
|
||||
"Jonathan Ong <me@jongleberry.com>",
|
||||
"Roman Shtylman <shtylman+expressjs@gmail.com>",
|
||||
"Young Jae Sim <hanul@hanul.me>"
|
||||
],
|
||||
"dependencies": {
|
||||
"parseurl": "1.0.1",
|
||||
"accepts": "1.0.1",
|
||||
"type-is": "1.1.0",
|
||||
"range-parser": "1.0.0",
|
||||
"cookie": "0.1.2",
|
||||
"buffer-crc32": "0.2.1",
|
||||
"fresh": "0.2.2",
|
||||
"methods": "1.0.0",
|
||||
"send": "0.3.0",
|
||||
"cookie-signature": "1.0.3",
|
||||
"merge-descriptors": "0.0.2",
|
||||
"utils-merge": "1.0.0",
|
||||
"escape-html": "1.0.1",
|
||||
"qs": "0.6.6",
|
||||
"serve-static": "1.1.0",
|
||||
"path-to-regexp": "0.1.2",
|
||||
"debug": "0.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "~1.18.2",
|
||||
"body-parser": "~1.1.2",
|
||||
"connect-redis": "~2.0.0",
|
||||
"ejs": "~1.0.0",
|
||||
"jade": "~0.35.0",
|
||||
"marked": "0.3.2",
|
||||
"multiparty": "~3.2.4",
|
||||
"hjs": "~0.0.6",
|
||||
"should": "~3.3.1",
|
||||
"supertest": "~0.12.0",
|
||||
"method-override": "1.0.0",
|
||||
"cookie-parser": "1.0.1",
|
||||
"express-session": "1.0.4",
|
||||
"morgan": "1.0.1",
|
||||
"vhost": "1.0.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": "strongloop/express",
|
||||
"homepage": "http://expressjs.com/",
|
||||
"keywords": [
|
||||
"express",
|
||||
"framework",
|
||||
@@ -80,13 +26,65 @@
|
||||
"app",
|
||||
"api"
|
||||
],
|
||||
"repository": "git://github.com/visionmedia/express",
|
||||
"scripts": {
|
||||
"prepublish": "npm prune",
|
||||
"test": "make test"
|
||||
"dependencies": {
|
||||
"accepts": "~1.1.2",
|
||||
"content-disposition": "0.5.0",
|
||||
"cookie-signature": "1.0.5",
|
||||
"debug": "~2.1.0",
|
||||
"depd": "~1.0.0",
|
||||
"escape-html": "1.0.1",
|
||||
"etag": "~1.5.0",
|
||||
"finalhandler": "0.3.2",
|
||||
"fresh": "0.2.4",
|
||||
"media-typer": "0.3.0",
|
||||
"methods": "1.1.0",
|
||||
"on-finished": "~2.1.1",
|
||||
"parseurl": "~1.3.0",
|
||||
"path-to-regexp": "0.1.3",
|
||||
"proxy-addr": "~1.0.3",
|
||||
"qs": "2.3.2",
|
||||
"range-parser": "~1.0.2",
|
||||
"send": "0.10.1",
|
||||
"serve-static": "~1.7.1",
|
||||
"type-is": "~1.5.2",
|
||||
"vary": "~1.0.0",
|
||||
"cookie": "0.1.2",
|
||||
"merge-descriptors": "0.0.2",
|
||||
"utils-merge": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"after": "0.8.1",
|
||||
"istanbul": "0.3.2",
|
||||
"mocha": "~2.0.0",
|
||||
"should": "~4.1.0",
|
||||
"supertest": "~0.14.0",
|
||||
"ejs": "~1.0.0",
|
||||
"marked": "0.3.2",
|
||||
"hjs": "~0.0.6",
|
||||
"body-parser": "~1.9.1",
|
||||
"connect-redis": "~2.1.0",
|
||||
"cookie-parser": "~1.3.3",
|
||||
"express-session": "~1.9.1",
|
||||
"jade": "~1.7.0",
|
||||
"method-override": "~2.3.0",
|
||||
"morgan": "~1.4.1",
|
||||
"multiparty": "~4.0.0",
|
||||
"vhost": "~3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"History.md",
|
||||
"Readme.md",
|
||||
"index.js",
|
||||
"lib/"
|
||||
],
|
||||
"scripts": {
|
||||
"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/"
|
||||
}
|
||||
}
|
||||
|
||||
167
test/Route.js
167
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,164 +10,229 @@ 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){
|
||||
throw new Error('boom!');
|
||||
});
|
||||
|
||||
route.get(function(err, req, res, next){
|
||||
throw new Error('oops');
|
||||
});
|
||||
|
||||
route.get(function(err, req, res, next){
|
||||
req.message = err.message;
|
||||
next();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
305
test/Router.js
305
test/Router.js
@@ -1,4 +1,5 @@
|
||||
|
||||
var after = require('after');
|
||||
var express = require('../')
|
||||
, Router = express.Router
|
||||
, methods = require('methods')
|
||||
@@ -42,6 +43,16 @@ describe('Router', function(){
|
||||
router.handle({ url: '/test/route', method: 'GET' }, { end: done });
|
||||
});
|
||||
|
||||
it('should handle blank URL', function(done){
|
||||
var router = new Router();
|
||||
|
||||
router.use(function (req, res) {
|
||||
false.should.be.true;
|
||||
});
|
||||
|
||||
router.handle({ url: '', method: 'GET' }, {}, done);
|
||||
});
|
||||
|
||||
describe('.handle', function(){
|
||||
it('should dispatch', function(done){
|
||||
var router = new Router();
|
||||
@@ -128,7 +139,170 @@ describe('Router', function(){
|
||||
done();
|
||||
});
|
||||
|
||||
router.handle({ url: '/foo/2', method: 'GET' }, {}, done);
|
||||
router.handle({ url: '/foo/2', method: 'GET' }, {}, function() {});
|
||||
});
|
||||
|
||||
it('should handle throwing in handler after async param', function(done) {
|
||||
var router = new Router();
|
||||
|
||||
router.param('user', function(req, res, next, val){
|
||||
process.nextTick(function(){
|
||||
req.user = val;
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
router.use('/:user', function(req, res, next){
|
||||
throw new Error('oh no!');
|
||||
});
|
||||
|
||||
router.use(function(err, req, res, next){
|
||||
assert.equal(err.message, 'oh no!');
|
||||
done();
|
||||
});
|
||||
|
||||
router.handle({ url: '/bob', method: 'GET' }, {}, function() {});
|
||||
});
|
||||
|
||||
it('should handle throwing inside error handlers', function(done) {
|
||||
var router = new Router();
|
||||
|
||||
router.use(function(req, res, next){
|
||||
throw new Error('boom!');
|
||||
});
|
||||
|
||||
router.use(function(err, req, res, next){
|
||||
throw new Error('oops');
|
||||
});
|
||||
|
||||
router.use(function(err, req, res, next){
|
||||
assert.equal(err.message, 'oops');
|
||||
done();
|
||||
});
|
||||
|
||||
router.handle({ url: '/', method: 'GET' }, {}, done);
|
||||
});
|
||||
})
|
||||
|
||||
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 ignore FQDN in path', function (done) {
|
||||
var request = { hit: 0, url: '/proxy/http://example.com/blog/post/1', method: 'GET' };
|
||||
var router = new Router();
|
||||
|
||||
router.use('/proxy', function (req, res, next) {
|
||||
assert.equal(req.hit++, 0);
|
||||
assert.equal(req.url, '/http://example.com/blog/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.handle(request, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
assert.equal(request.hit, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should adjust FQDN req.url', function (done) {
|
||||
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
|
||||
var router = new Router();
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
@@ -150,6 +324,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();
|
||||
@@ -183,5 +394,97 @@ describe('Router', function(){
|
||||
|
||||
router.handle({ url: '/foo/123/bar/baz', method: 'get' }, {}, done);
|
||||
});
|
||||
|
||||
it('should only call once per request', function(done) {
|
||||
var count = 0;
|
||||
var req = { url: '/foo/bob/bar', method: 'get' };
|
||||
var router = new Router();
|
||||
var sub = new Router();
|
||||
|
||||
sub.get('/bar', function(req, res, next) {
|
||||
next();
|
||||
});
|
||||
|
||||
router.param('user', function(req, res, next, user) {
|
||||
count++;
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
|
||||
router.use('/foo/:user/', new Router());
|
||||
router.use('/foo/:user/', sub);
|
||||
|
||||
router.handle(req, {}, function(err) {
|
||||
if (err) return done(err);
|
||||
assert.equal(count, 1);
|
||||
assert.equal(req.user, 'bob');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call when values differ', function(done) {
|
||||
var count = 0;
|
||||
var req = { url: '/foo/bob/bar', method: 'get' };
|
||||
var router = new Router();
|
||||
var sub = new Router();
|
||||
|
||||
sub.get('/bar', function(req, res, next) {
|
||||
next();
|
||||
});
|
||||
|
||||
router.param('user', function(req, res, next, user) {
|
||||
count++;
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
|
||||
router.use('/foo/:user/', new Router());
|
||||
router.use('/:user/bob/', sub);
|
||||
|
||||
router.handle(req, {}, function(err) {
|
||||
if (err) return done(err);
|
||||
assert.equal(count, 2);
|
||||
assert.equal(req.user, 'foo');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parallel requests', function() {
|
||||
it('should not mix requests', function(done) {
|
||||
var req1 = { url: '/foo/50/bar', method: 'get' };
|
||||
var req2 = { url: '/foo/10/bar', method: 'get' };
|
||||
var router = new Router();
|
||||
var sub = new Router();
|
||||
|
||||
done = after(2, done);
|
||||
|
||||
sub.get('/bar', function(req, res, next) {
|
||||
next();
|
||||
});
|
||||
|
||||
router.param('ms', function(req, res, next, ms) {
|
||||
ms = parseInt(ms, 10);
|
||||
req.ms = ms;
|
||||
setTimeout(next, ms);
|
||||
});
|
||||
|
||||
router.use('/foo/:ms/', new Router());
|
||||
router.use('/foo/:ms/', sub);
|
||||
|
||||
router.handle(req1, {}, function(err) {
|
||||
assert.ifError(err);
|
||||
assert.equal(req1.ms, 50);
|
||||
assert.equal(req1.originalUrl, '/foo/50/bar');
|
||||
done();
|
||||
});
|
||||
|
||||
router.handle(req2, {}, function(err) {
|
||||
assert.ifError(err);
|
||||
assert.equal(req2.ms, 10);
|
||||
assert.equal(req2.originalUrl, '/foo/10/bar');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
var app = require('../../examples/auth/app')
|
||||
, request = require('supertest');
|
||||
|
||||
function redirects(to, fn){
|
||||
return function(err, res){
|
||||
res.statusCode.should.equal(302)
|
||||
res.headers.should.have.property('location').match(to);
|
||||
fn()
|
||||
}
|
||||
}
|
||||
var app = require('../../examples/auth')
|
||||
var request = require('supertest')
|
||||
|
||||
function getCookie(res) {
|
||||
return res.headers['set-cookie'][0].split(';')[0];
|
||||
@@ -18,25 +10,93 @@ describe('auth', function(){
|
||||
it('should redirect to /login', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.end(redirects(/login$/, done))
|
||||
.expect('Location', '/login')
|
||||
.expect(302, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /restricted (w/o cookie)',function(){
|
||||
it('should redirect to /login', function(done){
|
||||
describe('GET /login',function(){
|
||||
it('should render login form', function(done){
|
||||
request(app)
|
||||
.get('/restricted')
|
||||
.end(redirects(/login$/,done))
|
||||
.get('/login')
|
||||
.expect(200, /<form/, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /login', function(){
|
||||
it('should fail without proper credentials', function(done){
|
||||
it('should display login error', function(done){
|
||||
request(app)
|
||||
.post('/login')
|
||||
.type('urlencoded')
|
||||
.send('username=not-tj&password=foobar')
|
||||
.end(redirects(/login$/, done))
|
||||
.expect('Location', '/login')
|
||||
.expect(302, function(err, res){
|
||||
if (err) return done(err)
|
||||
request(app)
|
||||
.get('/login')
|
||||
.set('Cookie', getCookie(res))
|
||||
.expect(200, /Authentication failed/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /logout',function(){
|
||||
it('should redirect to /', function(done){
|
||||
request(app)
|
||||
.get('/logout')
|
||||
.expect('Location', '/')
|
||||
.expect(302, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /restricted',function(){
|
||||
it('should redirect to /login without cookie', function(done){
|
||||
request(app)
|
||||
.get('/restricted')
|
||||
.expect('Location', '/login')
|
||||
.expect(302, done)
|
||||
})
|
||||
|
||||
it('should succeed with proper cookie', function(done){
|
||||
request(app)
|
||||
.post('/login')
|
||||
.type('urlencoded')
|
||||
.send('username=tj&password=foobar')
|
||||
.expect('Location', '/')
|
||||
.expect(302, function(err, res){
|
||||
if (err) return done(err)
|
||||
request(app)
|
||||
.get('/restricted')
|
||||
.set('Cookie', getCookie(res))
|
||||
.expect(200, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /login', function(){
|
||||
it('should fail without proper username', function(done){
|
||||
request(app)
|
||||
.post('/login')
|
||||
.type('urlencoded')
|
||||
.send('username=not-tj&password=foobar')
|
||||
.expect('Location', '/login')
|
||||
.expect(302, done)
|
||||
})
|
||||
|
||||
it('should fail without proper password', function(done){
|
||||
request(app)
|
||||
.post('/login')
|
||||
.type('urlencoded')
|
||||
.send('username=tj&password=baz')
|
||||
.expect('Location', '/login')
|
||||
.expect(302, done)
|
||||
})
|
||||
|
||||
it('should succeed with proper credentials', function(done){
|
||||
request(app)
|
||||
.post('/login')
|
||||
.type('urlencoded')
|
||||
.send('username=tj&password=foobar')
|
||||
.expect('Location', '/')
|
||||
.expect(302, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,16 +7,43 @@ describe('content-negotiation', function(){
|
||||
it('should default to text/html', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>')
|
||||
.end(done);
|
||||
.expect(200, '<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>', done)
|
||||
})
|
||||
|
||||
it('should accept to text/plain', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept', 'text/plain')
|
||||
.expect(' - Tobi\n - Loki\n - Jane\n')
|
||||
.end(done);
|
||||
.expect(200, ' - Tobi\n - Loki\n - Jane\n', done)
|
||||
})
|
||||
|
||||
it('should accept to application/json', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200, '[{"name":"Tobi"},{"name":"Loki"},{"name":"Jane"}]', done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users', function(){
|
||||
it('should default to text/html', function(done){
|
||||
request(app)
|
||||
.get('/users')
|
||||
.expect(200, '<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>', done)
|
||||
})
|
||||
|
||||
it('should accept to text/plain', function(done){
|
||||
request(app)
|
||||
.get('/users')
|
||||
.set('Accept', 'text/plain')
|
||||
.expect(200, ' - Tobi\n - Loki\n - Jane\n', done)
|
||||
})
|
||||
|
||||
it('should accept to application/json', function(done){
|
||||
request(app)
|
||||
.get('/users')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200, '[{"name":"Tobi"},{"name":"Loki"},{"name":"Jane"}]', done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
var app = require('../../examples/cookies/app')
|
||||
var app = require('../../examples/cookies')
|
||||
, request = require('supertest');
|
||||
|
||||
describe('cookies', function(){
|
||||
@@ -18,17 +18,59 @@ describe('cookies', function(){
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Cookie', res.headers['set-cookie'][0])
|
||||
.expect(200, /Remembered/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /forget', 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)
|
||||
request(app)
|
||||
.get('/forget')
|
||||
.set('Cookie', res.headers['set-cookie'][0])
|
||||
.expect('Set-Cookie', /remember=;/)
|
||||
.expect(302, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /', function(){
|
||||
it('should set a cookie', function(done){
|
||||
request(app)
|
||||
.post('/')
|
||||
.type('urlencoded')
|
||||
.send({ remember: 1 })
|
||||
.end(function(err, res){
|
||||
.expect(302, function(err, res){
|
||||
res.headers.should.have.property('set-cookie')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should no set cookie w/o reminder', function(done){
|
||||
request(app)
|
||||
.post('/')
|
||||
.send({})
|
||||
.expect(302, function(err, res){
|
||||
res.headers.should.not.have.property('set-cookie')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
var app = require('../../examples/downloads/app')
|
||||
var app = require('../../examples/downloads')
|
||||
, request = require('supertest');
|
||||
|
||||
describe('downloads', function(){
|
||||
@@ -30,4 +30,4 @@ describe('downloads', function(){
|
||||
.expect(404, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,14 +7,11 @@ describe('ejs', function(){
|
||||
it('should respond with html', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.end(function(err, res){
|
||||
res.should.have.status(200);
|
||||
res.should.have.header('Content-Type', 'text/html; charset=utf-8');
|
||||
res.text.should.include('<li>tobi <tobi@learnboost.com></li>');
|
||||
res.text.should.include('<li>loki <loki@learnboost.com></li>');
|
||||
res.text.should.include('<li>jane <jane@learnboost.com></li>');
|
||||
done();
|
||||
});
|
||||
.expect('Content-Type', 'text/html; charset=utf-8')
|
||||
.expect(/<li>tobi <tobi@learnboost\.com><\/li>/)
|
||||
.expect(/<li>loki <loki@learnboost\.com><\/li>/)
|
||||
.expect(/<li>jane <jane@learnboost\.com><\/li>/)
|
||||
.expect(200, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
var app = require('../../examples/markdown')
|
||||
, request = require('supertest');
|
||||
var request = require('supertest')
|
||||
|
||||
describe('markdown', function(){
|
||||
describe('GET /', function(){
|
||||
@@ -18,4 +18,4 @@ describe('markdown', function(){
|
||||
.expect(500,done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,10 +7,39 @@ describe('mvc', function(){
|
||||
it('should redirect to /users', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect('Location', '/users')
|
||||
.expect(302, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /pet/0', function(){
|
||||
it('should get pet', function(done){
|
||||
request(app)
|
||||
.get('/pet/0')
|
||||
.expect(200, /Tobi/, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /pet/0/edit', function(){
|
||||
it('should get pet edit page', function(done){
|
||||
request(app)
|
||||
.get('/pet/0/edit')
|
||||
.expect(/<form/)
|
||||
.expect(200, /Tobi/, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /pet/2', 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){
|
||||
res.should.have.status(302);
|
||||
res.headers.location.should.include('/users');
|
||||
done();
|
||||
if (err) return done(err);
|
||||
request(app)
|
||||
.get('/pet/3/edit')
|
||||
.expect(200, /Boots/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -19,13 +48,11 @@ describe('mvc', function(){
|
||||
it('should display a list of users', function(done){
|
||||
request(app)
|
||||
.get('/users')
|
||||
.end(function(err, res){
|
||||
res.text.should.include('<h1>Users</h1>');
|
||||
res.text.should.include('>TJ<');
|
||||
res.text.should.include('>Guillermo<');
|
||||
res.text.should.include('>Nathan<');
|
||||
done();
|
||||
})
|
||||
.expect(/<h1>Users<\/h1>/)
|
||||
.expect(/>TJ</)
|
||||
.expect(/>Guillermo</)
|
||||
.expect(/>Nathan</)
|
||||
.expect(200, done)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,21 +61,16 @@ describe('mvc', function(){
|
||||
it('should display the user', function(done){
|
||||
request(app)
|
||||
.get('/user/0')
|
||||
.end(function(err, res){
|
||||
res.text.should.include('<h1>TJ <a href="/user/0/edit">edit');
|
||||
done();
|
||||
})
|
||||
.expect(200, /<h1>TJ <a href="\/user\/0\/edit">edit/, done)
|
||||
})
|
||||
|
||||
it('should display the users pets', function(done){
|
||||
request(app)
|
||||
.get('/user/0')
|
||||
.end(function(err, res){
|
||||
res.text.should.include('/pet/0">Tobi');
|
||||
res.text.should.include('/pet/1">Loki');
|
||||
res.text.should.include('/pet/2">Jane');
|
||||
done();
|
||||
})
|
||||
.expect(/\/pet\/0">Tobi/)
|
||||
.expect(/\/pet\/1">Loki/)
|
||||
.expect(/\/pet\/2">Jane/)
|
||||
.expect(200, done)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,27 +87,46 @@ describe('mvc', function(){
|
||||
it('should display the edit form', function(done){
|
||||
request(app)
|
||||
.get('/user/1/edit')
|
||||
.end(function(err, res){
|
||||
res.text.should.include('<h1>Guillermo</h1>');
|
||||
res.text.should.include('value="put"');
|
||||
done();
|
||||
})
|
||||
.expect(/Guillermo/)
|
||||
.expect(200, /<form/, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /user/:id', function(){
|
||||
it('should 500 on error', function(done){
|
||||
request(app)
|
||||
.put('/user/1')
|
||||
.send({})
|
||||
.expect(500, done)
|
||||
})
|
||||
|
||||
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);
|
||||
request(app)
|
||||
.get('/user/1/edit')
|
||||
.end(function(err, res){
|
||||
res.text.should.include('<h1>Tobo</h1>');
|
||||
done();
|
||||
})
|
||||
.expect(200, /Tobo/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /user/:id/pet', 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){
|
||||
if (err) return done(err)
|
||||
request(app)
|
||||
.get('/user/2')
|
||||
.expect(200, /Snickers/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var app = require('../../examples/params/app')
|
||||
, request = require('supertest');
|
||||
var app = require('../../examples/params')
|
||||
var request = require('supertest')
|
||||
|
||||
describe('params', function(){
|
||||
describe('GET /', function(){
|
||||
@@ -18,6 +18,14 @@ describe('params', function(){
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /user/9', function(){
|
||||
it('should fail to find user', function(done){
|
||||
request(app)
|
||||
.get('/user/9')
|
||||
.expect(/failed to find user/,done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users/0-2', function(){
|
||||
it('should respond with three users', function(done){
|
||||
request(app)
|
||||
@@ -25,4 +33,12 @@ describe('params', function(){
|
||||
.expect(/users tj, tobi/,done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users/foo-bar', function(){
|
||||
it('should fail integer parsing', function(done){
|
||||
request(app)
|
||||
.get('/users/foo-bar')
|
||||
.expect(/failed to parseInt foo/,done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var app = require('../../examples/resource/app')
|
||||
, request = require('supertest');
|
||||
var app = require('../../examples/resource')
|
||||
var request = require('supertest')
|
||||
|
||||
describe('resource', function(){
|
||||
describe('GET /', function(){
|
||||
@@ -26,6 +26,14 @@ describe('resource', function(){
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users/9', function(){
|
||||
it('should respond with error', function(done){
|
||||
request(app)
|
||||
.get('/users/9')
|
||||
.expect('{"error":"Cannot find user"}', done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users/1..3', function(){
|
||||
it('should respond with users 1 through 3', function(done){
|
||||
request(app)
|
||||
@@ -35,13 +43,21 @@ describe('resource', function(){
|
||||
})
|
||||
|
||||
describe('DELETE /users/1', function(){
|
||||
it('should respond with users 1 through 3', function(done){
|
||||
it('should delete user 1', function(done){
|
||||
request(app)
|
||||
.del('/users/1')
|
||||
.expect(/^destroyed/,done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('DELETE /users/9', function(){
|
||||
it('should fail', function(done){
|
||||
request(app)
|
||||
.del('/users/9')
|
||||
.expect('Cannot find user', done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users/1..3.json', function(){
|
||||
it('should respond with users 2 and 3 as json', function(done){
|
||||
request(app)
|
||||
@@ -49,4 +65,4 @@ describe('resource', function(){
|
||||
.expect(/^\[null,{"name":"aaron"},{"name":"guillermo"}\]/,done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
46
test/acceptance/vhost.js
Normal file
46
test/acceptance/vhost.js
Normal file
@@ -0,0 +1,46 @@
|
||||
var app = require('../../examples/vhost')
|
||||
var request = require('supertest')
|
||||
|
||||
describe('vhost', function(){
|
||||
describe('example.com', function(){
|
||||
describe('GET /', function(){
|
||||
it('should say hello', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'example.com')
|
||||
.expect(200, /hello/i, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /foo', function(){
|
||||
it('should say foo', function(done){
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.set('Host', 'example.com')
|
||||
.expect(200, 'requested foo', done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('foo.example.com', function(){
|
||||
describe('GET /', function(){
|
||||
it('should redirect to /foo', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'foo.example.com')
|
||||
.expect(302, /Redirecting to http:\/\/example.com:3000\/foo/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('bar.example.com', function(){
|
||||
describe('GET /', function(){
|
||||
it('should redirect to /bar', function(done){
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'bar.example.com')
|
||||
.expect(302, /Redirecting to http:\/\/example.com:3000\/bar/, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -24,11 +24,72 @@ describe('web-service', function(){
|
||||
it('should respond users json', function(done){
|
||||
request(app)
|
||||
.get('/api/users?api-key=foo')
|
||||
.end(function(err, res){
|
||||
res.should.be.json;
|
||||
res.text.should.equal('[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]');
|
||||
done();
|
||||
});
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(200, '[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]', done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/repos', function(){
|
||||
describe('without an api key', function(){
|
||||
it('should respond with 400 bad request', function(done){
|
||||
request(app)
|
||||
.get('/api/repos')
|
||||
.expect(400, done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('with an invalid api key', function(){
|
||||
it('should respond with 401 unauthorized', function(done){
|
||||
request(app)
|
||||
.get('/api/repos?api-key=rawr')
|
||||
.expect(401, done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a valid api key', function(){
|
||||
it('should respond repos json', function(done){
|
||||
request(app)
|
||||
.get('/api/repos?api-key=foo')
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(/"name":"express"/)
|
||||
.expect(/"url":"http:\/\/github.com\/strongloop\/express"/)
|
||||
.expect(200, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/user/:name/repos', function(){
|
||||
describe('without an api key', function(){
|
||||
it('should respond with 400 bad request', function(done){
|
||||
request(app)
|
||||
.get('/api/user/loki/repos')
|
||||
.expect(400, done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('with an invalid api key', function(){
|
||||
it('should respond with 401 unauthorized', function(done){
|
||||
request(app)
|
||||
.get('/api/user/loki/repos?api-key=rawr')
|
||||
.expect(401, done);
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a valid api key', function(){
|
||||
it('should respond user repos json', function(done){
|
||||
request(app)
|
||||
.get('/api/user/loki/repos?api-key=foo')
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
.expect(/"name":"stylus"/)
|
||||
.expect(/"url":"http:\/\/github.com\/learnboost\/stylus"/)
|
||||
.expect(200, done)
|
||||
})
|
||||
|
||||
it('should 404 with unknown user', function(done){
|
||||
request(app)
|
||||
.get('/api/user/bob/repos?api-key=foo')
|
||||
.expect(404, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -37,12 +98,8 @@ describe('web-service', function(){
|
||||
it('should respond with 404 json', function(done){
|
||||
request(app)
|
||||
.get('/api/something?api-key=bar')
|
||||
.end(function(err, res){
|
||||
res.should.have.status(404);
|
||||
res.should.be.json;
|
||||
res.text.should.equal('{"error":"Lame, can\'t find that"}');
|
||||
done();
|
||||
});
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(404, '{"error":"Lame, can\'t find that"}', 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();
|
||||
});
|
||||
|
||||
38
test/app.js
38
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('/');
|
||||
})
|
||||
})
|
||||
|
||||
@@ -84,3 +99,12 @@ describe('in production', function(){
|
||||
process.env.NODE_ENV = 'test';
|
||||
})
|
||||
})
|
||||
|
||||
describe('without NODE_ENV', function(){
|
||||
it('should default to development', function(){
|
||||
process.env.NODE_ENV = '';
|
||||
var app = express();
|
||||
app.get('env').should.equal('development');
|
||||
process.env.NODE_ENV = 'test';
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ describe('app', function(){
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
it('should fail if not given fn', function(){
|
||||
var app = express();
|
||||
app.param.bind(app, ':name', 'bob').should.throw();
|
||||
})
|
||||
})
|
||||
|
||||
describe('.param(names, fn)', function(){
|
||||
@@ -95,5 +100,208 @@ describe('app', function(){
|
||||
.get('/user/123')
|
||||
.expect('123', done);
|
||||
})
|
||||
|
||||
it('should only call once per request', function(done) {
|
||||
var app = express();
|
||||
var called = 0;
|
||||
var count = 0;
|
||||
|
||||
app.param('user', function(req, res, next, user) {
|
||||
called++;
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/foo/:user', function(req, res, next) {
|
||||
count++;
|
||||
next();
|
||||
});
|
||||
app.get('/foo/:user', function(req, res, next) {
|
||||
count++;
|
||||
next();
|
||||
});
|
||||
app.use(function(req, res) {
|
||||
res.end([count, called, req.user].join(' '));
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/foo/bob')
|
||||
.expect('2 1 bob', done);
|
||||
})
|
||||
|
||||
it('should call when values differ', function(done) {
|
||||
var app = express();
|
||||
var called = 0;
|
||||
var count = 0;
|
||||
|
||||
app.param('user', function(req, res, next, user) {
|
||||
called++;
|
||||
req.users = (req.users || []).concat(user);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/:user/bob', function(req, res, next) {
|
||||
count++;
|
||||
next();
|
||||
});
|
||||
app.get('/foo/:user', function(req, res, next) {
|
||||
count++;
|
||||
next();
|
||||
});
|
||||
app.use(function(req, res) {
|
||||
res.end([count, called, req.users.join(',')].join(' '));
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/foo/bob')
|
||||
.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();
|
||||
|
||||
app.param('name', function(req, res, next, name){
|
||||
req.params.name = name;
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/:name', function(req, res){
|
||||
var name = req.params.name;
|
||||
res.send('' + name);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/foo%25bar')
|
||||
.expect('foo%bar', done);
|
||||
})
|
||||
|
||||
it('should catch thrown error', function(done){
|
||||
var app = express();
|
||||
|
||||
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 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();
|
||||
|
||||
app.param('id', function(req, res, next, id){
|
||||
next('route');
|
||||
});
|
||||
|
||||
app.get('/user/:id', function(req, res){
|
||||
var id = req.params.id;
|
||||
res.send('' + id);
|
||||
});
|
||||
|
||||
app.get('/:name/123', function(req, res){
|
||||
res.send('name');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/123')
|
||||
.expect('name', done);
|
||||
})
|
||||
|
||||
it('should defer all the param routes', function(done){
|
||||
var app = express();
|
||||
|
||||
app.param('id', function(req, res, next, val){
|
||||
if (val === 'new') return next('route');
|
||||
return next();
|
||||
});
|
||||
|
||||
app.all('/user/:id', function(req, res){
|
||||
res.send('all.id');
|
||||
});
|
||||
|
||||
app.get('/user/:id', function(req, res){
|
||||
res.send('get.id');
|
||||
});
|
||||
|
||||
app.get('/user/new', function(req, res){
|
||||
res.send('get.new');
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/new')
|
||||
.expect('get.new', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,6 +54,27 @@ describe('app', function(){
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle render error throws', function(done){
|
||||
var app = express();
|
||||
|
||||
function View(name, options){
|
||||
this.name = name;
|
||||
this.path = 'fale';
|
||||
}
|
||||
|
||||
View.prototype.render = function(options, fn){
|
||||
throw new Error('err!');
|
||||
};
|
||||
|
||||
app.set('view', View);
|
||||
|
||||
app.render('something', function(err, str){
|
||||
err.should.be.ok;
|
||||
err.message.should.equal('err!');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the file does not exist', function(){
|
||||
it('should provide a helpful error', function(done){
|
||||
var app = express();
|
||||
@@ -110,6 +131,64 @@ describe('app', function(){
|
||||
})
|
||||
})
|
||||
|
||||
describe('when "views" is given', function(){
|
||||
it('should lookup the file in the path', function(done){
|
||||
var app = express();
|
||||
|
||||
app.set('views', __dirname + '/fixtures/default_layout');
|
||||
app.locals.user = { name: 'tobi' };
|
||||
|
||||
app.render('user.jade', function(err, str){
|
||||
if (err) return done(err);
|
||||
str.should.equal('<p>tobi</p>');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
describe('when array of paths', function(){
|
||||
it('should lookup the file in the path', function(done){
|
||||
var app = express();
|
||||
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
|
||||
|
||||
app.set('views', views);
|
||||
app.locals.user = { name: 'tobi' };
|
||||
|
||||
app.render('user.jade', function(err, str){
|
||||
if (err) return done(err);
|
||||
str.should.equal('<span>tobi</span>');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('should lookup in later paths until found', function(done){
|
||||
var app = express();
|
||||
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
|
||||
|
||||
app.set('views', views);
|
||||
app.locals.name = 'tobi';
|
||||
|
||||
app.render('name.jade', function(err, str){
|
||||
if (err) return done(err);
|
||||
str.should.equal('<p>tobi</p>');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('should error if file does not exist', function(done){
|
||||
var app = express();
|
||||
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
|
||||
|
||||
app.set('views', views);
|
||||
app.locals.name = 'tobi';
|
||||
|
||||
app.render('pet.jade', function(err, str){
|
||||
err.message.should.equal('Failed to lookup view "pet.jade" in views directories "' + __dirname + '/fixtures/local_layout" or "' + __dirname + '/fixtures/default_layout"');
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a "view" constructor is given', function(){
|
||||
it('should create an instance of it', function(done){
|
||||
var app = express();
|
||||
@@ -132,6 +211,68 @@ describe('app', function(){
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('caching', function(){
|
||||
it('should always lookup view without cache', function(done){
|
||||
var app = express();
|
||||
var count = 0;
|
||||
|
||||
function View(name, options){
|
||||
this.name = name;
|
||||
this.path = 'fake';
|
||||
count++;
|
||||
}
|
||||
|
||||
View.prototype.render = function(options, fn){
|
||||
fn(null, 'abstract engine');
|
||||
};
|
||||
|
||||
app.set('view cache', false);
|
||||
app.set('view', View);
|
||||
|
||||
app.render('something', function(err, str){
|
||||
if (err) return done(err);
|
||||
count.should.equal(1);
|
||||
str.should.equal('abstract engine');
|
||||
app.render('something', function(err, str){
|
||||
if (err) return done(err);
|
||||
count.should.equal(2);
|
||||
str.should.equal('abstract engine');
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should cache with "view cache" setting', function(done){
|
||||
var app = express();
|
||||
var count = 0;
|
||||
|
||||
function View(name, options){
|
||||
this.name = name;
|
||||
this.path = 'fake';
|
||||
count++;
|
||||
}
|
||||
|
||||
View.prototype.render = function(options, fn){
|
||||
fn(null, 'abstract engine');
|
||||
};
|
||||
|
||||
app.set('view cache', true);
|
||||
app.set('view', View);
|
||||
|
||||
app.render('something', function(err, str){
|
||||
if (err) return done(err);
|
||||
count.should.equal(1);
|
||||
str.should.equal('abstract engine');
|
||||
app.render('something', function(err, str){
|
||||
if (err) return done(err);
|
||||
count.should.equal(1);
|
||||
str.should.equal('abstract engine');
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.render(name, options, fn)', function(){
|
||||
@@ -175,5 +316,37 @@ describe('app', function(){
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
describe('caching', function(){
|
||||
it('should cache with cache option', function(done){
|
||||
var app = express();
|
||||
var count = 0;
|
||||
|
||||
function View(name, options){
|
||||
this.name = name;
|
||||
this.path = 'fake';
|
||||
count++;
|
||||
}
|
||||
|
||||
View.prototype.render = function(options, fn){
|
||||
fn(null, 'abstract engine');
|
||||
};
|
||||
|
||||
app.set('view cache', false);
|
||||
app.set('view', View);
|
||||
|
||||
app.render('something', {cache: true}, function(err, str){
|
||||
if (err) return done(err);
|
||||
count.should.equal(1);
|
||||
str.should.equal('abstract engine');
|
||||
app.render('something', {cache: true}, function(err, str){
|
||||
if (err) return done(err);
|
||||
count.should.equal(1);
|
||||
str.should.equal('abstract engine');
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -49,4 +49,14 @@ describe('app.route', function(){
|
||||
.get('/test')
|
||||
.expect('test', done);
|
||||
});
|
||||
|
||||
it('should not error on empty routes', function(done){
|
||||
var app = express();
|
||||
|
||||
app.route('/:foo');
|
||||
|
||||
request(app)
|
||||
.get('/test')
|
||||
.expect(404, done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,6 +53,40 @@ describe('app.router', function(){
|
||||
[method]('/foo')
|
||||
.expect('head' == method ? '' : method, done);
|
||||
})
|
||||
|
||||
it('should reject numbers for app.' + method, function(){
|
||||
var app = express();
|
||||
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);
|
||||
});
|
||||
})
|
||||
|
||||
@@ -178,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();
|
||||
@@ -206,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();
|
||||
|
||||
@@ -220,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();
|
||||
|
||||
@@ -386,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){
|
||||
@@ -435,6 +679,23 @@ describe('app.router', function(){
|
||||
.get('/user/tj/edit')
|
||||
.expect('editing tj', done);
|
||||
})
|
||||
|
||||
it('should work in array of paths', function(done){
|
||||
var app = express();
|
||||
var cb = after(2, done);
|
||||
|
||||
app.get(['/user/:user/poke', '/user/:user/pokes'], function(req, res){
|
||||
res.end('poking ' + req.params.user);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/tj/poke')
|
||||
.expect('poking tj', cb);
|
||||
|
||||
request(app)
|
||||
.get('/user/tj/pokes')
|
||||
.expect('poking tj', cb);
|
||||
})
|
||||
})
|
||||
|
||||
describe(':name?', function(){
|
||||
@@ -534,6 +795,30 @@ describe('app.router', function(){
|
||||
})
|
||||
})
|
||||
|
||||
describe('when next("route") is called', function(){
|
||||
it('should jump to next route', function(done){
|
||||
var app = express()
|
||||
|
||||
function fn(req, res, next){
|
||||
res.set('X-Hit', '1')
|
||||
next('route')
|
||||
}
|
||||
|
||||
app.get('/foo', fn, function(req, res, next){
|
||||
res.end('failure')
|
||||
});
|
||||
|
||||
app.get('/foo', function(req, res){
|
||||
res.end('success')
|
||||
})
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('X-Hit', '1')
|
||||
.expect(200, 'success', done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when next(err) is called', function(){
|
||||
it('should break out of app.router', function(done){
|
||||
var app = express()
|
||||
@@ -568,6 +853,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){
|
||||
@@ -588,6 +899,45 @@ describe('app.router', function(){
|
||||
.expect('editing user 12', done);
|
||||
})
|
||||
|
||||
it('should run in order added', function(done){
|
||||
var app = express();
|
||||
var path = [];
|
||||
|
||||
app.get('*', function(req, res, next){
|
||||
path.push(0);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
path.push(1);
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(function(req, res, next){
|
||||
path.push(2);
|
||||
next();
|
||||
});
|
||||
|
||||
app.all('/user/:id', function(req, res, next){
|
||||
path.push(3);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('*', function(req, res, next){
|
||||
path.push(4);
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(function(req, res, next){
|
||||
path.push(5);
|
||||
res.end(path.join(','))
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/user/1')
|
||||
.expect(200, '0,1,2,3,4,5', done);
|
||||
})
|
||||
|
||||
it('should be chainable', function(){
|
||||
var app = express();
|
||||
app.get('/', function(){}).should.equal(app);
|
||||
|
||||
449
test/app.use.js
449
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){
|
||||
@@ -78,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);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,6 +13,29 @@ describe('config', function(){
|
||||
var app = express();
|
||||
app.set('foo', undefined).should.equal(app);
|
||||
})
|
||||
|
||||
describe('"etag"', function(){
|
||||
it('should throw on bad value', function(){
|
||||
var app = express()
|
||||
app.set.bind(app, 'etag', 42).should.throw(/unknown value/)
|
||||
})
|
||||
|
||||
it('should set "etag fn"', function(){
|
||||
var app = express()
|
||||
var fn = function(){}
|
||||
app.set('etag', fn)
|
||||
app.get('etag fn').should.equal(fn)
|
||||
})
|
||||
})
|
||||
|
||||
describe('"trust proxy"', function(){
|
||||
it('should set "trust proxy fn"', function(){
|
||||
var app = express()
|
||||
var fn = function(){}
|
||||
app.set('trust proxy', fn)
|
||||
app.get('trust proxy fn').should.equal(fn)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.get()', function(){
|
||||
@@ -91,4 +114,4 @@ describe('config', function(){
|
||||
app.disabled('foo').should.be.false;
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
var express = require('../');
|
||||
var request = require('supertest');
|
||||
var assert = require('assert');
|
||||
var should = require('should');
|
||||
|
||||
describe('exports', function(){
|
||||
it('should expose Router', function(){
|
||||
@@ -50,4 +50,12 @@ describe('exports', function(){
|
||||
.get('/')
|
||||
.expect('bar', done);
|
||||
})
|
||||
|
||||
it('should throw on old middlewares', function(){
|
||||
var error;
|
||||
try { express.bodyParser; } catch (e) { error = e; }
|
||||
should(error).have.property('message');
|
||||
error.message.should.containEql('middleware');
|
||||
error.message.should.containEql('bodyParser');
|
||||
})
|
||||
})
|
||||
|
||||
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>
|
||||
1
test/fixtures/default_layout/name.jade
vendored
Normal file
1
test/fixtures/default_layout/name.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
p= name
|
||||
1
test/fixtures/default_layout/user.jade
vendored
Normal file
1
test/fixtures/default_layout/user.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
p= user.name
|
||||
1
test/fixtures/local_layout/user.jade
vendored
Normal file
1
test/fixtures/local_layout/user.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
span= user.name
|
||||
@@ -41,4 +41,4 @@ describe('middleware', function(){
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
36
test/req.acceptsEncoding.js
Normal file
36
test/req.acceptsEncoding.js
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
var express = require('../')
|
||||
, request = require('supertest');
|
||||
|
||||
describe('req', function(){
|
||||
describe('.acceptsEncoding', function(){
|
||||
it('should be true if encoding accpeted', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.acceptsEncoding('gzip').should.be.ok;
|
||||
req.acceptsEncoding('deflate').should.be.ok;
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept-Encoding', ' gzip, deflate')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should be false if encoding not accpeted', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.acceptsEncoding('bogus').should.not.be.ok;
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept-Encoding', ' gzip, deflate')
|
||||
.expect(200, done);
|
||||
})
|
||||
})
|
||||
})
|
||||
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.acceptsLanguage.js
Normal file
53
test/req.acceptsLanguage.js
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
var express = require('../')
|
||||
, request = require('supertest');
|
||||
|
||||
describe('req', function(){
|
||||
describe('.acceptsLanguage', function(){
|
||||
it('should be true if language accpeted', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.acceptsLanguage('en-us').should.be.ok;
|
||||
req.acceptsLanguage('en').should.be.ok;
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept-Language', 'en;q=.5, en-us')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
it('should be false if language not accpeted', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.acceptsLanguage('es').should.not.be.ok;
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Accept-Language', 'en;q=.5, en-us')
|
||||
.expect(200, done);
|
||||
})
|
||||
|
||||
describe('when Accept-Language is not present', function(){
|
||||
it('should always return true', function(done){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
req.acceptsLanguage('en').should.be.ok;
|
||||
req.acceptsLanguage('es').should.be.ok;
|
||||
req.acceptsLanguage('jp').should.be.ok;
|
||||
res.end();
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.expect(200, done);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
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);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
87
test/req.baseUrl.js
Normal file
87
test/req.baseUrl.js
Normal file
@@ -0,0 +1,87 @@
|
||||
|
||||
var express = require('..')
|
||||
var request = require('supertest')
|
||||
|
||||
describe('req', function(){
|
||||
describe('.baseUrl', function(){
|
||||
it('should be empty for top-level route', function(done){
|
||||
var app = express()
|
||||
|
||||
app.get('/:a', function(req, res){
|
||||
res.end(req.baseUrl)
|
||||
})
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect(200, '', done)
|
||||
})
|
||||
|
||||
it('should contain lower path', function(done){
|
||||
var app = express()
|
||||
var sub = express.Router()
|
||||
|
||||
sub.get('/:b', function(req, res){
|
||||
res.end(req.baseUrl)
|
||||
})
|
||||
app.use('/:a', sub)
|
||||
|
||||
request(app)
|
||||
.get('/foo/bar')
|
||||
.expect(200, '/foo', done);
|
||||
})
|
||||
|
||||
it('should contain full lower path', function(done){
|
||||
var app = express()
|
||||
var sub1 = express.Router()
|
||||
var sub2 = express.Router()
|
||||
var sub3 = express.Router()
|
||||
|
||||
sub3.get('/:d', function(req, res){
|
||||
res.end(req.baseUrl)
|
||||
})
|
||||
sub2.use('/:c', sub3)
|
||||
sub1.use('/:b', sub2)
|
||||
app.use('/:a', sub1)
|
||||
|
||||
request(app)
|
||||
.get('/foo/bar/baz/zed')
|
||||
.expect(200, '/foo/bar/baz', done);
|
||||
})
|
||||
|
||||
it('should travel through routers correctly', function(done){
|
||||
var urls = []
|
||||
var app = express()
|
||||
var sub1 = express.Router()
|
||||
var sub2 = express.Router()
|
||||
var sub3 = express.Router()
|
||||
|
||||
sub3.get('/:d', function(req, res, next){
|
||||
urls.push('0@' + req.baseUrl)
|
||||
next()
|
||||
})
|
||||
sub2.use('/:c', sub3)
|
||||
sub1.use('/', function(req, res, next){
|
||||
urls.push('1@' + req.baseUrl)
|
||||
next()
|
||||
})
|
||||
sub1.use('/bar', sub2)
|
||||
sub1.use('/bar', function(req, res, next){
|
||||
urls.push('2@' + req.baseUrl)
|
||||
next()
|
||||
})
|
||||
app.use(function(req, res, next){
|
||||
urls.push('3@' + req.baseUrl)
|
||||
next()
|
||||
})
|
||||
app.use('/:a', sub1)
|
||||
app.use(function(req, res, next){
|
||||
urls.push('4@' + req.baseUrl)
|
||||
res.end(urls.join(','))
|
||||
})
|
||||
|
||||
request(app)
|
||||
.get('/foo/bar/baz/zed')
|
||||
.expect(200, '3@,1@/foo,0@/foo/bar/baz,2@/foo/bar,4@', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -6,15 +6,16 @@ describe('req', function(){
|
||||
describe('.fresh', function(){
|
||||
it('should return true when the resource is not modified', function(done){
|
||||
var app = express();
|
||||
var etag = '"12345"';
|
||||
|
||||
app.use(function(req, res){
|
||||
res.set('ETag', '12345');
|
||||
res.set('ETag', etag);
|
||||
res.send(req.fresh);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('If-None-Match', '12345')
|
||||
.set('If-None-Match', etag)
|
||||
.expect(304, done);
|
||||
})
|
||||
|
||||
@@ -22,14 +23,14 @@ describe('req', function(){
|
||||
var app = express();
|
||||
|
||||
app.use(function(req, res){
|
||||
res.set('ETag', '123');
|
||||
res.set('ETag', '"123"');
|
||||
res.send(req.fresh);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('If-None-Match', '12345')
|
||||
.expect('false', done);
|
||||
.set('If-None-Match', '"12345"')
|
||||
.expect(200, 'false', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -69,5 +69,70 @@ describe('req', function(){
|
||||
.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.host);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'localhost')
|
||||
.set('X-Forwarded-Host', 'example.com')
|
||||
.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.host);
|
||||
});
|
||||
|
||||
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.host);
|
||||
});
|
||||
|
||||
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.host);
|
||||
});
|
||||
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Host', 'localhost')
|
||||
.set('X-Forwarded-Host', 'evil')
|
||||
.expect('localhost', done);
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user