mirror of
https://github.com/expressjs/express.git
synced 2026-02-26 18:57:43 +00:00
Compare commits
591 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
cf709f3021 | ||
|
|
7515ee6a78 | ||
|
|
b8d6d258b0 | ||
|
|
35c50601bd | ||
|
|
f7be983a77 | ||
|
|
bc9bcb0317 | ||
|
|
a2553126dd | ||
|
|
0639c45acd | ||
|
|
e4302b2120 | ||
|
|
3d6b4ba013 | ||
|
|
920f46ad65 | ||
|
|
35a66d8a14 | ||
|
|
4e1e252e17 | ||
|
|
0461e55380 | ||
|
|
8dc4ff26f9 | ||
|
|
6f9e927633 | ||
|
|
40a43eb753 | ||
|
|
a8bb4bab2b | ||
|
|
417999884b | ||
|
|
17a739e35f | ||
|
|
555ffe37b2 | ||
|
|
165578a1da | ||
|
|
0bbbc84959 | ||
|
|
92d37671c5 | ||
|
|
2901bd6916 | ||
|
|
66b38b58bc | ||
|
|
dc31ea34b8 | ||
|
|
d58ca520c8 | ||
|
|
4b646c2f8d | ||
|
|
35757ed1f5 | ||
|
|
fed0d5df5c | ||
|
|
c99fa6a192 | ||
|
|
0d468cbe6c | ||
|
|
f92ba6d0cf | ||
|
|
0408e3727e | ||
|
|
be997fd654 | ||
|
|
5c3852b91c | ||
|
|
3c7310ebcb | ||
|
|
61f2929a35 | ||
|
|
c054581370 | ||
|
|
dbea8312bb | ||
|
|
1536a2196e | ||
|
|
374e6c3789 | ||
|
|
203fb05d3e | ||
|
|
ce8555c690 | ||
|
|
9863fa0903 | ||
|
|
2fd3e72a19 | ||
|
|
8ccceacf91 | ||
|
|
d5815922ca | ||
|
|
972c01afc9 | ||
|
|
8894e30869 | ||
|
|
5d8dba5fe0 | ||
|
|
90fbc1a33e | ||
|
|
55dea47b94 | ||
|
|
c7791a207b | ||
|
|
30d18888b9 | ||
|
|
6ac6305b53 | ||
|
|
29e8ccef4e | ||
|
|
ce17efd95b | ||
|
|
5480cb9571 | ||
|
|
b38ffd7376 | ||
|
|
8d8f44f352 | ||
|
|
af5f21b2e2 | ||
|
|
4ac7474e4e | ||
|
|
bdb3bb98f6 | ||
|
|
896609c859 | ||
|
|
50158b851c | ||
|
|
56b672e657 | ||
|
|
b9e9576083 | ||
|
|
79cc5a4d27 | ||
|
|
5f1d57704e | ||
|
|
19983272f3 | ||
|
|
e1ab302234 | ||
|
|
6b1c443212 | ||
|
|
b79a271553 | ||
|
|
260141ee08 | ||
|
|
fb1232043d | ||
|
|
2377fc8bcf | ||
|
|
c610902b67 | ||
|
|
a802405e19 | ||
|
|
0dbaacfe12 | ||
|
|
b8dd60dec7 | ||
|
|
147c2507c3 | ||
|
|
d72f27909f | ||
|
|
1ee5329b1c | ||
|
|
ba0b046a95 | ||
|
|
412eb2a9ce | ||
|
|
dd8e279cac | ||
|
|
642432cb5e | ||
|
|
001c9380be | ||
|
|
2e830fff99 | ||
|
|
06dcb22ae2 | ||
|
|
0120874b8e | ||
|
|
13475977af | ||
|
|
df50669092 | ||
|
|
abd6b7c5c3 | ||
|
|
e36746363a | ||
|
|
1eba854f23 | ||
|
|
6b19e3dc0a | ||
|
|
cb1fbce46b | ||
|
|
cc38cccae1 | ||
|
|
efbe1779e3 | ||
|
|
8e3d0a6569 | ||
|
|
8bd5d54b0e | ||
|
|
4867cf1e7b | ||
|
|
3ea3250dbe | ||
|
|
3bbcbfdcf9 | ||
|
|
c7e84d8044 | ||
|
|
9ae1d0d22d | ||
|
|
51e80ffd48 | ||
|
|
be52dbbaa1 | ||
|
|
76147c78a1 | ||
|
|
59da745d6c | ||
|
|
68996d7561 | ||
|
|
5e12bab5cc | ||
|
|
7693aa5464 | ||
|
|
110f471efa | ||
|
|
2064f412cb | ||
|
|
3228fd3cbc | ||
|
|
bad55f7977 | ||
|
|
3cf7b2e39e | ||
|
|
b443da410a | ||
|
|
87912103c9 | ||
|
|
4ce1ee458e | ||
|
|
b0351a08de | ||
|
|
3321055025 | ||
|
|
5572897998 | ||
|
|
3112f92d08 | ||
|
|
74f55a863a | ||
|
|
9ea18e10c9 | ||
|
|
d84457b9c2 | ||
|
|
643397ed21 | ||
|
|
85bf9ab76a | ||
|
|
6ec1904aac | ||
|
|
f1315b9efa | ||
|
|
a0e6bb5cb2 | ||
|
|
eaf63d94f0 | ||
|
|
45500fba74 | ||
|
|
8472effab3 | ||
|
|
d368aed150 | ||
|
|
e3b60e80c0 | ||
|
|
9df10674f0 | ||
|
|
e3617fb8ab | ||
|
|
0fbfce58c6 | ||
|
|
f8b954bcd9 | ||
|
|
caa25b506d | ||
|
|
6911815171 | ||
|
|
0719e5f402 | ||
|
|
07b731add0 | ||
|
|
d42d8f5b07 | ||
|
|
143e72dd85 | ||
|
|
6835289564 | ||
|
|
1396e0855d | ||
|
|
6a7363e4ae | ||
|
|
9bc63d92a0 | ||
|
|
6b05f60bad | ||
|
|
25e6629bcc | ||
|
|
0796c1d2d2 | ||
|
|
aac1d52c4f | ||
|
|
f41d09a3cf | ||
|
|
8aff64f89a | ||
|
|
4bf9cfd477 | ||
|
|
08cbc442f5 | ||
|
|
a02dd201e6 | ||
|
|
a5f7dcee04 | ||
|
|
0ddd761904 | ||
|
|
991c2a9d05 | ||
|
|
4983c38298 | ||
|
|
337ab24899 | ||
|
|
63c6a9c5ad | ||
|
|
718e68ffae | ||
|
|
f56a5f01c4 | ||
|
|
b77ffe0228 | ||
|
|
fd6439bb36 | ||
|
|
121f8d02f3 | ||
|
|
ff23423d34 | ||
|
|
1d97599f8b | ||
|
|
e465624fd0 | ||
|
|
5ddbb6965f | ||
|
|
a3b5f6d07f | ||
|
|
ac2cbef8be | ||
|
|
dff22e9d09 | ||
|
|
7282b50ad0 | ||
|
|
8c059469fd | ||
|
|
8c3f153dd4 | ||
|
|
185b526e60 | ||
|
|
38996b30b1 | ||
|
|
827dfed7c2 | ||
|
|
28af21baeb | ||
|
|
951c70496b | ||
|
|
a36eeb96f3 | ||
|
|
7018d3d0e6 | ||
|
|
3f14b4de1f | ||
|
|
26c0be4c4e | ||
|
|
cec0c06a70 | ||
|
|
476f8deb07 | ||
|
|
dc5932d177 | ||
|
|
cfd93b7529 | ||
|
|
8b2208f394 | ||
|
|
00a3b01f39 | ||
|
|
3baca251f0 | ||
|
|
72daae1d92 | ||
|
|
fcbe53ddb5 | ||
|
|
e9851672eb | ||
|
|
9a45f7bd3d | ||
|
|
85834fd146 | ||
|
|
a0c1ac7b45 | ||
|
|
7b0dca0f9c | ||
|
|
34c83d7d29 | ||
|
|
7c6882234e | ||
|
|
2e68ddbae9 | ||
|
|
7724fc6af7 | ||
|
|
2939075f03 | ||
|
|
606f68de02 | ||
|
|
c6c71abf4d | ||
|
|
863160ae49 | ||
|
|
edd39fb194 | ||
|
|
a71d264d45 | ||
|
|
8a7a695836 | ||
|
|
de54af4061 | ||
|
|
2f2a652bc9 | ||
|
|
1e638663de | ||
|
|
1684a8792a | ||
|
|
f47c0d9774 | ||
|
|
89e7264e53 | ||
|
|
cada9f61c8 | ||
|
|
373fa55981 | ||
|
|
2bc703cfc2 | ||
|
|
c9865b821d | ||
|
|
9c0de23645 | ||
|
|
b7a38af41d | ||
|
|
661914781e | ||
|
|
6e3f3887e9 | ||
|
|
a66d6bb034 | ||
|
|
2e197e2b98 | ||
|
|
55d1a4f964 | ||
|
|
82a7d7a977 | ||
|
|
dae54b456f | ||
|
|
1b7a044f33 | ||
|
|
18264403b1 | ||
|
|
04d43b7039 | ||
|
|
2dfecfb661 | ||
|
|
250f1f5f6e | ||
|
|
3ac718763f | ||
|
|
05e1555c0d | ||
|
|
855d1e2bf5 | ||
|
|
f0bfb3b2b2 | ||
|
|
4b4db0f7fb | ||
|
|
9bed2b80ee | ||
|
|
bb157c0cbf | ||
|
|
1ef05d4a28 | ||
|
|
0b88208022 | ||
|
|
c9d9ed3493 | ||
|
|
bd8b9f5781 | ||
|
|
9cbcf23df0 | ||
|
|
36e42db05b | ||
|
|
2bf6a1d813 | ||
|
|
e8373d3564 | ||
|
|
e218377a3d | ||
|
|
7d1aed4955 | ||
|
|
b4acbcf1fe | ||
|
|
50cb62c5d2 | ||
|
|
4cf868bd74 | ||
|
|
baa5a7c3e9 | ||
|
|
ee228f7aea | ||
|
|
d5b11c7d1b | ||
|
|
ed7db34bab | ||
|
|
57e45e3af8 | ||
|
|
1dc46478cb | ||
|
|
113ed0927d | ||
|
|
ab8be2d741 | ||
|
|
3b53b11fcd | ||
|
|
9fb661559b | ||
|
|
04882cf72c | ||
|
|
288176bbc9 | ||
|
|
5638a4fc62 | ||
|
|
3b4ce91fa3 | ||
|
|
1c87e5e9a8 | ||
|
|
a887e6a881 | ||
|
|
69290cad6f | ||
|
|
b66c7da05f | ||
|
|
92ddf77453 | ||
|
|
8e2f538983 | ||
|
|
2817d8caf2 | ||
|
|
b7f08fb159 | ||
|
|
0c2768f5bd | ||
|
|
09bede1a92 | ||
|
|
7059d3b71e | ||
|
|
e5de08faa1 | ||
|
|
e43ff076fd | ||
|
|
3ea7381dea | ||
|
|
f1c46f51e5 | ||
|
|
297fb4e0b0 | ||
|
|
9e406dfee2 | ||
|
|
30b7aa8a17 | ||
|
|
c1d16e0016 | ||
|
|
929ffb8d77 | ||
|
|
197a2e3b54 | ||
|
|
6cf6c8b918 | ||
|
|
058d7ec2ea | ||
|
|
752b5f705e | ||
|
|
e7fa579637 | ||
|
|
97781d4112 | ||
|
|
3ddd8e66a7 | ||
|
|
8a1e865e37 | ||
|
|
e850cb3ea3 | ||
|
|
13d3efe8df | ||
|
|
d6ecf785a2 | ||
|
|
19cb39869f | ||
|
|
a38bdf6758 | ||
|
|
bdbdab7fcc | ||
|
|
5aa9670120 | ||
|
|
8ad8cb93cc | ||
|
|
610e172fcf | ||
|
|
6942070a21 | ||
|
|
e283200511 | ||
|
|
54a192a5c5 | ||
|
|
c3bd65eda2 | ||
|
|
7c2ed1d2d6 | ||
|
|
3de81e0147 | ||
|
|
7a31a1d311 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
coverage.html
|
||||
coverage/
|
||||
.DS_Store
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
@@ -13,7 +12,5 @@ benchmarks/graphs
|
||||
testing
|
||||
node_modules/
|
||||
testing
|
||||
.coverage_data
|
||||
cover_html
|
||||
test.js
|
||||
.idea
|
||||
|
||||
0
.gitmodules
vendored
0
.gitmodules
vendored
@@ -1,9 +1,11 @@
|
||||
.git*
|
||||
benchmarks/
|
||||
coverage/
|
||||
docs/
|
||||
examples/
|
||||
support/
|
||||
test/
|
||||
testing.js
|
||||
.DS_Store
|
||||
coverage.html
|
||||
lib-cov
|
||||
.travis.yml
|
||||
Contributing.md
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.11"
|
||||
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/visionmedia/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.
|
||||
|
||||
811
History.md
811
History.md
@@ -1,27 +1,798 @@
|
||||
|
||||
3.3.4 / 2013-07-08
|
||||
4.7.4 / 2014-08-04
|
||||
==================
|
||||
|
||||
* update send and connect
|
||||
* fix `res.sendfile` regression for serving directory index files
|
||||
* deps: send@0.7.4
|
||||
- Fix incorrect 403 on Windows and Node.js 0.11
|
||||
- Fix serving index files without root dir
|
||||
* deps: serve-static@~1.4.4
|
||||
- deps: send@0.7.4
|
||||
|
||||
3.3.3 / 2013-07-04
|
||||
4.7.3 / 2014-08-04
|
||||
==================
|
||||
|
||||
* deps: send@0.7.3
|
||||
- Fix incorrect 403 on Windows and Node.js 0.11
|
||||
* deps: serve-static@~1.4.3
|
||||
- Fix incorrect 403 on Windows and Node.js 0.11
|
||||
- deps: send@0.7.3
|
||||
|
||||
4.7.2 / 2014-07-27
|
||||
==================
|
||||
|
||||
* deps: depd@0.4.4
|
||||
- Work-around v8 generating empty stack traces
|
||||
* deps: send@0.7.2
|
||||
- deps: depd@0.4.4
|
||||
* deps: serve-static@~1.4.2
|
||||
|
||||
4.7.1 / 2014-07-26
|
||||
==================
|
||||
|
||||
* deps: depd@0.4.3
|
||||
- Fix exception when global `Error.stackTraceLimit` is too low
|
||||
* deps: send@0.7.1
|
||||
- deps: depd@0.4.3
|
||||
* deps: serve-static@~1.4.1
|
||||
|
||||
4.7.0 / 2014-07-25
|
||||
==================
|
||||
|
||||
* fix `req.protocol` for proxy-direct connections
|
||||
* configurable query parser with `app.set('query parser', parser)`
|
||||
- `app.set('query parser', 'extended')` parse with "qs" module
|
||||
- `app.set('query parser', 'simple')` parse with "querystring" core module
|
||||
- `app.set('query parser', false)` disable query string parsing
|
||||
- `app.set('query parser', true)` enable simple parsing
|
||||
* deprecate `res.json(status, obj)` -- use `res.status(status).json(obj)` instead
|
||||
* deprecate `res.jsonp(status, obj)` -- use `res.status(status).jsonp(obj)` instead
|
||||
* deprecate `res.send(status, body)` -- use `res.status(status).send(body)` instead
|
||||
* deps: debug@1.0.4
|
||||
* deps: depd@0.4.2
|
||||
- Add `TRACE_DEPRECATION` environment variable
|
||||
- Remove non-standard grey color from color output
|
||||
- Support `--no-deprecation` argument
|
||||
- Support `--trace-deprecation` argument
|
||||
* deps: finalhandler@0.1.0
|
||||
- Respond after request fully read
|
||||
- deps: debug@1.0.4
|
||||
* deps: parseurl@~1.2.0
|
||||
- Cache URLs based on original value
|
||||
- Remove no-longer-needed URL mis-parse work-around
|
||||
- Simplify the "fast-path" `RegExp`
|
||||
* deps: send@0.7.0
|
||||
- Add `dotfiles` option
|
||||
- Cap `maxAge` value to 1 year
|
||||
- deps: debug@1.0.4
|
||||
- deps: depd@0.4.2
|
||||
* deps: serve-static@~1.4.0
|
||||
- deps: parseurl@~1.2.0
|
||||
- deps: send@0.7.0
|
||||
* perf: prevent multiple `Buffer` creation in `res.send`
|
||||
|
||||
4.6.1 / 2014-07-12
|
||||
==================
|
||||
|
||||
* fix `subapp.mountpath` regression for `app.use(subapp)`
|
||||
|
||||
4.6.0 / 2014-07-11
|
||||
==================
|
||||
|
||||
* accept multiple callbacks to `app.use()`
|
||||
* add explicit "Rosetta Flash JSONP abuse" protection
|
||||
- previous versions are not vulnerable; this is just explicit protection
|
||||
* catch errors in multiple `req.param(name, fn)` handlers
|
||||
* deprecate `res.redirect(url, status)` -- use `res.redirect(status, url)` instead
|
||||
* fix `res.send(status, num)` to send `num` as json (not error)
|
||||
* remove unnecessary escaping when `res.jsonp` returns JSON response
|
||||
* support non-string `path` in `app.use(path, fn)`
|
||||
- supports array of paths
|
||||
- supports `RegExp`
|
||||
* router: fix optimization on router exit
|
||||
* router: refactor location of `try` blocks
|
||||
* router: speed up standard `app.use(fn)`
|
||||
* deps: debug@1.0.3
|
||||
- Add support for multiple wildcards in namespaces
|
||||
* deps: finalhandler@0.0.3
|
||||
- deps: debug@1.0.3
|
||||
* deps: methods@1.1.0
|
||||
- add `CONNECT`
|
||||
* deps: parseurl@~1.1.3
|
||||
- faster parsing of href-only URLs
|
||||
* deps: path-to-regexp@0.1.3
|
||||
* deps: send@0.6.0
|
||||
- deps: debug@1.0.3
|
||||
* deps: serve-static@~1.3.2
|
||||
- deps: parseurl@~1.1.3
|
||||
- deps: send@0.6.0
|
||||
* perf: fix arguments reassign deopt in some `res` methods
|
||||
|
||||
4.5.1 / 2014-07-06
|
||||
==================
|
||||
|
||||
* fix routing regression when altering `req.method`
|
||||
|
||||
4.5.0 / 2014-07-04
|
||||
==================
|
||||
|
||||
* add deprecation message to non-plural `req.accepts*`
|
||||
* add deprecation message to `res.send(body, status)`
|
||||
* add deprecation message to `res.vary()`
|
||||
* add `headers` option to `res.sendfile`
|
||||
- use to set headers on successful file transfer
|
||||
* add `mergeParams` option to `Router`
|
||||
- merges `req.params` from parent routes
|
||||
* add `req.hostname` -- correct name for what `req.host` returns
|
||||
* deprecate things with `depd` module
|
||||
* deprecate `req.host` -- use `req.hostname` instead
|
||||
* fix behavior when handling request without routes
|
||||
* fix handling when `route.all` is only route
|
||||
* invoke `router.param()` only when route matches
|
||||
* restore `req.params` after invoking router
|
||||
* use `finalhandler` for final response handling
|
||||
* use `media-typer` to alter content-type charset
|
||||
* deps: accepts@~1.0.7
|
||||
* deps: send@0.5.0
|
||||
- Accept string for `maxage` (converted by `ms`)
|
||||
- Include link in default redirect response
|
||||
* deps: serve-static@~1.3.0
|
||||
- Accept string for `maxAge` (converted by `ms`)
|
||||
- Add `setHeaders` option
|
||||
- Include HTML link in redirect response
|
||||
- deps: send@0.5.0
|
||||
* deps: type-is@~1.3.2
|
||||
|
||||
4.4.5 / 2014-06-26
|
||||
==================
|
||||
|
||||
* deps: cookie-signature@1.0.4
|
||||
- fix for timing attacks
|
||||
|
||||
4.4.4 / 2014-06-20
|
||||
==================
|
||||
|
||||
* fix `res.attachment` Unicode filenames in Safari
|
||||
* fix "trim prefix" debug message in `express:router`
|
||||
* deps: accepts@~1.0.5
|
||||
* deps: buffer-crc32@0.2.3
|
||||
|
||||
4.4.3 / 2014-06-11
|
||||
==================
|
||||
|
||||
* fix persistence of modified `req.params[name]` from `app.param()`
|
||||
* deps: accepts@1.0.3
|
||||
- deps: negotiator@0.4.6
|
||||
* deps: debug@1.0.2
|
||||
* deps: send@0.4.3
|
||||
- Do not throw un-catchable error on file open race condition
|
||||
- Use `escape-html` for HTML escaping
|
||||
- deps: debug@1.0.2
|
||||
- deps: finished@1.2.2
|
||||
- deps: fresh@0.2.2
|
||||
* deps: serve-static@1.2.3
|
||||
- Do not throw un-catchable error on file open race condition
|
||||
- deps: send@0.4.3
|
||||
|
||||
4.4.2 / 2014-06-09
|
||||
==================
|
||||
|
||||
* fix catching errors from top-level handlers
|
||||
* use `vary` module for `res.vary`
|
||||
* deps: debug@1.0.1
|
||||
* deps: proxy-addr@1.0.1
|
||||
* deps: send@0.4.2
|
||||
- fix "event emitter leak" warnings
|
||||
- deps: debug@1.0.1
|
||||
- deps: finished@1.2.1
|
||||
* deps: serve-static@1.2.2
|
||||
- fix "event emitter leak" warnings
|
||||
- deps: send@0.4.2
|
||||
* deps: type-is@1.2.1
|
||||
|
||||
4.4.1 / 2014-06-02
|
||||
==================
|
||||
|
||||
* deps: methods@1.0.1
|
||||
* deps: send@0.4.1
|
||||
- Send `max-age` in `Cache-Control` in correct format
|
||||
* deps: serve-static@1.2.1
|
||||
- use `escape-html` for escaping
|
||||
- deps: send@0.4.1
|
||||
|
||||
4.4.0 / 2014-05-30
|
||||
==================
|
||||
|
||||
* custom etag control with `app.set('etag', val)`
|
||||
- `app.set('etag', function(body, encoding){ return '"etag"' })` custom etag generation
|
||||
- `app.set('etag', 'weak')` weak tag
|
||||
- `app.set('etag', 'strong')` strong etag
|
||||
- `app.set('etag', false)` turn off
|
||||
- `app.set('etag', true)` standard etag
|
||||
* mark `res.send` ETag as weak and reduce collisions
|
||||
* update accepts to 1.0.2
|
||||
- Fix interpretation when header not in request
|
||||
* update send to 0.4.0
|
||||
- Calculate ETag with md5 for reduced collisions
|
||||
- Ignore stream errors after request ends
|
||||
- deps: debug@0.8.1
|
||||
* update serve-static to 1.2.0
|
||||
- Calculate ETag with md5 for reduced collisions
|
||||
- Ignore stream errors after request ends
|
||||
- deps: send@0.4.0
|
||||
|
||||
4.3.2 / 2014-05-28
|
||||
==================
|
||||
|
||||
* fix handling of errors from `router.param()` callbacks
|
||||
|
||||
4.3.1 / 2014-05-23
|
||||
==================
|
||||
|
||||
* revert "fix behavior of multiple `app.VERB` for the same path"
|
||||
- this caused a regression in the order of route execution
|
||||
|
||||
4.3.0 / 2014-05-21
|
||||
==================
|
||||
|
||||
* add `req.baseUrl` to access the path stripped from `req.url` in routes
|
||||
* fix behavior of multiple `app.VERB` for the same path
|
||||
* fix issue routing requests among sub routers
|
||||
* invoke `router.param()` only when necessary instead of every match
|
||||
* proper proxy trust with `app.set('trust proxy', trust)`
|
||||
- `app.set('trust proxy', 1)` trust first hop
|
||||
- `app.set('trust proxy', 'loopback')` trust loopback addresses
|
||||
- `app.set('trust proxy', '10.0.0.1')` trust single IP
|
||||
- `app.set('trust proxy', '10.0.0.1/16')` trust subnet
|
||||
- `app.set('trust proxy', '10.0.0.1, 10.0.0.2')` trust list
|
||||
- `app.set('trust proxy', false)` turn off
|
||||
- `app.set('trust proxy', true)` trust everything
|
||||
* set proper `charset` in `Content-Type` for `res.send`
|
||||
* update type-is to 1.2.0
|
||||
- support suffix matching
|
||||
|
||||
4.2.0 / 2014-05-11
|
||||
==================
|
||||
|
||||
* deprecate `app.del()` -- use `app.delete()` instead
|
||||
* deprecate `res.json(obj, status)` -- use `res.json(status, obj)` instead
|
||||
- the edge-case `res.json(status, num)` requires `res.status(status).json(num)`
|
||||
* deprecate `res.jsonp(obj, status)` -- use `res.jsonp(status, obj)` instead
|
||||
- the edge-case `res.jsonp(status, num)` requires `res.status(status).jsonp(num)`
|
||||
* fix `req.next` when inside router instance
|
||||
* include `ETag` header in `HEAD` requests
|
||||
* keep previous `Content-Type` for `res.jsonp`
|
||||
* support PURGE method
|
||||
- add `app.purge`
|
||||
- add `router.purge`
|
||||
- include PURGE in `app.all`
|
||||
* update debug to 0.8.0
|
||||
- add `enable()` method
|
||||
- change from stderr to stdout
|
||||
* update methods to 1.0.0
|
||||
- add PURGE
|
||||
|
||||
4.1.2 / 2014-05-08
|
||||
==================
|
||||
|
||||
* fix `req.host` for IPv6 literals
|
||||
* fix `res.jsonp` error if callback param is object
|
||||
|
||||
4.1.1 / 2014-04-27
|
||||
==================
|
||||
|
||||
* fix package.json to reflect supported node version
|
||||
|
||||
4.1.0 / 2014-04-24
|
||||
==================
|
||||
|
||||
* pass options from `res.sendfile` to `send`
|
||||
* preserve casing of headers in `res.header` and `res.set`
|
||||
* support unicode file names in `res.attachment` and `res.download`
|
||||
* update accepts to 1.0.1
|
||||
- deps: negotiator@0.4.0
|
||||
* update cookie to 0.1.2
|
||||
- Fix for maxAge == 0
|
||||
- made compat with expires field
|
||||
* update send to 0.3.0
|
||||
- Accept API options in options object
|
||||
- Coerce option types
|
||||
- Control whether to generate etags
|
||||
- Default directory access to 403 when index disabled
|
||||
- Fix sending files with dots without root set
|
||||
- Include file path in etag
|
||||
- Make "Can't set headers after they are sent." catchable
|
||||
- Send full entity-body for multi range requests
|
||||
- Set etags to "weak"
|
||||
- Support "If-Range" header
|
||||
- Support multiple index paths
|
||||
- deps: mime@1.2.11
|
||||
* update serve-static to 1.1.0
|
||||
- Accept options directly to `send` module
|
||||
- Resolve relative paths at middleware setup
|
||||
- Use parseurl to parse the URL from request
|
||||
- deps: send@0.3.0
|
||||
* update type-is to 1.1.0
|
||||
- add non-array values support
|
||||
- add `multipart` as a shorthand
|
||||
|
||||
4.0.0 / 2014-04-09
|
||||
==================
|
||||
|
||||
* remove:
|
||||
- node 0.8 support
|
||||
- connect and connect's patches except for charset handling
|
||||
- express(1) - moved to [express-generator](https://github.com/expressjs/generator)
|
||||
- `express.createServer()` - it has been deprecated for a long time. Use `express()`
|
||||
- `app.configure` - use logic in your own app code
|
||||
- `app.router` - is removed
|
||||
- `req.auth` - use `basic-auth` instead
|
||||
- `req.accepted*` - use `req.accepts*()` instead
|
||||
- `res.location` - relative URL resolution is removed
|
||||
- `res.charset` - include the charset in the content type when using `res.set()`
|
||||
- all bundled middleware except `static`
|
||||
* change:
|
||||
- `app.route` -> `app.mountpath` when mounting an express app in another express app
|
||||
- `json spaces` no longer enabled by default in development
|
||||
- `req.accepts*` -> `req.accepts*s` - i.e. `req.acceptsEncoding` -> `req.acceptsEncodings`
|
||||
- `req.params` is now an object instead of an array
|
||||
- `res.locals` is no longer a function. It is a plain js object. Treat it as such.
|
||||
- `res.headerSent` -> `res.headersSent` to match node.js ServerResponse object
|
||||
* refactor:
|
||||
- `req.accepts*` with [accepts](https://github.com/expressjs/accepts)
|
||||
- `req.is` with [type-is](https://github.com/expressjs/type-is)
|
||||
- [path-to-regexp](https://github.com/component/path-to-regexp)
|
||||
* add:
|
||||
- `app.router()` - returns the app Router instance
|
||||
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route
|
||||
- Router & Route - public API
|
||||
|
||||
3.15.3 / 2014-08-04
|
||||
===================
|
||||
|
||||
* fix `res.sendfile` regression for serving directory index files
|
||||
* deps: connect@2.24.3
|
||||
- deps: serve-index@~1.1.5
|
||||
- deps: serve-static@~1.4.4
|
||||
* deps: send@0.7.4
|
||||
- Fix incorrect 403 on Windows and Node.js 0.11
|
||||
- Fix serving index files without root dir
|
||||
|
||||
3.15.2 / 2014-07-27
|
||||
===================
|
||||
|
||||
* deps: connect@2.24.2
|
||||
- deps: body-parser@~1.5.2
|
||||
- deps: depd@0.4.4
|
||||
- deps: express-session@~1.7.2
|
||||
- deps: morgan@~1.2.2
|
||||
- deps: serve-static@~1.4.2
|
||||
* deps: depd@0.4.4
|
||||
- Work-around v8 generating empty stack traces
|
||||
* deps: send@0.7.2
|
||||
- deps: depd@0.4.4
|
||||
|
||||
3.15.1 / 2014-07-26
|
||||
===================
|
||||
|
||||
* deps: connect@2.24.1
|
||||
- deps: body-parser@~1.5.1
|
||||
- deps: depd@0.4.3
|
||||
- deps: express-session@~1.7.1
|
||||
- deps: morgan@~1.2.1
|
||||
- deps: serve-index@~1.1.4
|
||||
- deps: serve-static@~1.4.1
|
||||
* deps: depd@0.4.3
|
||||
- Fix exception when global `Error.stackTraceLimit` is too low
|
||||
* deps: send@0.7.1
|
||||
- deps: depd@0.4.3
|
||||
|
||||
3.15.0 / 2014-07-22
|
||||
===================
|
||||
|
||||
* Fix `req.protocol` for proxy-direct connections
|
||||
* Pass options from `res.sendfile` to `send`
|
||||
* deps: connect@2.24.0
|
||||
- deps: body-parser@~1.5.0
|
||||
- deps: compression@~1.0.9
|
||||
- deps: connect-timeout@~1.2.1
|
||||
- deps: debug@1.0.4
|
||||
- deps: depd@0.4.2
|
||||
- deps: express-session@~1.7.0
|
||||
- deps: finalhandler@0.1.0
|
||||
- deps: method-override@~2.1.2
|
||||
- deps: morgan@~1.2.0
|
||||
- deps: multiparty@3.3.1
|
||||
- deps: parseurl@~1.2.0
|
||||
- deps: serve-static@~1.4.0
|
||||
* deps: debug@1.0.4
|
||||
* deps: depd@0.4.2
|
||||
- Add `TRACE_DEPRECATION` environment variable
|
||||
- Remove non-standard grey color from color output
|
||||
- Support `--no-deprecation` argument
|
||||
- Support `--trace-deprecation` argument
|
||||
* deps: parseurl@~1.2.0
|
||||
- Cache URLs based on original value
|
||||
- Remove no-longer-needed URL mis-parse work-around
|
||||
- Simplify the "fast-path" `RegExp`
|
||||
* deps: send@0.7.0
|
||||
- Add `dotfiles` option
|
||||
- Cap `maxAge` value to 1 year
|
||||
- deps: debug@1.0.4
|
||||
- deps: depd@0.4.2
|
||||
|
||||
3.14.0 / 2014-07-11
|
||||
===================
|
||||
|
||||
* add explicit "Rosetta Flash JSONP abuse" protection
|
||||
- previous versions are not vulnerable; this is just explicit protection
|
||||
* deprecate `res.redirect(url, status)` -- use `res.redirect(status, url)` instead
|
||||
* fix `res.send(status, num)` to send `num` as json (not error)
|
||||
* remove unnecessary escaping when `res.jsonp` returns JSON response
|
||||
* deps: basic-auth@1.0.0
|
||||
- support empty password
|
||||
- support empty username
|
||||
* deps: connect@2.23.0
|
||||
- deps: debug@1.0.3
|
||||
- deps: express-session@~1.6.4
|
||||
- deps: method-override@~2.1.0
|
||||
- deps: parseurl@~1.1.3
|
||||
- deps: serve-static@~1.3.1
|
||||
* deps: debug@1.0.3
|
||||
- Add support for multiple wildcards in namespaces
|
||||
* deps: methods@1.1.0
|
||||
- add `CONNECT`
|
||||
* deps: parseurl@~1.1.3
|
||||
- faster parsing of href-only URLs
|
||||
|
||||
3.13.0 / 2014-07-03
|
||||
===================
|
||||
|
||||
* add deprecation message to `app.configure`
|
||||
* add deprecation message to `req.auth`
|
||||
* use `basic-auth` to parse `Authorization` header
|
||||
* deps: connect@2.22.0
|
||||
- deps: csurf@~1.3.0
|
||||
- deps: express-session@~1.6.1
|
||||
- deps: multiparty@3.3.0
|
||||
- deps: serve-static@~1.3.0
|
||||
* deps: send@0.5.0
|
||||
- Accept string for `maxage` (converted by `ms`)
|
||||
- Include link in default redirect response
|
||||
|
||||
3.12.1 / 2014-06-26
|
||||
===================
|
||||
|
||||
* deps: connect@2.21.1
|
||||
- deps: cookie-parser@1.3.2
|
||||
- deps: cookie-signature@1.0.4
|
||||
- deps: express-session@~1.5.2
|
||||
- deps: type-is@~1.3.2
|
||||
* deps: cookie-signature@1.0.4
|
||||
- fix for timing attacks
|
||||
|
||||
3.12.0 / 2014-06-21
|
||||
===================
|
||||
|
||||
* use `media-typer` to alter content-type charset
|
||||
* deps: connect@2.21.0
|
||||
- deprecate `connect(middleware)` -- use `app.use(middleware)` instead
|
||||
- deprecate `connect.createServer()` -- use `connect()` instead
|
||||
- fix `res.setHeader()` patch to work with with get -> append -> set pattern
|
||||
- deps: compression@~1.0.8
|
||||
- deps: errorhandler@~1.1.1
|
||||
- deps: express-session@~1.5.0
|
||||
- deps: serve-index@~1.1.3
|
||||
|
||||
3.11.0 / 2014-06-19
|
||||
===================
|
||||
|
||||
* deprecate things with `depd` module
|
||||
* deps: buffer-crc32@0.2.3
|
||||
* deps: connect@2.20.2
|
||||
- deprecate `verify` option to `json` -- use `body-parser` npm module instead
|
||||
- deprecate `verify` option to `urlencoded` -- use `body-parser` npm module instead
|
||||
- deprecate things with `depd` module
|
||||
- use `finalhandler` for final response handling
|
||||
- use `media-typer` to parse `content-type` for charset
|
||||
- deps: body-parser@1.4.3
|
||||
- deps: connect-timeout@1.1.1
|
||||
- deps: cookie-parser@1.3.1
|
||||
- deps: csurf@1.2.2
|
||||
- deps: errorhandler@1.1.0
|
||||
- deps: express-session@1.4.0
|
||||
- deps: multiparty@3.2.9
|
||||
- deps: serve-index@1.1.2
|
||||
- deps: type-is@1.3.1
|
||||
- deps: vhost@2.0.0
|
||||
|
||||
3.10.5 / 2014-06-11
|
||||
===================
|
||||
|
||||
* deps: connect@2.19.6
|
||||
- deps: body-parser@1.3.1
|
||||
- deps: compression@1.0.7
|
||||
- deps: debug@1.0.2
|
||||
- deps: serve-index@1.1.1
|
||||
- deps: serve-static@1.2.3
|
||||
* deps: debug@1.0.2
|
||||
* deps: send@0.4.3
|
||||
- Do not throw un-catchable error on file open race condition
|
||||
- Use `escape-html` for HTML escaping
|
||||
- deps: debug@1.0.2
|
||||
- deps: finished@1.2.2
|
||||
- deps: fresh@0.2.2
|
||||
|
||||
3.10.4 / 2014-06-09
|
||||
===================
|
||||
|
||||
* deps: connect@2.19.5
|
||||
- fix "event emitter leak" warnings
|
||||
- deps: csurf@1.2.1
|
||||
- deps: debug@1.0.1
|
||||
- deps: serve-static@1.2.2
|
||||
- deps: type-is@1.2.1
|
||||
* deps: debug@1.0.1
|
||||
* deps: send@0.4.2
|
||||
- fix "event emitter leak" warnings
|
||||
- deps: finished@1.2.1
|
||||
- deps: debug@1.0.1
|
||||
|
||||
3.10.3 / 2014-06-05
|
||||
===================
|
||||
|
||||
* use `vary` module for `res.vary`
|
||||
* deps: connect@2.19.4
|
||||
- deps: errorhandler@1.0.2
|
||||
- deps: method-override@2.0.2
|
||||
- deps: serve-favicon@2.0.1
|
||||
* deps: debug@1.0.0
|
||||
|
||||
3.10.2 / 2014-06-03
|
||||
===================
|
||||
|
||||
* deps: connect@2.19.3
|
||||
- deps: compression@1.0.6
|
||||
|
||||
3.10.1 / 2014-06-03
|
||||
===================
|
||||
|
||||
* deps: connect@2.19.2
|
||||
- deps: compression@1.0.4
|
||||
* deps: proxy-addr@1.0.1
|
||||
|
||||
3.10.0 / 2014-06-02
|
||||
===================
|
||||
|
||||
* deps: connect@2.19.1
|
||||
- deprecate `methodOverride()` -- use `method-override` npm module instead
|
||||
- deps: body-parser@1.3.0
|
||||
- deps: method-override@2.0.1
|
||||
- deps: multiparty@3.2.8
|
||||
- deps: response-time@2.0.0
|
||||
- deps: serve-static@1.2.1
|
||||
* deps: methods@1.0.1
|
||||
* deps: send@0.4.1
|
||||
- Send `max-age` in `Cache-Control` in correct format
|
||||
|
||||
3.9.0 / 2014-05-30
|
||||
==================
|
||||
|
||||
* custom etag control with `app.set('etag', val)`
|
||||
- `app.set('etag', function(body, encoding){ return '"etag"' })` custom etag generation
|
||||
- `app.set('etag', 'weak')` weak tag
|
||||
- `app.set('etag', 'strong')` strong etag
|
||||
- `app.set('etag', false)` turn off
|
||||
- `app.set('etag', true)` standard etag
|
||||
* Include ETag in HEAD requests
|
||||
* mark `res.send` ETag as weak and reduce collisions
|
||||
* update connect to 2.18.0
|
||||
- deps: compression@1.0.3
|
||||
- deps: serve-index@1.1.0
|
||||
- deps: serve-static@1.2.0
|
||||
* update send to 0.4.0
|
||||
- Calculate ETag with md5 for reduced collisions
|
||||
- Ignore stream errors after request ends
|
||||
- deps: debug@0.8.1
|
||||
|
||||
3.8.1 / 2014-05-27
|
||||
==================
|
||||
|
||||
* update connect to 2.17.3
|
||||
- deps: body-parser@1.2.2
|
||||
- deps: express-session@1.2.1
|
||||
- deps: method-override@1.0.2
|
||||
|
||||
3.8.0 / 2014-05-21
|
||||
==================
|
||||
|
||||
* keep previous `Content-Type` for `res.jsonp`
|
||||
* set proper `charset` in `Content-Type` for `res.send`
|
||||
* update connect to 2.17.1
|
||||
- fix `res.charset` appending charset when `content-type` has one
|
||||
- deps: express-session@1.2.0
|
||||
- deps: morgan@1.1.1
|
||||
- deps: serve-index@1.0.3
|
||||
|
||||
3.7.0 / 2014-05-18
|
||||
==================
|
||||
|
||||
* proper proxy trust with `app.set('trust proxy', trust)`
|
||||
- `app.set('trust proxy', 1)` trust first hop
|
||||
- `app.set('trust proxy', 'loopback')` trust loopback addresses
|
||||
- `app.set('trust proxy', '10.0.0.1')` trust single IP
|
||||
- `app.set('trust proxy', '10.0.0.1/16')` trust subnet
|
||||
- `app.set('trust proxy', '10.0.0.1, 10.0.0.2')` trust list
|
||||
- `app.set('trust proxy', false)` turn off
|
||||
- `app.set('trust proxy', true)` trust everything
|
||||
* update connect to 2.16.2
|
||||
- deprecate `res.headerSent` -- use `res.headersSent`
|
||||
- deprecate `res.on("header")` -- use on-headers module instead
|
||||
- fix edge-case in `res.appendHeader` that would append in wrong order
|
||||
- json: use body-parser
|
||||
- urlencoded: use body-parser
|
||||
- dep: bytes@1.0.0
|
||||
- dep: cookie-parser@1.1.0
|
||||
- dep: csurf@1.2.0
|
||||
- dep: express-session@1.1.0
|
||||
- dep: method-override@1.0.1
|
||||
|
||||
3.6.0 / 2014-05-09
|
||||
==================
|
||||
|
||||
* deprecate `app.del()` -- use `app.delete()` instead
|
||||
* deprecate `res.json(obj, status)` -- use `res.json(status, obj)` instead
|
||||
- the edge-case `res.json(status, num)` requires `res.status(status).json(num)`
|
||||
* deprecate `res.jsonp(obj, status)` -- use `res.jsonp(status, obj)` instead
|
||||
- the edge-case `res.jsonp(status, num)` requires `res.status(status).jsonp(num)`
|
||||
* support PURGE method
|
||||
- add `app.purge`
|
||||
- add `router.purge`
|
||||
- include PURGE in `app.all`
|
||||
* update connect to 2.15.0
|
||||
* Add `res.appendHeader`
|
||||
* Call error stack even when response has been sent
|
||||
* Patch `res.headerSent` to return Boolean
|
||||
* Patch `res.headersSent` for node.js 0.8
|
||||
* Prevent default 404 handler after response sent
|
||||
* dep: compression@1.0.2
|
||||
* dep: connect-timeout@1.1.0
|
||||
* dep: debug@^0.8.0
|
||||
* dep: errorhandler@1.0.1
|
||||
* dep: express-session@1.0.4
|
||||
* dep: morgan@1.0.1
|
||||
* dep: serve-favicon@2.0.0
|
||||
* dep: serve-index@1.0.2
|
||||
* update debug to 0.8.0
|
||||
* add `enable()` method
|
||||
* change from stderr to stdout
|
||||
* update methods to 1.0.0
|
||||
- add PURGE
|
||||
* update mkdirp to 0.5.0
|
||||
|
||||
3.5.3 / 2014-05-08
|
||||
==================
|
||||
|
||||
* fix `req.host` for IPv6 literals
|
||||
* fix `res.jsonp` error if callback param is object
|
||||
|
||||
3.5.2 / 2014-04-24
|
||||
==================
|
||||
|
||||
* update connect to 2.14.5
|
||||
* update cookie to 0.1.2
|
||||
* update mkdirp to 0.4.0
|
||||
* update send to 0.3.0
|
||||
|
||||
3.5.1 / 2014-03-25
|
||||
==================
|
||||
|
||||
* pin less-middleware in generated app
|
||||
|
||||
3.5.0 / 2014-03-06
|
||||
==================
|
||||
|
||||
* bump deps
|
||||
|
||||
3.4.8 / 2014-01-13
|
||||
==================
|
||||
|
||||
* prevent incorrect automatic OPTIONS responses #1868 @dpatti
|
||||
* update binary and examples for jade 1.0 #1876 @yossi, #1877 @reqshark, #1892 @matheusazzi
|
||||
* throw 400 in case of malformed paths @rlidwka
|
||||
|
||||
3.4.7 / 2013-12-10
|
||||
==================
|
||||
|
||||
* update connect
|
||||
|
||||
3.3.2 / 2013-07-03
|
||||
3.4.6 / 2013-12-01
|
||||
==================
|
||||
|
||||
* update connect (raw-body)
|
||||
|
||||
3.4.5 / 2013-11-27
|
||||
==================
|
||||
|
||||
* update connect
|
||||
* res.location: remove leading ./ #1802 @kapouer
|
||||
* res.redirect: fix `res.redirect('toString') #1829 @michaelficarra
|
||||
* res.send: always send ETag when content-length > 0
|
||||
* router: add Router.all() method
|
||||
|
||||
3.4.4 / 2013-10-29
|
||||
==================
|
||||
|
||||
* update connect
|
||||
* update supertest
|
||||
* update methods
|
||||
* express(1): replace bodyParser() with urlencoded() and json() #1795 @chirag04
|
||||
|
||||
3.4.3 / 2013-10-23
|
||||
==================
|
||||
|
||||
* update connect
|
||||
|
||||
3.4.2 / 2013-10-18
|
||||
==================
|
||||
|
||||
* update connect
|
||||
* downgrade commander
|
||||
|
||||
3.4.1 / 2013-10-15
|
||||
==================
|
||||
|
||||
* update connect
|
||||
* update commander
|
||||
* jsonp: check if callback is a function
|
||||
* router: wrap encodeURIComponent in a try/catch #1735 (@lxe)
|
||||
* res.format: now includes chraset @1747 (@sorribas)
|
||||
* res.links: allow multiple calls @1746 (@sorribas)
|
||||
|
||||
3.4.0 / 2013-09-07
|
||||
==================
|
||||
|
||||
* add res.vary(). Closes #1682
|
||||
* update connect
|
||||
|
||||
3.3.8 / 2013-09-02
|
||||
==================
|
||||
|
||||
* update connect
|
||||
|
||||
3.3.7 / 2013-08-28
|
||||
==================
|
||||
|
||||
* update connect
|
||||
|
||||
3.3.6 / 2013-08-27
|
||||
==================
|
||||
|
||||
* Revert "remove charset from json responses. Closes #1631" (causes issues in some clients)
|
||||
* add: req.accepts take an argument list
|
||||
|
||||
3.3.4 / 2013-07-08
|
||||
==================
|
||||
|
||||
* update send and connect
|
||||
|
||||
3.3.3 / 2013-07-04
|
||||
==================
|
||||
|
||||
* update connect
|
||||
|
||||
3.3.2 / 2013-07-03
|
||||
==================
|
||||
|
||||
* update connect
|
||||
* update send
|
||||
* remove .version export
|
||||
|
||||
3.3.1 / 2013-06-27
|
||||
3.3.1 / 2013-06-27
|
||||
==================
|
||||
|
||||
* update connect
|
||||
|
||||
3.3.0 / 2013-06-26
|
||||
3.3.0 / 2013-06-26
|
||||
==================
|
||||
|
||||
* update connect
|
||||
@@ -30,12 +801,12 @@
|
||||
* change: return actual booleans from req.accept* functions
|
||||
* fix jsonp callback array throw
|
||||
|
||||
3.2.6 / 2013-06-02
|
||||
3.2.6 / 2013-06-02
|
||||
==================
|
||||
|
||||
* update connect
|
||||
|
||||
3.2.5 / 2013-05-21
|
||||
3.2.5 / 2013-05-21
|
||||
==================
|
||||
|
||||
* update connect
|
||||
@@ -43,23 +814,23 @@
|
||||
* add: throw a meaningful error when there is no default engine
|
||||
* change generation of ETags with res.send() to GET requests only. Closes #1619
|
||||
|
||||
3.2.4 / 2013-05-09
|
||||
3.2.4 / 2013-05-09
|
||||
==================
|
||||
|
||||
|
||||
* fix `req.subdomains` when no Host is present
|
||||
* fix `req.host` when no Host is present, return undefined
|
||||
|
||||
3.2.3 / 2013-05-07
|
||||
3.2.3 / 2013-05-07
|
||||
==================
|
||||
|
||||
* update connect / qs
|
||||
|
||||
3.2.2 / 2013-05-03
|
||||
3.2.2 / 2013-05-03
|
||||
==================
|
||||
|
||||
* update qs
|
||||
|
||||
3.2.1 / 2013-04-29
|
||||
3.2.1 / 2013-04-29
|
||||
==================
|
||||
|
||||
* add app.VERB() paths array deprecation warning
|
||||
@@ -67,27 +838,27 @@
|
||||
* update qs and remove all ~ semver crap
|
||||
* fix: accept number as value of Signed Cookie
|
||||
|
||||
3.2.0 / 2013-04-15
|
||||
3.2.0 / 2013-04-15
|
||||
==================
|
||||
|
||||
* add "view" constructor setting to override view behaviour
|
||||
* add req.acceptsEncoding(name)
|
||||
* add req.acceptedEncodings
|
||||
* revert cookie signature change causing session race conditions
|
||||
* fix sorting of Accept values of the same quality
|
||||
* fix sorting of Accept values of the same quality
|
||||
|
||||
3.1.2 / 2013-04-12
|
||||
3.1.2 / 2013-04-12
|
||||
==================
|
||||
|
||||
* add support for custom Accept parameters
|
||||
* update cookie-signature
|
||||
|
||||
3.1.1 / 2013-04-01
|
||||
3.1.1 / 2013-04-01
|
||||
==================
|
||||
|
||||
* add X-Forwarded-Host support to `req.host`
|
||||
* fix relative redirects
|
||||
* update mkdirp
|
||||
* fix relative redirects
|
||||
* update mkdirp
|
||||
* update buffer-crc32
|
||||
* remove legacy app.configure() method from app template.
|
||||
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2009-2011 TJ Holowaychuk <tj@vision-media.ca>
|
||||
Copyright (c) 2009-2014 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
33
Makefile
33
Makefile
@@ -1,33 +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) \
|
||||
$(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
|
||||
|
||||
benchmark:
|
||||
@./support/bench
|
||||
|
||||
clean:
|
||||
rm -f coverage.html
|
||||
rm -fr lib-cov
|
||||
|
||||
.PHONY: test test-unit test-acceptance benchmark clean
|
||||
193
Readme.md
193
Readme.md
@@ -1,179 +1,128 @@
|
||||

|
||||
[](https://expressjs.com/)
|
||||
|
||||
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org). [](http://travis-ci.org/visionmedia/express) [](https://gemnasium.com/visionmedia/express)
|
||||
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
|
||||
|
||||
[](https://badge.fury.io/js/express)
|
||||
[](https://travis-ci.org/visionmedia/express)
|
||||
[](https://coveralls.io/r/visionmedia/express)
|
||||
[](https://www.gittip.com/dougwilson/)
|
||||
|
||||
```js
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.send('Hello World');
|
||||
});
|
||||
app.get('/', function (req, res) {
|
||||
res.send('Hello World')
|
||||
})
|
||||
|
||||
app.listen(3000);
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
## Installation
|
||||
**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).
|
||||
|
||||
$ npm install -g express
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
$ npm install express
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
|
||||
The quickest way to get started with express is to utilize the executable [`express(1)`](https://github.com/expressjs/generator) to generate an application as shown below:
|
||||
|
||||
Create the app:
|
||||
Install the executable. The executable's major version will match Express's:
|
||||
|
||||
$ npm install -g express
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
```bash
|
||||
$ npm install -g express-generator@4
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
Create the app:
|
||||
|
||||
$ npm install
|
||||
```bash
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
```
|
||||
|
||||
Start the server:
|
||||
Install dependencies:
|
||||
|
||||
$ node app
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Start the server:
|
||||
|
||||
```bash
|
||||
$ npm start
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* Built on [Connect](http://github.com/senchalabs/connect)
|
||||
* Robust routing
|
||||
* HTTP helpers (redirection, caching, etc)
|
||||
* View system supporting 14+ template engines
|
||||
* Content negotiation
|
||||
* Focus on high performance
|
||||
* Environment based configuration
|
||||
* Executable for generating applications quickly
|
||||
* High test coverage
|
||||
|
||||
## Philosophy
|
||||
|
||||
The Express philosophy is to provide small, robust tooling for HTTP servers. Making
|
||||
The Express philosophy is to provide small, robust tooling for HTTP servers, making
|
||||
it a great solution for single page applications, web sites, hybrids, or public
|
||||
HTTP APIs.
|
||||
|
||||
Built on Connect you can use _only_ what you need, and nothing more, applications
|
||||
can be as big or as small as you like, even a single file. 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)
|
||||
Express does not force you to use any specific ORM or template engine. With support for over
|
||||
14 template engines via [Consolidate.js](https://github.com/visionmedia/consolidate.js),
|
||||
you can quickly craft your perfect framework.
|
||||
|
||||
## More Information
|
||||
|
||||
* Join #express on freenode
|
||||
* [Google Group](http://groups.google.com/group/express-js) for discussion
|
||||
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
|
||||
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
|
||||
* [Website and Documentation](http://expressjs.com/) - [[website repo](https://github.com/visionmedia/expressjs.com)]
|
||||
* [Github Organization](https://github.com/expressjs) for Official Middleware & Modules
|
||||
* [#express](https://webchat.freenode.net/?channels=express) on freenode IRC
|
||||
* Visit the [Wiki](https://github.com/visionmedia/express/wiki)
|
||||
* [Google Group](https://groups.google.com/group/express-js) for discussion
|
||||
* [Русскоязычная документация](http://jsman.ru/express/)
|
||||
* [한국어 문서](http://expressjs.kr) - [[website repo](https://github.com/Hanul/expressjs.kr)]
|
||||
* Run express examples [online](https://runnable.com/express)
|
||||
|
||||
## Viewing Examples
|
||||
|
||||
Clone the Express repo, then install the dev dependencies to install all the example / test suite deps:
|
||||
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
|
||||
|
||||
$ git clone git://github.com/visionmedia/express.git --depth 1
|
||||
$ cd express
|
||||
$ npm install
|
||||
```bash
|
||||
$ git clone git://github.com/visionmedia/express.git --depth 1
|
||||
$ cd express
|
||||
$ npm install
|
||||
```
|
||||
|
||||
then run whichever tests you want:
|
||||
Then run whichever example you want:
|
||||
|
||||
$ node examples/content-negotiation
|
||||
|
||||
You can also view live examples here:
|
||||
|
||||
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
|
||||
|
||||
## Running Tests
|
||||
|
||||
To run the test suite first invoke the following command within the repo, installing the development dependencies:
|
||||
To run the test suite, first invoke the following command within the repo, installing the development dependencies:
|
||||
|
||||
$ npm install
|
||||
|
||||
then run the tests:
|
||||
|
||||
$ make test
|
||||
|
||||
## Contributors
|
||||
|
||||
```
|
||||
project: express
|
||||
commits: 3559
|
||||
active : 468 days
|
||||
files : 237
|
||||
authors:
|
||||
1891 Tj Holowaychuk 53.1%
|
||||
1285 visionmedia 36.1%
|
||||
182 TJ Holowaychuk 5.1%
|
||||
54 Aaron Heckmann 1.5%
|
||||
34 csausdev 1.0%
|
||||
26 ciaranj 0.7%
|
||||
21 Robert Sköld 0.6%
|
||||
6 Guillermo Rauch 0.2%
|
||||
3 Dav Glass 0.1%
|
||||
3 Nick Poulden 0.1%
|
||||
2 Randy Merrill 0.1%
|
||||
2 Benny Wong 0.1%
|
||||
2 Hunter Loftis 0.1%
|
||||
2 Jake Gordon 0.1%
|
||||
2 Brian McKinney 0.1%
|
||||
2 Roman Shtylman 0.1%
|
||||
2 Ben Weaver 0.1%
|
||||
2 Dave Hoover 0.1%
|
||||
2 Eivind Fjeldstad 0.1%
|
||||
2 Daniel Shaw 0.1%
|
||||
1 Matt Colyer 0.0%
|
||||
1 Pau Ramon 0.0%
|
||||
1 Pero Pejovic 0.0%
|
||||
1 Peter Rekdal Sunde 0.0%
|
||||
1 Raynos 0.0%
|
||||
1 Teng Siong Ong 0.0%
|
||||
1 Viktor Kelemen 0.0%
|
||||
1 ctide 0.0%
|
||||
1 8bitDesigner 0.0%
|
||||
1 isaacs 0.0%
|
||||
1 mgutz 0.0%
|
||||
1 pikeas 0.0%
|
||||
1 shuwatto 0.0%
|
||||
1 tstrimple 0.0%
|
||||
1 ewoudj 0.0%
|
||||
1 Adam Sanderson 0.0%
|
||||
1 Andrii Kostenko 0.0%
|
||||
1 Andy Hiew 0.0%
|
||||
1 Arpad Borsos 0.0%
|
||||
1 Ashwin Purohit 0.0%
|
||||
1 Benjen 0.0%
|
||||
1 Darren Torpey 0.0%
|
||||
1 Greg Ritter 0.0%
|
||||
1 Gregory Ritter 0.0%
|
||||
1 James Herdman 0.0%
|
||||
1 Jim Snodgrass 0.0%
|
||||
1 Joe McCann 0.0%
|
||||
1 Jonathan Dumaine 0.0%
|
||||
1 Jonathan Palardy 0.0%
|
||||
1 Jonathan Zacsh 0.0%
|
||||
1 Justin Lilly 0.0%
|
||||
1 Ken Sato 0.0%
|
||||
1 Maciej Małecki 0.0%
|
||||
1 Masahiro Hayashi 0.0%
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
## License
|
||||
Then run the tests:
|
||||
|
||||
(The MIT License)
|
||||
```bash
|
||||
$ npm test
|
||||
```
|
||||
|
||||
Copyright (c) 2009-2012 TJ Holowaychuk <tj@vision-media.ca>
|
||||
### Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
* Author: [TJ Holowaychuk](https://github.com/visionmedia)
|
||||
* Lead Maintainer: [Douglas Christopher Wilson](https://github.com/dougwilson)
|
||||
* [All Contributors](https://github.com/visionmedia/express/graphs/contributors)
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
### License
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
[MIT](LICENSE)
|
||||
|
||||
13
benchmarks/Makefile
Normal file
13
benchmarks/Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
all:
|
||||
@./run 1 middleware
|
||||
@./run 5 middleware
|
||||
@./run 10 middleware
|
||||
@./run 15 middleware
|
||||
@./run 20 middleware
|
||||
@./run 30 middleware
|
||||
@./run 50 middleware
|
||||
@./run 100 middleware
|
||||
@echo
|
||||
|
||||
.PHONY: all
|
||||
23
benchmarks/middleware.js
Normal file
23
benchmarks/middleware.js
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
var http = require('http');
|
||||
var express = require('..');
|
||||
var app = express();
|
||||
|
||||
// number of middleware
|
||||
|
||||
var n = parseInt(process.env.MW || '1', 10);
|
||||
console.log(' %s middleware', n);
|
||||
|
||||
while (n--) {
|
||||
app.use(function(req, res, next){
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
var body = new Buffer('Hello World');
|
||||
|
||||
app.use(function(req, res, next){
|
||||
res.send(body);
|
||||
});
|
||||
|
||||
app.listen(3333);
|
||||
16
benchmarks/run
Executable file
16
benchmarks/run
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo
|
||||
MW=$1 node $2 &
|
||||
pid=$!
|
||||
|
||||
sleep 2
|
||||
|
||||
wrk 'http://localhost:3333/?foo[bar]=baz' \
|
||||
-d 3 \
|
||||
-c 50 \
|
||||
-t 8 \
|
||||
| grep 'Requests/sec' \
|
||||
| awk '{ print " " $2 }'
|
||||
|
||||
kill $pid
|
||||
422
bin/express
422
bin/express
@@ -1,422 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var exec = require('child_process').exec
|
||||
, program = require('commander')
|
||||
, mkdirp = require('mkdirp')
|
||||
, pkg = require('../package.json')
|
||||
, version = pkg.version
|
||||
, os = require('os')
|
||||
, fs = require('fs');
|
||||
|
||||
// CLI
|
||||
|
||||
program
|
||||
.version(version)
|
||||
.option('-s, --sessions', 'add session support')
|
||||
.option('-e, --ejs', 'add ejs engine support (defaults to jade)')
|
||||
.option('-J, --jshtml', 'add jshtml engine support (defaults to jade)')
|
||||
.option('-H, --hogan', 'add hogan.js engine support')
|
||||
.option('-c, --css <engine>', 'add stylesheet <engine> support (less|stylus) (defaults to plain css)')
|
||||
.option('-f, --force', 'force on non-empty directory')
|
||||
.parse(process.argv);
|
||||
|
||||
// Path
|
||||
|
||||
var path = program.args.shift() || '.';
|
||||
|
||||
// end-of-line code
|
||||
|
||||
var eol = os.EOL
|
||||
|
||||
// Template engine
|
||||
|
||||
program.template = 'jade';
|
||||
if (program.ejs) program.template = 'ejs';
|
||||
if (program.jshtml) program.template = 'jshtml';
|
||||
if (program.hogan) program.template = 'hjs';
|
||||
|
||||
/**
|
||||
* Routes index template.
|
||||
*/
|
||||
|
||||
var index = [
|
||||
''
|
||||
, '/*'
|
||||
, ' * GET home page.'
|
||||
, ' */'
|
||||
, ''
|
||||
, 'exports.index = function(req, res){'
|
||||
, ' res.render(\'index\', { title: \'Express\' });'
|
||||
, '};'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* Routes users template.
|
||||
*/
|
||||
|
||||
var users = [
|
||||
''
|
||||
, '/*'
|
||||
, ' * GET users listing.'
|
||||
, ' */'
|
||||
, ''
|
||||
, 'exports.list = function(req, res){'
|
||||
, ' res.send("respond with a resource");'
|
||||
, '};'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* Jade layout template.
|
||||
*/
|
||||
|
||||
var jadeLayout = [
|
||||
'doctype 5'
|
||||
, 'html'
|
||||
, ' head'
|
||||
, ' title= title'
|
||||
, ' link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')'
|
||||
, ' body'
|
||||
, ' block content'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* Jade index template.
|
||||
*/
|
||||
|
||||
var jadeIndex = [
|
||||
'extends layout'
|
||||
, ''
|
||||
, 'block content'
|
||||
, ' h1= title'
|
||||
, ' p Welcome to #{title}'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* EJS index template.
|
||||
*/
|
||||
|
||||
var ejsIndex = [
|
||||
'<!DOCTYPE html>'
|
||||
, '<html>'
|
||||
, ' <head>'
|
||||
, ' <title><%= title %></title>'
|
||||
, ' <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
|
||||
, ' </head>'
|
||||
, ' <body>'
|
||||
, ' <h1><%= title %></h1>'
|
||||
, ' <p>Welcome to <%= title %></p>'
|
||||
, ' </body>'
|
||||
, '</html>'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* JSHTML layout template.
|
||||
*/
|
||||
|
||||
var jshtmlLayout = [
|
||||
'<!DOCTYPE html>'
|
||||
, '<html>'
|
||||
, ' <head>'
|
||||
, ' <title> @write(title) </title>'
|
||||
, ' <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
|
||||
, ' </head>'
|
||||
, ' <body>'
|
||||
, ' @write(body)'
|
||||
, ' </body>'
|
||||
, '</html>'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* JSHTML index template.
|
||||
*/
|
||||
|
||||
var jshtmlIndex = [
|
||||
'<h1>@write(title)</h1>'
|
||||
, '<p>Welcome to @write(title)</p>'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* Hogan.js index template.
|
||||
*/
|
||||
var hoganIndex = [
|
||||
'<!DOCTYPE html>'
|
||||
, '<html>'
|
||||
, ' <head>'
|
||||
, ' <title>{{ title }}</title>'
|
||||
, ' <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
|
||||
, ' </head>'
|
||||
, ' <body>'
|
||||
, ' <h1>{{ title }}</h1>'
|
||||
, ' <p>Welcome to {{ title }}</p>'
|
||||
, ' </body>'
|
||||
, '</html>'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* Default css template.
|
||||
*/
|
||||
|
||||
var css = [
|
||||
'body {'
|
||||
, ' padding: 50px;'
|
||||
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
|
||||
, '}'
|
||||
, ''
|
||||
, 'a {'
|
||||
, ' color: #00B7FF;'
|
||||
, '}'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* Default less template.
|
||||
*/
|
||||
|
||||
var less = [
|
||||
'body {'
|
||||
, ' padding: 50px;'
|
||||
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
|
||||
, '}'
|
||||
, ''
|
||||
, 'a {'
|
||||
, ' color: #00B7FF;'
|
||||
, '}'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* Default stylus template.
|
||||
*/
|
||||
|
||||
var stylus = [
|
||||
'body'
|
||||
, ' padding: 50px'
|
||||
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif'
|
||||
, 'a'
|
||||
, ' color: #00B7FF'
|
||||
].join(eol);
|
||||
|
||||
/**
|
||||
* App template.
|
||||
*/
|
||||
|
||||
var app = [
|
||||
''
|
||||
, '/**'
|
||||
, ' * Module dependencies.'
|
||||
, ' */'
|
||||
, ''
|
||||
, 'var express = require(\'express\')'
|
||||
, ' , routes = require(\'./routes\')'
|
||||
, ' , user = require(\'./routes/user\')'
|
||||
, ' , http = require(\'http\')'
|
||||
, ' , path = require(\'path\');'
|
||||
, ''
|
||||
, 'var app = express();'
|
||||
, ''
|
||||
, '// all environments'
|
||||
, 'app.set(\'port\', process.env.PORT || 3000);'
|
||||
, 'app.set(\'views\', __dirname + \'/views\');'
|
||||
, 'app.set(\'view engine\', \':TEMPLATE\');'
|
||||
, 'app.use(express.favicon());'
|
||||
, 'app.use(express.logger(\'dev\'));'
|
||||
, 'app.use(express.bodyParser());'
|
||||
, 'app.use(express.methodOverride());{sess}'
|
||||
, 'app.use(app.router);{css}'
|
||||
, 'app.use(express.static(path.join(__dirname, \'public\')));'
|
||||
, ''
|
||||
, '// development only'
|
||||
, 'if (\'development\' == app.get(\'env\')) {'
|
||||
, ' app.use(express.errorHandler());'
|
||||
, '}'
|
||||
, ''
|
||||
, 'app.get(\'/\', routes.index);'
|
||||
, 'app.get(\'/users\', user.list);'
|
||||
, ''
|
||||
, 'http.createServer(app).listen(app.get(\'port\'), function(){'
|
||||
, ' console.log(\'Express server listening on port \' + app.get(\'port\'));'
|
||||
, '});'
|
||||
, ''
|
||||
].join(eol);
|
||||
|
||||
// Generate application
|
||||
|
||||
(function createApplication(path) {
|
||||
emptyDirectory(path, function(empty){
|
||||
if (empty || program.force) {
|
||||
createApplicationAt(path);
|
||||
} else {
|
||||
program.confirm('destination is not empty, continue? ', function(ok){
|
||||
if (ok) {
|
||||
process.stdin.destroy();
|
||||
createApplicationAt(path);
|
||||
} else {
|
||||
abort('aborting');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})(path);
|
||||
|
||||
/**
|
||||
* Create application at the given directory `path`.
|
||||
*
|
||||
* @param {String} path
|
||||
*/
|
||||
|
||||
function createApplicationAt(path) {
|
||||
console.log();
|
||||
process.on('exit', function(){
|
||||
console.log();
|
||||
console.log(' install dependencies:');
|
||||
console.log(' $ cd %s && npm install', path);
|
||||
console.log();
|
||||
console.log(' run the app:');
|
||||
console.log(' $ node app');
|
||||
console.log();
|
||||
});
|
||||
|
||||
mkdir(path, function(){
|
||||
mkdir(path + '/public');
|
||||
mkdir(path + '/public/javascripts');
|
||||
mkdir(path + '/public/images');
|
||||
mkdir(path + '/public/stylesheets', function(){
|
||||
switch (program.css) {
|
||||
case 'less':
|
||||
write(path + '/public/stylesheets/style.less', less);
|
||||
break;
|
||||
case 'stylus':
|
||||
write(path + '/public/stylesheets/style.styl', stylus);
|
||||
break;
|
||||
default:
|
||||
write(path + '/public/stylesheets/style.css', css);
|
||||
}
|
||||
});
|
||||
|
||||
mkdir(path + '/routes', function(){
|
||||
write(path + '/routes/index.js', index);
|
||||
write(path + '/routes/user.js', users);
|
||||
});
|
||||
|
||||
mkdir(path + '/views', function(){
|
||||
switch (program.template) {
|
||||
case 'ejs':
|
||||
write(path + '/views/index.ejs', ejsIndex);
|
||||
break;
|
||||
case 'jade':
|
||||
write(path + '/views/layout.jade', jadeLayout);
|
||||
write(path + '/views/index.jade', jadeIndex);
|
||||
break;
|
||||
case 'jshtml':
|
||||
write(path + '/views/layout.jshtml', jshtmlLayout);
|
||||
write(path + '/views/index.jshtml', jshtmlIndex);
|
||||
break;
|
||||
case 'hjs':
|
||||
write(path + '/views/index.hjs', hoganIndex);
|
||||
break;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// CSS Engine support
|
||||
switch (program.css) {
|
||||
case 'less':
|
||||
app = app.replace('{css}', eol + 'app.use(require(\'less-middleware\')({ src: __dirname + \'/public\' }));');
|
||||
break;
|
||||
case 'stylus':
|
||||
app = app.replace('{css}', eol + 'app.use(require(\'stylus\').middleware(__dirname + \'/public\'));');
|
||||
break;
|
||||
default:
|
||||
app = app.replace('{css}', '');
|
||||
}
|
||||
|
||||
// Session support
|
||||
app = app.replace('{sess}', program.sessions
|
||||
? eol + 'app.use(express.cookieParser(\'your secret here\'));' + eol + 'app.use(express.session());'
|
||||
: '');
|
||||
|
||||
// Template support
|
||||
app = app.replace(':TEMPLATE', program.template);
|
||||
|
||||
// package.json
|
||||
var pkg = {
|
||||
name: 'application-name'
|
||||
, version: '0.0.1'
|
||||
, private: true
|
||||
, scripts: { start: 'node app.js' }
|
||||
, dependencies: {
|
||||
express: version
|
||||
}
|
||||
}
|
||||
|
||||
if (program.template) pkg.dependencies[program.template] = '*';
|
||||
|
||||
// CSS Engine support
|
||||
switch (program.css) {
|
||||
case 'less':
|
||||
pkg.dependencies['less-middleware'] = '*';
|
||||
break;
|
||||
default:
|
||||
if (program.css) {
|
||||
pkg.dependencies[program.css] = '*';
|
||||
}
|
||||
}
|
||||
|
||||
write(path + '/package.json', JSON.stringify(pkg, null, 2));
|
||||
write(path + '/app.js', app);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given directory `path` is empty.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Function} fn
|
||||
*/
|
||||
|
||||
function emptyDirectory(path, fn) {
|
||||
fs.readdir(path, function(err, files){
|
||||
if (err && 'ENOENT' != err.code) throw err;
|
||||
fn(!files || !files.length);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* echo str > path.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {String} str
|
||||
*/
|
||||
|
||||
function write(path, str) {
|
||||
fs.writeFile(path, str);
|
||||
console.log(' \x1b[36mcreate\x1b[0m : ' + path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mkdir -p.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Function} fn
|
||||
*/
|
||||
|
||||
function mkdir(path, fn) {
|
||||
mkdirp(path, 0755, function(err){
|
||||
if (err) throw err;
|
||||
console.log(' \033[36mcreate\033[0m : ' + path);
|
||||
fn && fn();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit with the given `str`.
|
||||
*
|
||||
* @param {String} str
|
||||
*/
|
||||
|
||||
function abort(str) {
|
||||
console.error(str);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -2,8 +2,10 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, hash = require('./pass').hash;
|
||||
var express = require('../..');
|
||||
var hash = require('./pass').hash;
|
||||
var bodyParser = require('body-parser');
|
||||
var session = require('express-session');
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
@@ -14,15 +16,18 @@ app.set('views', __dirname + '/views');
|
||||
|
||||
// middleware
|
||||
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.cookieParser('shhhh, very secret'));
|
||||
app.use(express.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
|
||||
|
||||
app.use(function(req, res, next){
|
||||
var err = req.session.error
|
||||
, msg = req.session.success;
|
||||
var err = req.session.error;
|
||||
var msg = req.session.success;
|
||||
delete req.session.error;
|
||||
delete req.session.success;
|
||||
res.locals.message = '';
|
||||
@@ -62,7 +67,7 @@ function authenticate(name, pass, fn) {
|
||||
if (err) return fn(err);
|
||||
if (hash == user.hash) return fn(null, user);
|
||||
fn(new Error('invalid password'));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function restrict(req, res, next) {
|
||||
@@ -75,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){
|
||||
@@ -98,9 +103,9 @@ app.post('/login', function(req, res){
|
||||
authenticate(req.body.username, req.body.password, function(err, user){
|
||||
if (user) {
|
||||
// Regenerate session when signing in
|
||||
// to prevent fixation
|
||||
// to prevent fixation
|
||||
req.session.regenerate(function(){
|
||||
// Store the user's primary key
|
||||
// Store the user's primary key
|
||||
// in the session store to be retrieved,
|
||||
// or in this case the entire user object
|
||||
req.session.user = user;
|
||||
@@ -113,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');
|
||||
|
||||
@@ -32,7 +32,7 @@ var iterations = 12000;
|
||||
exports.hash = function (pwd, salt, fn) {
|
||||
if (3 == arguments.length) {
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash){
|
||||
fn(err, (new Buffer(hash, 'binary')).toString('base64'));
|
||||
fn(err, hash.toString('base64'));
|
||||
});
|
||||
} else {
|
||||
fn = salt;
|
||||
@@ -41,8 +41,8 @@ exports.hash = function (pwd, salt, fn) {
|
||||
salt = salt.toString('base64');
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash){
|
||||
if (err) return fn(err);
|
||||
fn(null, salt, (new Buffer(hash, 'binary')).toString('base64'));
|
||||
fn(null, salt, hash.toString('base64'));
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, app = express();
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var app = express();
|
||||
|
||||
app.set('views', __dirname);
|
||||
app.set('view engine', 'jade');
|
||||
@@ -14,11 +18,14 @@ while (n--) {
|
||||
pets.push({ name: 'Jane', age: 6, species: 'ferret' });
|
||||
}
|
||||
|
||||
app.use(express.logger('dev'));
|
||||
app.use(logger('dev'));
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
var users = [];
|
||||
|
||||
users.push({ name: 'Tobi' });
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
var express = require('../../');
|
||||
var app = module.exports = express();
|
||||
var users = require('./db');
|
||||
|
||||
var express = require('../../')
|
||||
, app = module.exports = express()
|
||||
, users = require('./db');
|
||||
|
||||
// so either you can deal with different types of formatting
|
||||
// for expected response in index.js
|
||||
app.get('/', function(req, res){
|
||||
res.format({
|
||||
html: function(){
|
||||
@@ -20,22 +21,24 @@ app.get('/', function(req, res){
|
||||
json: function(){
|
||||
res.json(users);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// or you could write a tiny middleware like
|
||||
// this to abstract make things a bit more declarative:
|
||||
// this to add a layer of abstraction
|
||||
// and make things a bit more declarative:
|
||||
|
||||
function format(mod) {
|
||||
var obj = require(mod);
|
||||
function format(path) {
|
||||
var obj = require(path);
|
||||
return function(req, res){
|
||||
res.format(obj);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -7,14 +6,8 @@ var express = require('../../');
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
// ignore GET /favicon.ico
|
||||
app.use(express.favicon());
|
||||
|
||||
// pass a secret to cookieParser() for signed cookies
|
||||
app.use(express.cookieParser('manny is cool'));
|
||||
|
||||
// add req.session cookie support
|
||||
app.use(express.cookieSession());
|
||||
app.use(cookieSession({ secret: 'manny is cool' }));
|
||||
|
||||
// do something with the session
|
||||
app.use(count);
|
||||
@@ -26,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');
|
||||
}
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../')
|
||||
, app = module.exports = express();
|
||||
|
||||
|
||||
// add favicon() before logger() so
|
||||
// GET /favicon.ico requests are not
|
||||
// logged, because this middleware
|
||||
// reponds to /favicon.ico and does not
|
||||
// call next()
|
||||
app.use(express.favicon());
|
||||
var express = require('../../');
|
||||
var app = module.exports = express();
|
||||
var logger = require('morgan');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var bodyParser = require('body-parser');
|
||||
|
||||
// custom log format
|
||||
if ('test' != process.env.NODE_ENV)
|
||||
app.use(express.logger(':method :url'));
|
||||
if ('test' != process.env.NODE_ENV) app.use(logger(':method :url'));
|
||||
|
||||
// parses request cookies, populating
|
||||
// req.cookies and req.signedCookies
|
||||
// when the secret is passed, used
|
||||
// when the secret is passed, used
|
||||
// for signing the cookies.
|
||||
app.use(express.cookieParser('my secret here'));
|
||||
app.use(cookieParser('my secret here'));
|
||||
|
||||
// parses json, x-www-form-urlencoded, and multipart/form-data
|
||||
app.use(express.bodyParser());
|
||||
// parses x-www-form-urlencoded
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
|
||||
app.get('/', function(req, res){
|
||||
if (req.cookies.remember) {
|
||||
@@ -48,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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, app = express()
|
||||
, api = express();
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var app = express();
|
||||
var bodyParser = require('body-parser');
|
||||
var api = express();
|
||||
|
||||
// app middleware
|
||||
|
||||
@@ -12,8 +14,8 @@ app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// api middleware
|
||||
|
||||
api.use(express.logger('dev'));
|
||||
api.use(express.bodyParser());
|
||||
api.use(logger('dev'));
|
||||
api.use(bodyParser.json());
|
||||
|
||||
/**
|
||||
* CORS support.
|
||||
@@ -23,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);
|
||||
@@ -31,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>
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../')
|
||||
, app = module.exports = express();
|
||||
var express = require('../../');
|
||||
var app = module.exports = express();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.send('<ul>'
|
||||
+ '<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>');
|
||||
});
|
||||
|
||||
// /files/* is accessed via req.params[0]
|
||||
// but here we name it :file
|
||||
app.get('/files/:file(*)', function(req, res, next){
|
||||
var file = req.params.file
|
||||
, path = __dirname + '/files/' + file;
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
1
examples/downloads/files/utf-8 한中日.txt
Normal file
1
examples/downloads/files/utf-8 한中日.txt
Normal file
@@ -0,0 +1 @@
|
||||
한中日
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -44,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');
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../')
|
||||
, app = module.exports = express()
|
||||
, silent = 'test' == process.env.NODE_ENV;
|
||||
var express = require('../../');
|
||||
var app = module.exports = express();
|
||||
var logger = require('morgan');
|
||||
var silent = 'test' == process.env.NODE_ENV;
|
||||
|
||||
// general config
|
||||
app.set('views', __dirname + '/views');
|
||||
@@ -17,22 +18,36 @@ 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');
|
||||
|
||||
app.use(express.favicon());
|
||||
silent || app.use(logger('dev'));
|
||||
|
||||
silent || app.use(express.logger('dev'));
|
||||
// Routes
|
||||
|
||||
// "app.router" positions our routes
|
||||
// above the middleware defined below,
|
||||
// this means that Express will attempt
|
||||
// to match & call routes _before_ continuing
|
||||
// on, at which point we assume it's a 404 because
|
||||
// no route has handled the request.
|
||||
app.get('/', function(req, res){
|
||||
res.render('index.jade');
|
||||
});
|
||||
|
||||
app.use(app.router);
|
||||
app.get('/404', function(req, res, next){
|
||||
// trigger a 404 since no other middleware
|
||||
// will match /404 after this one, and we're not
|
||||
// responding here
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/403', function(req, res, next){
|
||||
// trigger a 403 error
|
||||
var err = new Error('not allowed!');
|
||||
err.status = 403;
|
||||
next(err);
|
||||
});
|
||||
|
||||
app.get('/500', function(req, res, next){
|
||||
// trigger a generic (500) error
|
||||
next(new Error('keyboard cat!'));
|
||||
});
|
||||
|
||||
// Error handlers
|
||||
|
||||
// Since this is the last non-error-handling
|
||||
// middleware use()d, we assume 404, as nothing else
|
||||
@@ -44,7 +59,7 @@ app.use(app.router);
|
||||
|
||||
app.use(function(req, res, next){
|
||||
res.status(404);
|
||||
|
||||
|
||||
// respond with html page
|
||||
if (req.accepts('html')) {
|
||||
res.render('404', { url: req.url });
|
||||
@@ -81,32 +96,9 @@ app.use(function(err, req, res, next){
|
||||
res.render('500', { error: err });
|
||||
});
|
||||
|
||||
// Routes
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('index.jade');
|
||||
});
|
||||
|
||||
app.get('/404', function(req, res, next){
|
||||
// trigger a 404 since no other middleware
|
||||
// will match /404 after this one, and we're not
|
||||
// responding here
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/403', function(req, res, next){
|
||||
// trigger a 403 error
|
||||
var err = new Error('not allowed!');
|
||||
err.status = 403;
|
||||
next(err);
|
||||
});
|
||||
|
||||
app.get('/500', function(req, res, next){
|
||||
// trigger a generic (500) error
|
||||
next(new Error('keyboard cat!'));
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
silent || console.log('Express started on port 3000');
|
||||
}
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../')
|
||||
, app = module.exports = express()
|
||||
, test = app.get('env') == 'test';
|
||||
var express = require('../../');
|
||||
var logger = require('morgan');
|
||||
var app = module.exports = express();
|
||||
var test = app.get('env') == 'test';
|
||||
|
||||
if (!test) app.use(express.logger('dev'));
|
||||
app.use(app.router);
|
||||
|
||||
// the error handler is strategically
|
||||
// placed *below* the app.router; if it
|
||||
// were above it would not receive errors
|
||||
// from app.get() etc
|
||||
app.use(error);
|
||||
if (!test) app.use(logger('dev'));
|
||||
|
||||
// error handling middleware have an arity of 4
|
||||
// instead of the typical (req, res, next),
|
||||
@@ -42,7 +35,13 @@ app.get('/next', function(req, res, next){
|
||||
});
|
||||
});
|
||||
|
||||
// the error handler is placed after routes
|
||||
// if it were above it would not receive errors
|
||||
// from app.get() etc
|
||||
app.use(error);
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
var express = require('../..')
|
||||
, app = express();
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var app = express();
|
||||
|
||||
app.set('view engine', 'jade');
|
||||
app.set('views', __dirname + '/views');
|
||||
@@ -20,10 +21,10 @@ User.prototype.toJSON = function(){
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
app.use(express.logger('dev'));
|
||||
app.use(logger('dev'));
|
||||
|
||||
// earlier on expose an object
|
||||
// that we can tack properties on.
|
||||
@@ -56,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');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
var express = require('../../');
|
||||
|
||||
var app = express();
|
||||
@@ -7,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');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -12,9 +11,7 @@ var pub = __dirname + '/public';
|
||||
// setup middleware
|
||||
|
||||
var app = express();
|
||||
app.use(app.router);
|
||||
app.use(express.static(pub));
|
||||
app.use(express.errorHandler());
|
||||
|
||||
// Optional since express defaults to CWD/views
|
||||
|
||||
@@ -41,5 +38,14 @@ app.get('/', function(req, res){
|
||||
res.render('users', { users: users });
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
// change this to a better error handler in your code
|
||||
// sending stacktrace to users in production is not good
|
||||
app.use(function(err, req, res, next) {
|
||||
res.send(err.stack);
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
!!! 5
|
||||
doctype html
|
||||
html
|
||||
include header
|
||||
body
|
||||
block content
|
||||
block content
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, fs = require('fs')
|
||||
, md = require('marked').parse;
|
||||
var express = require('../..');
|
||||
var fs = require('fs');
|
||||
var md = require('marked').parse;
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
@@ -18,13 +17,13 @@ app.engine('md', function(path, options, fn){
|
||||
var html = md(str);
|
||||
html = html.replace(/\{([^}]+)\}/g, function(_, name){
|
||||
return options[name] || '';
|
||||
})
|
||||
});
|
||||
fn(null, html);
|
||||
} catch(err) {
|
||||
fn(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
app.set('views', __dirname + '/views');
|
||||
|
||||
@@ -33,12 +32,13 @@ app.set('view engine', 'md');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('index', { title: 'Markdown Example' });
|
||||
})
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, format = require('util').format;
|
||||
var express = require('../..');
|
||||
var multiparty = require('multiparty');
|
||||
var format = require('util').format;
|
||||
|
||||
var app = module.exports = express()
|
||||
|
||||
// bodyParser in connect 2.x uses node-formidable to parse
|
||||
// the multipart form data.
|
||||
app.use(express.bodyParser())
|
||||
var app = module.exports = express();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.send('<form method="post" enctype="multipart/form-data">'
|
||||
@@ -21,16 +17,44 @@ app.get('/', function(req, res){
|
||||
});
|
||||
|
||||
app.post('/', function(req, res, next){
|
||||
// the uploaded file can be found as `req.files.image` and the
|
||||
// title field as `req.body.title`
|
||||
res.send(format('\nuploaded %s (%d Kb) to %s as %s'
|
||||
, req.files.image.name
|
||||
, req.files.image.size / 1024 | 0
|
||||
, req.files.image.path
|
||||
, req.body.title));
|
||||
// create a form to begin parsing
|
||||
var form = new multiparty.Form();
|
||||
var image;
|
||||
var title;
|
||||
|
||||
form.on('error', next);
|
||||
form.on('close', function(){
|
||||
res.send(format('\nuploaded %s (%d Kb) as %s'
|
||||
, image.filename
|
||||
, image.size / 1024 | 0
|
||||
, title));
|
||||
});
|
||||
|
||||
// listen on field event for title
|
||||
form.on('field', function(name, val){
|
||||
if (name !== 'title') return;
|
||||
title = val;
|
||||
});
|
||||
|
||||
// listen on part event for image file
|
||||
form.on('part', function(part){
|
||||
if (!part.filename) return;
|
||||
if (part.name !== 'image') return part.resume();
|
||||
image = {};
|
||||
image.filename = part.filename;
|
||||
image.size = 0;
|
||||
part.on('data', function(buf){
|
||||
image.size += buf.length;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// parse the form
|
||||
form.parse(req);
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
app.listen(4000);
|
||||
console.log('Express started on port 4000');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
exports.index = function(req, res){
|
||||
res.redirect('/users');
|
||||
};
|
||||
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var db = require('../../db');
|
||||
|
||||
exports.engine = 'jade';
|
||||
|
||||
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();
|
||||
};
|
||||
@@ -20,7 +21,7 @@ exports.edit = function(req, res, next){
|
||||
|
||||
exports.update = function(req, res, next){
|
||||
var body = req.body;
|
||||
req.pet.name = body.user.name;
|
||||
req.pet.name = body.pet.name;
|
||||
res.message('Information updated!');
|
||||
res.redirect('/pet/' + req.pet.id);
|
||||
};
|
||||
|
||||
@@ -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')
|
||||
label Name:
|
||||
input(type='text', name='user[name]', value=pet.name)
|
||||
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')
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var db = require('../../db');
|
||||
|
||||
@@ -8,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);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var db = require('../../db');
|
||||
|
||||
@@ -8,11 +11,11 @@ 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();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.list = function(req, res, next){
|
||||
res.render('list', { users: db.users });
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<h1><%= user.name %></h1>
|
||||
<form action='/user/<%= user.id %>' method='post'>
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<label>Name: <input type="text" name="user[name]" value="<%= user.name %>" /></label>
|
||||
<input type="submit" value="Update" />
|
||||
</form>
|
||||
|
||||
<form action='/user/<%= user.id %>/pet' method='post'>
|
||||
<label>Pet: <input type="text" name="pet[name]" placeholder="name" /></label>
|
||||
<input type="submit" value="Add" />
|
||||
</form>
|
||||
11
examples/mvc/controllers/user/views/edit.jade
Normal file
11
examples/mvc/controllers/user/views/edit.jade
Normal file
@@ -0,0 +1,11 @@
|
||||
link(rel='stylesheet', href='/style.css')
|
||||
h1= user.name
|
||||
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')
|
||||
|
||||
form(action='/user/#{user.id}/pet', method='post')
|
||||
label= 'Pet: '
|
||||
input(type='text', name='pet[name]', placeholder='Name')
|
||||
input(type='submit', value='Add')
|
||||
@@ -1,8 +0,0 @@
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<h1>Users</h1>
|
||||
<p>Click a user below to view their pets.</p>
|
||||
<ul>
|
||||
<% users.forEach(function(user){ %>
|
||||
<li><a href="/user/<%= user.id %>"><%= user.name %></a></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
7
examples/mvc/controllers/user/views/list.jade
Normal file
7
examples/mvc/controllers/user/views/list.jade
Normal file
@@ -0,0 +1,7 @@
|
||||
link(rel='stylesheet', href='/style.css')
|
||||
h1 Users
|
||||
p Click a user below to view their pets.
|
||||
ul
|
||||
each user in users
|
||||
li
|
||||
a(href='/user/#{user.id}')= user.name
|
||||
@@ -1,21 +0,0 @@
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<h1><%= user.name %> <a href="/user/<%= user.id %>/edit">edit</a></h1>
|
||||
|
||||
<% if (hasMessages) { %>
|
||||
<ul id="messages">
|
||||
<% messages.forEach(function(msg){ %>
|
||||
<li><%= msg %></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% } %>
|
||||
|
||||
<% if (user.pets.length) { %>
|
||||
<p>View <%= user.name %>s pets:</p>
|
||||
<ul>
|
||||
<% user.pets.forEach(function(pet){ %>
|
||||
<li><a href="/pet/<%= pet.id %>"><%= pet.name %></a></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% } else { %>
|
||||
<p>No pets!</p>
|
||||
<% } %>
|
||||
17
examples/mvc/controllers/user/views/show.jade
Normal file
17
examples/mvc/controllers/user/views/show.jade
Normal file
@@ -0,0 +1,17 @@
|
||||
link(rel='stylesheet', href='/style.css')
|
||||
h1= user.name + ' '
|
||||
a(href='/user/#{user.id}/edit') edit
|
||||
|
||||
if (hasMessages)
|
||||
ul#messages
|
||||
each msg in messages
|
||||
li= msg
|
||||
|
||||
if (user.pets.length)
|
||||
p View #{user.name}'s pets:
|
||||
ul
|
||||
each pet in user.pets
|
||||
li
|
||||
a(href='/pet/#{pet.id}')= pet.name
|
||||
else
|
||||
p No pets!
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// faux database
|
||||
|
||||
var pets = exports.pets = [];
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var session = require('express-session');
|
||||
var bodyParser = require('body-parser');
|
||||
var methodOverride = require('method-override');
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
// settings
|
||||
|
||||
// map .renderFile to ".html" files
|
||||
app.engine('html', require('ejs').renderFile);
|
||||
|
||||
// make ".html" the default
|
||||
app.set('view engine', 'html');
|
||||
// set our default template engine to "jade"
|
||||
// which prevents the need for extensions
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
// set views for error and 404 pages
|
||||
app.set('views', __dirname + '/views');
|
||||
@@ -25,20 +31,23 @@ app.response.message = function(msg){
|
||||
};
|
||||
|
||||
// log
|
||||
if (!module.parent) app.use(express.logger('dev'));
|
||||
if (!module.parent) app.use(logger('dev'));
|
||||
|
||||
// serve static files
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// session support
|
||||
app.use(express.cookieParser('some secret here'));
|
||||
app.use(express.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(express.bodyParser());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
// support _method (PUT in forms etc)
|
||||
app.use(express.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){
|
||||
@@ -66,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');
|
||||
@@ -86,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');
|
||||
}
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../..')
|
||||
, fs = require('fs');
|
||||
var express = require('../../..');
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = function(parent, options){
|
||||
var verbose = options.verbose;
|
||||
fs.readdirSync(__dirname + '/../controllers').forEach(function(name){
|
||||
verbose && console.log('\n %s:', name);
|
||||
var obj = require('./../controllers/' + name)
|
||||
, name = obj.name || name
|
||||
, prefix = obj.prefix || ''
|
||||
, app = express()
|
||||
, method
|
||||
, path;
|
||||
var obj = require('./../controllers/' + name);
|
||||
var name = obj.name || name;
|
||||
var prefix = obj.prefix || '';
|
||||
var app = express();
|
||||
var handler;
|
||||
var method;
|
||||
var path;
|
||||
|
||||
// allow specifying the view engine
|
||||
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) {
|
||||
@@ -59,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);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<h1>404: Not Found</h1>
|
||||
<p>Sorry we can't find <%= url %></p>
|
||||
3
examples/mvc/views/404.jade
Normal file
3
examples/mvc/views/404.jade
Normal file
@@ -0,0 +1,3 @@
|
||||
link(rel='stylesheet', href='/style.css')
|
||||
h1 404: Not Found
|
||||
p Sorry we can't find #{url}
|
||||
@@ -1,3 +0,0 @@
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<h1>500: Internal Server Error</h1>
|
||||
<p>Looks like something blew up!</p>
|
||||
3
examples/mvc/views/5xx.jade
Normal file
3
examples/mvc/views/5xx.jade
Normal file
@@ -0,0 +1,3 @@
|
||||
link(rel='stylesheet', href='/style.css')
|
||||
h1 500: Internal Server Error
|
||||
p Looks like something blew up!
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// first:
|
||||
// $ npm install redis online
|
||||
// $ redis-server
|
||||
@@ -7,10 +6,10 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, online = require('online')
|
||||
, redis = require('redis')
|
||||
, db = redis.createClient();
|
||||
var express = require('../..');
|
||||
var online = require('online');
|
||||
var redis = require('redis');
|
||||
var db = redis.createClient();
|
||||
|
||||
// online
|
||||
|
||||
@@ -50,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');
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../')
|
||||
, app = module.exports = express();
|
||||
var express = require('../../');
|
||||
var app = module.exports = express();
|
||||
|
||||
// Faux database
|
||||
|
||||
@@ -18,9 +17,9 @@ 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) ){
|
||||
app.param(['to', 'from'], function(req, res, next, num, name){
|
||||
req.params[name] = parseInt(num, 10);
|
||||
if( isNaN(req.params[name]) ){
|
||||
next(new Error('failed to parseInt '+num));
|
||||
} else {
|
||||
next();
|
||||
@@ -58,13 +57,14 @@ app.get('/user/:user', function(req, res, next){
|
||||
*/
|
||||
|
||||
app.get('/users/:from-:to', function(req, res, next){
|
||||
var from = req.params.from
|
||||
, to = req.params.to
|
||||
, names = users.map(function(user){ return user.name; });
|
||||
var from = req.params.from;
|
||||
var to = req.params.to;
|
||||
var names = users.map(function(user){ return user.name; });
|
||||
res.send('users ' + names.slice(from, to).join(', '));
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -12,13 +11,16 @@ var app = module.exports = express();
|
||||
app.resource = function(path, obj) {
|
||||
this.get(path, obj.index);
|
||||
this.get(path + '/:a..:b.:format?', function(req, res){
|
||||
var a = parseInt(req.params.a, 10)
|
||||
, b = parseInt(req.params.b, 10)
|
||||
, format = req.params.format;
|
||||
var a = parseInt(req.params.a, 10);
|
||||
var b = parseInt(req.params.b, 10);
|
||||
var format = req.params.format;
|
||||
obj.range(req, res, a, b, format);
|
||||
});
|
||||
this.get(path + '/:id', obj.show);
|
||||
this.del(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
|
||||
@@ -41,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');
|
||||
@@ -82,10 +83,11 @@ app.get('/', function(req, res){
|
||||
, '<li>GET /users/1..3.json</li>'
|
||||
, '<li>DELETE /users/4</li>'
|
||||
, '</ul>'
|
||||
].join('\n'));
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express')
|
||||
, verbose = process.env.NODE_ENV != 'test'
|
||||
, app = module.exports = express();
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var verbose = process.env.NODE_ENV != 'test';
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
app.map = function(a, route){
|
||||
route = route || '';
|
||||
@@ -29,7 +34,7 @@ var users = {
|
||||
res.send('user ' + req.params.uid);
|
||||
},
|
||||
|
||||
del: function(req, res){
|
||||
delete: function(req, res){
|
||||
res.send('delete users');
|
||||
}
|
||||
};
|
||||
@@ -39,7 +44,7 @@ var pets = {
|
||||
res.send('user ' + req.params.uid + '\'s pets');
|
||||
},
|
||||
|
||||
del: function(req, res){
|
||||
delete: function(req, res){
|
||||
res.send('delete ' + req.params.uid + '\'s pet ' + req.params.pid);
|
||||
}
|
||||
};
|
||||
@@ -47,17 +52,21 @@ var pets = {
|
||||
app.map({
|
||||
'/users': {
|
||||
get: users.list,
|
||||
del: users.del,
|
||||
delete: users.delete,
|
||||
'/:uid': {
|
||||
get: users.get,
|
||||
'/pets': {
|
||||
get: pets.list,
|
||||
'/:pid': {
|
||||
del: pets.del
|
||||
delete: pets.delete
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
/* istanbul ignore next */
|
||||
if (!module.parent) {
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
}
|
||||
|
||||
@@ -77,9 +77,12 @@ app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
|
||||
res.send('Editing user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, app = express()
|
||||
, site = require('./site')
|
||||
, post = require('./post')
|
||||
, user = require('./user');
|
||||
var express = require('../..');
|
||||
var app = express();
|
||||
var logger = require('morgan');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var bodyParser = require('body-parser');
|
||||
var site = require('./site');
|
||||
var post = require('./post');
|
||||
var user = require('./user');
|
||||
|
||||
// Config
|
||||
|
||||
app.set('view engine', 'jade');
|
||||
app.set('views', __dirname + '/views');
|
||||
app.use(express.logger('dev'));
|
||||
app.use(express.cookieParser());
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.methodOverride());
|
||||
app.use(logger('dev'));
|
||||
app.use(methodOverride('_method'));
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// General
|
||||
@@ -36,5 +38,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');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// Fake posts database
|
||||
|
||||
var posts = [
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
exports.index = function(req, res){
|
||||
res.render('index', { title: 'Route Separation Example' });
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// Fake user database
|
||||
|
||||
var users = [
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
var search = document.querySelector('[type=search]');
|
||||
var code = document.querySelector('pre');
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// first:
|
||||
// $ npm install redis
|
||||
// $ redis-server
|
||||
@@ -7,9 +6,10 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, redis = require('redis')
|
||||
, db = redis.createClient();
|
||||
var express = require('../..');
|
||||
var redis = require('redis');
|
||||
|
||||
var db = redis.createClient();
|
||||
|
||||
// npm install redis
|
||||
|
||||
@@ -57,5 +57,8 @@ app.get('/client.js', function(req, res){
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
!!! 5
|
||||
doctype
|
||||
html
|
||||
head
|
||||
title Search example
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
|
||||
// first:
|
||||
// $ npm install redis
|
||||
// $ redis-server
|
||||
|
||||
var express = require('../..');
|
||||
var session = require('express-session');
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use(express.logger('dev'));
|
||||
|
||||
// Required by session() middleware
|
||||
// pass the secret for signed cookies
|
||||
// (required by session())
|
||||
app.use(express.cookieParser('keyboard cat'));
|
||||
|
||||
// Populates req.session
|
||||
app.use(express.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 = '';
|
||||
@@ -28,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');
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var session = require('express-session');
|
||||
|
||||
// pass the express to the connect redis module
|
||||
// allowing it to inherit from express.session.Store
|
||||
var RedisStore = require('connect-redis')(express);
|
||||
// allowing it to inherit from session.Store
|
||||
var RedisStore = require('connect-redis')(session);
|
||||
|
||||
var app = express();
|
||||
|
||||
app.use(express.logger('dev'));
|
||||
|
||||
// Required by session() middleware
|
||||
// pass the secret for signed cookies
|
||||
// (required by session())
|
||||
app.use(express.cookieParser('keyboard cat'));
|
||||
app.use(logger('dev'));
|
||||
|
||||
// Populates req.session
|
||||
app.use(express.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 = '';
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var app = express();
|
||||
|
||||
// log requests
|
||||
app.use(express.logger('dev'));
|
||||
app.use(logger('dev'));
|
||||
|
||||
// express on its own has no notion
|
||||
// of a "file". The express.static()
|
||||
@@ -28,17 +32,9 @@ app.use('/static', express.static(__dirname + '/public'));
|
||||
// this will allow "GET /style.css" instead of "GET /css/style.css":
|
||||
app.use(express.static(__dirname + '/public/css'));
|
||||
|
||||
// this examples does not have any routes, however
|
||||
// you may `app.use(app.router)` before or after these
|
||||
// static() middleware. If placed before them your routes
|
||||
// will be matched BEFORE file serving takes place. If placed
|
||||
// after as shown here then file serving is performed BEFORE
|
||||
// any routes are hit:
|
||||
app.use(app.router);
|
||||
|
||||
app.listen(3000);
|
||||
console.log('listening on port 3000');
|
||||
console.log('try:');
|
||||
console.log(' GET /hello.txt');
|
||||
console.log(' GET /js/app.js');
|
||||
console.log(' GET /css/style.css');
|
||||
console.log(' GET /css/style.css');
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
*/
|
||||
|
||||
var express = require('../..');
|
||||
var logger = require('morgan');
|
||||
var vhost = require('vhost');
|
||||
|
||||
/*
|
||||
edit /etc/hosts:
|
||||
@@ -16,31 +18,34 @@ edit /etc/hosts:
|
||||
|
||||
var main = express();
|
||||
|
||||
main.use(express.logger('dev'));
|
||||
if (!module.parent) main.use(logger('dev'));
|
||||
|
||||
main.get('/', function(req, res){
|
||||
res.send('Hello from main app!')
|
||||
res.send('Hello from main app!');
|
||||
});
|
||||
|
||||
main.get('/:sub', function(req, res){
|
||||
res.send('requsted ' + req.params.sub);
|
||||
res.send('requested ' + req.params.sub);
|
||||
});
|
||||
|
||||
// Redirect app
|
||||
|
||||
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(express.vhost('*.example.com', redirect)) // Serves all subdomains via Redirect app
|
||||
app.use(express.vhost('example.com', main)); // Serves top level domain via Main server app
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, path = require('path')
|
||||
, extname = path.extname
|
||||
var http = require('http');
|
||||
var path = require('path');
|
||||
var extname = path.extname;
|
||||
|
||||
/**
|
||||
* Expose `GithubView`.
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../')
|
||||
, http = require('http')
|
||||
, GithubView = require('./github-view')
|
||||
, md = require('marked').parse;
|
||||
var express = require('../../');
|
||||
var http = require('http');
|
||||
var GithubView = require('./github-view');
|
||||
var md = require('marked').parse;
|
||||
|
||||
var app = module.exports = express();
|
||||
|
||||
@@ -16,12 +15,12 @@ app.engine('md', function(str, options, fn){
|
||||
var html = md(str);
|
||||
html = html.replace(/\{([^}]+)\}/g, function(_, name){
|
||||
return options[name] || '';
|
||||
})
|
||||
});
|
||||
fn(null, html);
|
||||
} catch(err) {
|
||||
fn(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// pointing to a particular github repo to load files from it
|
||||
app.set('views', 'visionmedia/express');
|
||||
@@ -34,13 +33,14 @@ app.get('/', function(req, res){
|
||||
// app.locals, res.locals, and locals passed
|
||||
// work like they normally would
|
||||
res.render('examples/markdown/views/index.md', { title: 'Example' });
|
||||
})
|
||||
});
|
||||
|
||||
app.get('/Readme.md', function(req, res){
|
||||
// rendering a view from https://github.com/visionmedia/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');
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../..')
|
||||
, User = require('./user')
|
||||
, app = express();
|
||||
var express = require('../..');
|
||||
var User = require('./user');
|
||||
var app = express();
|
||||
|
||||
app.set('views', __dirname);
|
||||
app.set('view engine', 'jade');
|
||||
@@ -69,7 +72,7 @@ app.get('/middleware', count, users, function(req, res, next){
|
||||
// this approach is much like the last
|
||||
// however we're explicitly exposing
|
||||
// the locals within each middleware
|
||||
//
|
||||
//
|
||||
// note that this may not always work
|
||||
// well, for example here we filter
|
||||
// the users in the middleware, which
|
||||
@@ -142,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');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
doctype 5
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
module.exports = User;
|
||||
|
||||
// faux model
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -40,29 +39,6 @@ app.use('/api', function(req, res, next){
|
||||
next();
|
||||
});
|
||||
|
||||
// position our routes above the error handling middleware,
|
||||
// and below our API middleware, since we want the API validation
|
||||
// to take place BEFORE our routes
|
||||
app.use(app.router);
|
||||
|
||||
// middleware with an arity of 4 are considered
|
||||
// error handling middleware. When you next(err)
|
||||
// it will be passed through the defined middleware
|
||||
// in order, but ONLY those with an arity of 4, ignoring
|
||||
// regular middleware.
|
||||
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 });
|
||||
});
|
||||
|
||||
// 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" });
|
||||
});
|
||||
|
||||
// map of valid api keys, typically mapped to
|
||||
// account info with some sort of database like redis.
|
||||
// api keys do _not_ serve as authentication, merely to
|
||||
@@ -102,14 +78,33 @@ app.get('/api/repos', function(req, res, next){
|
||||
});
|
||||
|
||||
app.get('/api/user/:name/repos', function(req, res, next){
|
||||
var name = req.params.name
|
||||
, user = userRepos[name];
|
||||
|
||||
var name = req.params.name;
|
||||
var user = userRepos[name];
|
||||
|
||||
if (user) res.send(user);
|
||||
else next();
|
||||
});
|
||||
|
||||
// middleware with an arity of 4 are considered
|
||||
// error handling middleware. When you next(err)
|
||||
// it will be passed through the defined middleware
|
||||
// in order, but ONLY those with an arity of 4, ignoring
|
||||
// regular middleware.
|
||||
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 });
|
||||
});
|
||||
|
||||
// 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" });
|
||||
});
|
||||
|
||||
/* 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,17 +2,20 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var connect = require('connect')
|
||||
, Router = require('./router')
|
||||
, methods = require('methods')
|
||||
, middleware = require('./middleware')
|
||||
, debug = require('debug')('express:application')
|
||||
, locals = require('./utils').locals
|
||||
, View = require('./view')
|
||||
, utils = connect.utils
|
||||
, path = require('path')
|
||||
, http = require('http')
|
||||
, join = path.join;
|
||||
var finalhandler = require('finalhandler');
|
||||
var mixin = require('utils-merge');
|
||||
var Router = require('./router');
|
||||
var methods = require('methods');
|
||||
var middleware = require('./middleware/init');
|
||||
var query = require('./middleware/query');
|
||||
var debug = require('debug')('express:application');
|
||||
var View = require('./view');
|
||||
var http = require('http');
|
||||
var compileETag = require('./utils').compileETag;
|
||||
var compileQueryParser = require('./utils').compileQueryParser;
|
||||
var compileTrust = require('./utils').compileTrust;
|
||||
var deprecate = require('depd')('express');
|
||||
var resolve = require('path').resolve;
|
||||
|
||||
/**
|
||||
* Application prototype.
|
||||
@@ -46,13 +49,14 @@ app.init = function(){
|
||||
app.defaultConfiguration = function(){
|
||||
// default settings
|
||||
this.enable('x-powered-by');
|
||||
this.set('env', process.env.NODE_ENV || 'development');
|
||||
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);
|
||||
debug('booting in %s mode', this.get('env'));
|
||||
this.set('trust proxy', false);
|
||||
|
||||
// implicit middleware
|
||||
this.use(connect.query());
|
||||
this.use(middleware.init(this));
|
||||
debug('booting in %s mode', env);
|
||||
|
||||
// inherit protos
|
||||
this.on('mount', function(parent){
|
||||
@@ -62,79 +66,150 @@ app.defaultConfiguration = function(){
|
||||
this.settings.__proto__ = parent.settings;
|
||||
});
|
||||
|
||||
// router
|
||||
this._router = new Router(this);
|
||||
this.routes = this._router.map;
|
||||
this.__defineGetter__('router', function(){
|
||||
this._usedRouter = true;
|
||||
this._router.caseSensitive = this.enabled('case sensitive routing');
|
||||
this._router.strict = this.enabled('strict routing');
|
||||
return this._router.middleware;
|
||||
});
|
||||
|
||||
// setup locals
|
||||
this.locals = locals(this);
|
||||
this.locals = Object.create(null);
|
||||
|
||||
// top-most app is mounted at /
|
||||
this.mountpath = '/';
|
||||
|
||||
// default locals
|
||||
this.locals.settings = this.settings;
|
||||
|
||||
// default configuration
|
||||
this.set('view', View);
|
||||
this.set('views', process.cwd() + '/views');
|
||||
this.set('views', resolve('views'));
|
||||
this.set('jsonp callback name', 'callback');
|
||||
|
||||
this.configure('development', function(){
|
||||
this.set('json spaces', 2);
|
||||
});
|
||||
|
||||
this.configure('production', function(){
|
||||
if (env === 'production') {
|
||||
this.enable('view cache');
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'router', {
|
||||
get: function() {
|
||||
throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Proxy `connect#use()` to apply settings to
|
||||
* mounted applications.
|
||||
* lazily adds the base router if it has not yet been added.
|
||||
*
|
||||
* We cannot add the base router in the defaultConfiguration because
|
||||
* it reads app settings which might be set after that has run.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
app.lazyrouter = function() {
|
||||
if (!this._router) {
|
||||
this._router = new Router({
|
||||
caseSensitive: this.enabled('case sensitive routing'),
|
||||
strict: this.enabled('strict routing')
|
||||
});
|
||||
|
||||
this._router.use(query(this.get('query parser fn')));
|
||||
this._router.use(middleware.init(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch a req, res pair into the application. Starts pipeline processing.
|
||||
*
|
||||
* If no _done_ callback is provided, then default error handlers will respond
|
||||
* in the event of an error bubbling through the stack.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
app.handle = function(req, res, done) {
|
||||
var router = this._router;
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Proxy `Router#use()` to add middleware to the app router.
|
||||
* See Router#use() documentation for details.
|
||||
*
|
||||
* 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 app;
|
||||
app.use = function use(path, fn) {
|
||||
var mount_app;
|
||||
var mount_path;
|
||||
|
||||
// default route to '/'
|
||||
if ('string' != typeof route) fn = route, route = '/';
|
||||
// check for .use(path, app) or .use(app) signature
|
||||
if (arguments.length <= 2) {
|
||||
mount_path = typeof path === 'string'
|
||||
? path
|
||||
: '/';
|
||||
mount_app = typeof path === 'function'
|
||||
? path
|
||||
: fn;
|
||||
}
|
||||
|
||||
// setup router
|
||||
this.lazyrouter();
|
||||
var router = this._router;
|
||||
|
||||
// express app
|
||||
if (fn.handle && fn.set) app = fn;
|
||||
if (mount_app && mount_app.handle && mount_app.set) {
|
||||
debug('.use app under %s', mount_path);
|
||||
mount_app.mountpath = mount_path;
|
||||
mount_app.parent = this;
|
||||
|
||||
// restore .app property on req and res
|
||||
if (app) {
|
||||
app.route = route;
|
||||
fn = function(req, res, next) {
|
||||
// restore .app property on req and res
|
||||
router.use(mount_path, function mounted_app(req, res, next) {
|
||||
var orig = req.app;
|
||||
app.handle(req, res, function(err){
|
||||
mount_app.handle(req, res, function(err) {
|
||||
req.__proto__ = orig.request;
|
||||
res.__proto__ = orig.response;
|
||||
next(err);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
// mounted an app
|
||||
mount_app.emit('mount', this);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
connect.proto.use.call(this, route, fn);
|
||||
|
||||
// mounted an app
|
||||
if (app) {
|
||||
app.parent = this;
|
||||
app.emit('mount', this);
|
||||
}
|
||||
// pass-through use
|
||||
router.use.apply(router, arguments);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Proxy to the app `Router#route()`
|
||||
* Returns a new `Route` instance for the _path_.
|
||||
*
|
||||
* Routes are isolated middleware stacks for specific paths.
|
||||
* See the Route api docs for details.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.route = function(path){
|
||||
this.lazyrouter();
|
||||
return this._router.route(path);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register the given template engine callback `fn`
|
||||
* as `ext`.
|
||||
@@ -177,30 +252,10 @@ app.engine = function(ext, fn){
|
||||
};
|
||||
|
||||
/**
|
||||
* Map the given param placeholder `name`(s) to the given callback(s).
|
||||
* Proxy to `Router#param()` with one added api feature. The _name_ parameter
|
||||
* can be an array of names.
|
||||
*
|
||||
* Parameter mapping is used to provide pre-conditions to routes
|
||||
* which use normalized placeholders. For example a _:user_id_ parameter
|
||||
* could automatically load a user's information from the database without
|
||||
* any additional code,
|
||||
*
|
||||
* The callback uses the samesignature as middleware, the only differencing
|
||||
* being that the value of the placeholder is passed, in this case the _id_
|
||||
* of the user. Once the `next()` function is invoked, just like middleware
|
||||
* it will continue on to execute the route, or subsequent parameter functions.
|
||||
*
|
||||
* app.param('user_id', function(req, res, next, id){
|
||||
* User.find(id, function(err, user){
|
||||
* if (err) {
|
||||
* next(err);
|
||||
* } else if (user) {
|
||||
* req.user = user;
|
||||
* next();
|
||||
* } else {
|
||||
* next(new Error('failed to load user'));
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
* See the Router#param() docs for more details.
|
||||
*
|
||||
* @param {String|Array} name
|
||||
* @param {Function} fn
|
||||
@@ -209,27 +264,17 @@ app.engine = function(ext, fn){
|
||||
*/
|
||||
|
||||
app.param = function(name, fn){
|
||||
var self = this
|
||||
, fns = [].slice.call(arguments, 1);
|
||||
var self = this;
|
||||
self.lazyrouter();
|
||||
|
||||
// array
|
||||
if (Array.isArray(name)) {
|
||||
name.forEach(function(name){
|
||||
fns.forEach(function(fn){
|
||||
self.param(name, fn);
|
||||
});
|
||||
});
|
||||
// param logic
|
||||
} else if ('function' == typeof name) {
|
||||
this._router.param(name);
|
||||
// single
|
||||
} else {
|
||||
if (':' == name[0]) name = name.substr(1);
|
||||
fns.forEach(function(fn){
|
||||
self._router.param(name, fn);
|
||||
name.forEach(function(key) {
|
||||
self.param(key, fn);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
self._router.param(name, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -243,18 +288,37 @@ app.param = function(name, fn){
|
||||
* Mounted servers inherit their parent server's settings.
|
||||
*
|
||||
* @param {String} setting
|
||||
* @param {String} val
|
||||
* @param {*} [val]
|
||||
* @return {Server} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -273,7 +337,7 @@ app.set = function(setting, val){
|
||||
|
||||
app.path = function(){
|
||||
return this.parent
|
||||
? this.parent.path() + this.route
|
||||
? this.parent.path() + this.mountpath
|
||||
: '';
|
||||
};
|
||||
|
||||
@@ -339,60 +403,6 @@ app.disable = function(setting){
|
||||
return this.set(setting, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure callback for zero or more envs,
|
||||
* when no `env` is specified that callback will
|
||||
* be invoked for all environments. Any combination
|
||||
* can be used multiple times, in any order desired.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* app.configure(function(){
|
||||
* // executed for all envs
|
||||
* });
|
||||
*
|
||||
* app.configure('stage', function(){
|
||||
* // executed staging env
|
||||
* });
|
||||
*
|
||||
* app.configure('stage', 'production', function(){
|
||||
* // executed for stage and production
|
||||
* });
|
||||
*
|
||||
* Note:
|
||||
*
|
||||
* These callbacks are invoked immediately, and
|
||||
* are effectively sugar for the following:
|
||||
*
|
||||
* var env = process.env.NODE_ENV || 'development';
|
||||
*
|
||||
* switch (env) {
|
||||
* case 'development':
|
||||
* ...
|
||||
* break;
|
||||
* case 'stage':
|
||||
* ...
|
||||
* break;
|
||||
* case 'production':
|
||||
* ...
|
||||
* break;
|
||||
* }
|
||||
*
|
||||
* @param {String} env...
|
||||
* @param {Function} fn
|
||||
* @return {app} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.configure = function(env, fn){
|
||||
var envs = 'all'
|
||||
, args = [].slice.call(arguments);
|
||||
fn = args.pop();
|
||||
if (args.length) envs = args;
|
||||
if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delegate `.VERB(...)` calls to `router.VERB(...)`.
|
||||
*/
|
||||
@@ -401,16 +411,10 @@ methods.forEach(function(method){
|
||||
app[method] = function(path){
|
||||
if ('get' == method && 1 == arguments.length) return this.set(path);
|
||||
|
||||
// deprecated
|
||||
if (Array.isArray(path)) {
|
||||
console.trace('passing an array to app.VERB() is deprecated and will be removed in 4.0');
|
||||
}
|
||||
this.lazyrouter();
|
||||
|
||||
// if no router attached yet, attach the router
|
||||
if (!this._usedRouter) this.use(this.router);
|
||||
|
||||
// setup route
|
||||
this._router[method].apply(this._router, arguments);
|
||||
var route = this._router.route(path);
|
||||
route[method].apply(route, [].slice.call(arguments, 1));
|
||||
return this;
|
||||
};
|
||||
});
|
||||
@@ -426,16 +430,20 @@ methods.forEach(function(method){
|
||||
*/
|
||||
|
||||
app.all = function(path){
|
||||
var args = arguments;
|
||||
this.lazyrouter();
|
||||
|
||||
var route = this._router.route(path);
|
||||
var args = [].slice.call(arguments, 1);
|
||||
methods.forEach(function(method){
|
||||
app[method].apply(this, args);
|
||||
}, this);
|
||||
route[method].apply(route, args);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// del -> delete alias
|
||||
|
||||
app.del = app.delete;
|
||||
app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead');
|
||||
|
||||
/**
|
||||
* Render the given view `name` name with `options`
|
||||
@@ -455,10 +463,10 @@ app.del = app.delete;
|
||||
*/
|
||||
|
||||
app.render = function(name, options, fn){
|
||||
var opts = {}
|
||||
, cache = this.cache
|
||||
, engines = this.engines
|
||||
, view;
|
||||
var opts = {};
|
||||
var cache = this.cache;
|
||||
var engines = this.engines;
|
||||
var view;
|
||||
|
||||
// support callback function as second arg
|
||||
if ('function' == typeof options) {
|
||||
@@ -466,13 +474,13 @@ app.render = function(name, options, fn){
|
||||
}
|
||||
|
||||
// merge app.locals
|
||||
utils.merge(opts, this.locals);
|
||||
mixin(opts, this.locals);
|
||||
|
||||
// merge options._locals
|
||||
if (options._locals) utils.merge(opts, options._locals);
|
||||
if (options._locals) mixin(opts, options._locals);
|
||||
|
||||
// merge options
|
||||
utils.merge(opts, options);
|
||||
mixin(opts, options);
|
||||
|
||||
// set .cache unless explicitly provided
|
||||
opts.cache = null == opts.cache
|
||||
@@ -491,7 +499,7 @@ app.render = function(name, options, fn){
|
||||
});
|
||||
|
||||
if (!view.path) {
|
||||
var err = new Error('Failed to lookup view "' + name + '"');
|
||||
var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"');
|
||||
err.view = view;
|
||||
return fn(err);
|
||||
}
|
||||
@@ -533,3 +541,14 @@ app.listen = function(){
|
||||
var server = http.createServer(this);
|
||||
return server.listen.apply(server, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Log error using console.error.
|
||||
*
|
||||
* @param {Error} err
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function logerror(err){
|
||||
if (this.get('env') !== 'test') console.error(err.stack || err.toString());
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var connect = require('connect')
|
||||
, proto = require('./application')
|
||||
, Route = require('./router/route')
|
||||
, Router = require('./router')
|
||||
, req = require('./request')
|
||||
, res = require('./response')
|
||||
, utils = connect.utils;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var mixin = require('utils-merge');
|
||||
var proto = require('./application');
|
||||
var Route = require('./router/route');
|
||||
var Router = require('./router');
|
||||
var req = require('./request');
|
||||
var res = require('./response');
|
||||
|
||||
/**
|
||||
* Expose `createApplication()`.
|
||||
@@ -16,12 +16,6 @@ var connect = require('connect')
|
||||
|
||||
exports = module.exports = createApplication;
|
||||
|
||||
/**
|
||||
* Expose mime.
|
||||
*/
|
||||
|
||||
exports.mime = connect.mime;
|
||||
|
||||
/**
|
||||
* Create an express application.
|
||||
*
|
||||
@@ -30,41 +24,19 @@ exports.mime = connect.mime;
|
||||
*/
|
||||
|
||||
function createApplication() {
|
||||
var app = connect();
|
||||
utils.merge(app, proto);
|
||||
var app = function(req, res, next) {
|
||||
app.handle(req, res, next);
|
||||
};
|
||||
|
||||
mixin(app, proto);
|
||||
mixin(app, EventEmitter.prototype);
|
||||
|
||||
app.request = { __proto__: req, app: app };
|
||||
app.response = { __proto__: res, app: app };
|
||||
app.init();
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose connect.middleware as express.*
|
||||
* for example `express.logger` etc.
|
||||
*/
|
||||
|
||||
for (var key in connect.middleware) {
|
||||
Object.defineProperty(
|
||||
exports
|
||||
, key
|
||||
, Object.getOwnPropertyDescriptor(connect.middleware, key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Error on createServer().
|
||||
*/
|
||||
|
||||
exports.createServer = function(){
|
||||
console.warn('Warning: express.createServer() is deprecated, express');
|
||||
console.warn('applications no longer inherit from http.Server,');
|
||||
console.warn('please use:');
|
||||
console.warn('');
|
||||
console.warn(' var express = require("express");');
|
||||
console.warn(' var app = express();');
|
||||
console.warn('');
|
||||
return createApplication();
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose the prototypes.
|
||||
*/
|
||||
@@ -80,7 +52,42 @@ exports.response = res;
|
||||
exports.Route = Route;
|
||||
exports.Router = Router;
|
||||
|
||||
// Error handler title
|
||||
/**
|
||||
* Expose middleware
|
||||
*/
|
||||
|
||||
exports.errorHandler.title = 'Express';
|
||||
exports.query = require('./middleware/query');
|
||||
exports.static = require('serve-static');
|
||||
|
||||
/**
|
||||
* Replace removed middleware with an appropriate error message.
|
||||
*/
|
||||
|
||||
[
|
||||
'json',
|
||||
'urlencoded',
|
||||
'bodyParser',
|
||||
'compress',
|
||||
'cookieSession',
|
||||
'session',
|
||||
'logger',
|
||||
'cookieParser',
|
||||
'favicon',
|
||||
'responseTime',
|
||||
'errorHandler',
|
||||
'timeout',
|
||||
'methodOverride',
|
||||
'vhost',
|
||||
'csrf',
|
||||
'directory',
|
||||
'limit',
|
||||
'multipart',
|
||||
'staticCache',
|
||||
].forEach(function (name) {
|
||||
Object.defineProperty(exports, name, {
|
||||
get: function () {
|
||||
throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('./utils');
|
||||
|
||||
/**
|
||||
* Initialization middleware, exposing the
|
||||
* request and response to eachother, as well
|
||||
@@ -25,8 +18,9 @@ exports.init = function(app){
|
||||
req.__proto__ = app.request;
|
||||
res.__proto__ = app.response;
|
||||
|
||||
res.locals = res.locals || utils.locals(res);
|
||||
res.locals = res.locals || Object.create(null);
|
||||
|
||||
next();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
30
lib/middleware/query.js
Normal file
30
lib/middleware/query.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var parseUrl = require('parseurl');
|
||||
var qs = require('qs');
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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) {
|
||||
var val = parseUrl(req).query;
|
||||
req.query = queryparse(val, options);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
328
lib/request.js
328
lib/request.js
@@ -1,15 +1,15 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, utils = require('./utils')
|
||||
, connect = require('connect')
|
||||
, fresh = require('fresh')
|
||||
, parseRange = require('range-parser')
|
||||
, parse = connect.utils.parseUrl
|
||||
, mime = connect.mime;
|
||||
var accepts = require('accepts');
|
||||
var deprecate = require('depd')('express');
|
||||
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.
|
||||
@@ -56,6 +56,8 @@ req.header = function(name){
|
||||
};
|
||||
|
||||
/**
|
||||
* To do: update docs.
|
||||
*
|
||||
* Check if the given `type(s)` is acceptable, returning
|
||||
* the best match when true, otherwise `undefined`, in which
|
||||
* case you should respond with 406 "Not Acceptable".
|
||||
@@ -63,6 +65,7 @@ req.header = function(name){
|
||||
* The `type` value may be a single mime type string
|
||||
* such as "application/json", the extension name
|
||||
* such as "json", a comma-delimted list such as "json, html, text/plain",
|
||||
* an argument list such as `"json", "html", "text/plain"`,
|
||||
* or an array `["json", "html", "text/plain"]`. When a list
|
||||
* or array is given the _best_ match, if any is returned.
|
||||
*
|
||||
@@ -89,6 +92,7 @@ req.header = function(name){
|
||||
*
|
||||
* // Accept: text/*;q=.5, application/json
|
||||
* req.accepts(['html', 'json']);
|
||||
* req.accepts('html', 'json');
|
||||
* req.accepts('html, json');
|
||||
* // => "json"
|
||||
*
|
||||
@@ -97,54 +101,61 @@ req.header = function(name){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.accepts = function(type){
|
||||
return utils.accepts(type, this.get('Accept'));
|
||||
req.accepts = function(){
|
||||
var accept = accepts(this);
|
||||
return accept.types.apply(accept, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = function(encoding){
|
||||
return !! ~this.acceptedEncodings.indexOf(encoding);
|
||||
req.acceptsEncodings = function(){
|
||||
var accept = accepts(this);
|
||||
return accept.encodings.apply(accept, arguments);
|
||||
};
|
||||
|
||||
req.acceptsEncoding = deprecate.function(req.acceptsEncodings,
|
||||
'req.acceptsEncoding: Use acceptsEncodings instead');
|
||||
|
||||
/**
|
||||
* 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 = function(charset){
|
||||
var accepted = this.acceptedCharsets;
|
||||
return accepted.length
|
||||
? !! ~accepted.indexOf(charset)
|
||||
: true;
|
||||
req.acceptsCharsets = function(){
|
||||
var accept = accepts(this);
|
||||
return accept.charsets.apply(accept, arguments);
|
||||
};
|
||||
|
||||
req.acceptsCharset = deprecate.function(req.acceptsCharsets,
|
||||
'req.acceptsCharset: Use acceptsCharsets instead');
|
||||
|
||||
/**
|
||||
* 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 = function(lang){
|
||||
var accepted = this.acceptedLanguages;
|
||||
return accepted.length
|
||||
? !! ~accepted.indexOf(lang)
|
||||
: true;
|
||||
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`.
|
||||
@@ -171,98 +182,6 @@ req.range = function(size){
|
||||
return parseRange(size, range);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an array of encodings.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ['gzip', 'deflate']
|
||||
*
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('acceptedEncodings', function(){
|
||||
var accept = this.get('Accept-Encoding');
|
||||
return accept
|
||||
? accept.trim().split(/ *, */)
|
||||
: [];
|
||||
});
|
||||
|
||||
/**
|
||||
* Return an array of Accepted media types
|
||||
* ordered from highest quality to lowest.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* [ { value: 'application/json',
|
||||
* quality: 1,
|
||||
* type: 'application',
|
||||
* subtype: 'json' },
|
||||
* { value: 'text/html',
|
||||
* quality: 0.5,
|
||||
* type: 'text',
|
||||
* subtype: 'html' } ]
|
||||
*
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('accepted', function(){
|
||||
var accept = this.get('Accept');
|
||||
return accept
|
||||
? utils.parseAccept(accept)
|
||||
: [];
|
||||
});
|
||||
|
||||
/**
|
||||
* Return an array of Accepted languages
|
||||
* ordered from highest quality to lowest.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Accept-Language: en;q=.5, en-us
|
||||
* ['en-us', 'en']
|
||||
*
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('acceptedLanguages', function(){
|
||||
var accept = this.get('Accept-Language');
|
||||
return accept
|
||||
? utils
|
||||
.parseParams(accept)
|
||||
.map(function(obj){
|
||||
return obj.value;
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
/**
|
||||
* Return an array of Accepted charsets
|
||||
* ordered from highest quality to lowest.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Accept-Charset: iso-8859-5;q=.2, unicode-1-1;q=0.8
|
||||
* ['unicode-1-1', 'iso-8859-5']
|
||||
*
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('acceptedCharsets', function(){
|
||||
var accept = this.get('Accept-Charset');
|
||||
return accept
|
||||
? utils
|
||||
.parseParams(accept)
|
||||
.map(function(obj){
|
||||
return obj.value;
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
/**
|
||||
* Return the value of param `name` when present or `defaultValue`.
|
||||
*
|
||||
@@ -272,7 +191,7 @@ req.__defineGetter__('acceptedCharsets', function(){
|
||||
*
|
||||
* To utilize request bodies, `req.body`
|
||||
* should be an object. This can be done by using
|
||||
* the `connect.bodyParser()` middleware.
|
||||
* the `bodyParser()` middleware.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Mixed} [defaultValue]
|
||||
@@ -316,38 +235,38 @@ req.param = function(name, defaultValue){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.is = function(type){
|
||||
var ct = this.get('Content-Type');
|
||||
if (!ct) return false;
|
||||
ct = ct.split(';')[0];
|
||||
if (!~type.indexOf('/')) type = mime.lookup(type);
|
||||
if (~type.indexOf('*')) {
|
||||
type = type.split('/');
|
||||
ct = ct.split('/');
|
||||
if ('*' == type[0] && type[1] == ct[1]) return true;
|
||||
if ('*' == type[1] && type[0] == ct[0]) return true;
|
||||
return false;
|
||||
}
|
||||
return !! ~ct.indexOf(type);
|
||||
req.is = function(types){
|
||||
if (!Array.isArray(types)) types = [].slice.call(arguments);
|
||||
return typeis(this, 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];
|
||||
});
|
||||
|
||||
@@ -360,71 +279,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(/ *, */)
|
||||
: [];
|
||||
});
|
||||
|
||||
/**
|
||||
* Return basic auth credentials.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* // http://tobi:hello@example.com
|
||||
* req.auth
|
||||
* // => { username: 'tobi', password: 'hello' }
|
||||
*
|
||||
* @return {Object} or undefined
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('auth', function(){
|
||||
// missing
|
||||
var auth = this.get('Authorization');
|
||||
if (!auth) return;
|
||||
|
||||
// malformed
|
||||
var parts = auth.split(' ');
|
||||
if ('basic' != parts[0].toLowerCase()) return;
|
||||
if (!parts[1]) return;
|
||||
auth = parts[1];
|
||||
|
||||
// credentials
|
||||
auth = new Buffer(auth, 'base64').toString().match(/^([^:]*):(.*)$/);
|
||||
if (!auth) return;
|
||||
return { username: auth[1], password: auth[2] };
|
||||
defineGetter(req, 'ips', function ips() {
|
||||
var trust = this.app.get('trust proxy fn');
|
||||
var addrs = proxyaddr.all(this, trust);
|
||||
return addrs.slice(1).reverse();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -442,9 +331,9 @@ req.__defineGetter__('auth', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('subdomains', function(){
|
||||
defineGetter(req, 'subdomains', function subdomains() {
|
||||
var offset = this.app.get('subdomain offset');
|
||||
return (this.host || '')
|
||||
return (this.hostname || '')
|
||||
.split('.')
|
||||
.reverse()
|
||||
.slice(offset);
|
||||
@@ -457,25 +346,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;
|
||||
return host.split(':')[0];
|
||||
|
||||
// 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
|
||||
@@ -485,7 +397,7 @@ req.__defineGetter__('host', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('fresh', function(){
|
||||
defineGetter(req, 'fresh', function(){
|
||||
var method = this.method;
|
||||
var s = this.res.statusCode;
|
||||
|
||||
@@ -509,7 +421,7 @@ req.__defineGetter__('fresh', function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('stale', function(){
|
||||
defineGetter(req, 'stale', function stale(){
|
||||
return !this.fresh;
|
||||
});
|
||||
|
||||
@@ -520,7 +432,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
|
||||
});
|
||||
};
|
||||
|
||||
438
lib/response.js
438
lib/response.js
@@ -2,21 +2,23 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, path = require('path')
|
||||
, connect = require('connect')
|
||||
, utils = connect.utils
|
||||
, sign = require('cookie-signature').sign
|
||||
, normalizeType = require('./utils').normalizeType
|
||||
, normalizeTypes = require('./utils').normalizeTypes
|
||||
, etag = require('./utils').etag
|
||||
, statusCodes = http.STATUS_CODES
|
||||
, cookie = require('cookie')
|
||||
, send = require('send')
|
||||
, mime = connect.mime
|
||||
, basename = path.basename
|
||||
, extname = path.extname
|
||||
, join = path.join;
|
||||
var deprecate = require('depd')('express');
|
||||
var escapeHtml = require('escape-html');
|
||||
var http = require('http');
|
||||
var path = require('path');
|
||||
var mixin = require('utils-merge');
|
||||
var sign = require('cookie-signature').sign;
|
||||
var normalizeType = require('./utils').normalizeType;
|
||||
var normalizeTypes = require('./utils').normalizeTypes;
|
||||
var setCharset = require('./utils').setCharset;
|
||||
var contentDisposition = require('./utils').contentDisposition;
|
||||
var statusCodes = http.STATUS_CODES;
|
||||
var cookie = require('cookie');
|
||||
var send = require('send');
|
||||
var basename = path.basename;
|
||||
var extname = path.extname;
|
||||
var mime = send.mime;
|
||||
var vary = require('vary');
|
||||
|
||||
/**
|
||||
* Response prototype.
|
||||
@@ -55,7 +57,9 @@ res.status = function(code){
|
||||
*/
|
||||
|
||||
res.links = function(links){
|
||||
return this.set('Link', Object.keys(links).map(function(rel){
|
||||
var link = this.get('Link') || '';
|
||||
if (link) link += ', ';
|
||||
return this.set('Link', link + Object.keys(links).map(function(rel){
|
||||
return '<' + links[rel] + '>; rel="' + rel + '"';
|
||||
}).join(', '));
|
||||
};
|
||||
@@ -68,69 +72,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.status(status).end() instead');
|
||||
this.statusCode = chunk;
|
||||
chunk = http.STATUS_CODES[chunk];
|
||||
}
|
||||
|
||||
switch (typeof chunk) {
|
||||
// string defaulting to html
|
||||
case 'string':
|
||||
if (!this.get('Content-Type')) {
|
||||
this.charset = this.charset || 'utf-8';
|
||||
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 (len > 1024 && 'GET' == 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +177,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;
|
||||
};
|
||||
|
||||
@@ -157,24 +198,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];
|
||||
} 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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,10 +223,12 @@ 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);
|
||||
};
|
||||
@@ -197,24 +240,24 @@ res.json = function(obj){
|
||||
*
|
||||
* 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];
|
||||
} 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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,21 +265,37 @@ 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.charset = this.charset || 'utf-8';
|
||||
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)) {
|
||||
callback = callback[0];
|
||||
}
|
||||
|
||||
// jsonp
|
||||
if (callback) {
|
||||
if (callback instanceof Array) callback = callback[0];
|
||||
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 = cb + ' && ' + 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);
|
||||
@@ -253,8 +312,12 @@ res.jsonp = function(obj){
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `maxAge` defaulting to 0
|
||||
* - `root` root directory for relative filenames
|
||||
* - `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:
|
||||
*
|
||||
@@ -276,18 +339,16 @@ res.jsonp = function(obj){
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Object|Function} options or fn
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.sendfile = function(path, options, fn){
|
||||
var self = this
|
||||
, req = self.req
|
||||
, next = this.req.next
|
||||
, options = options || {}
|
||||
, done;
|
||||
options = options || {};
|
||||
var self = this;
|
||||
var req = self.req;
|
||||
var next = this.req.next;
|
||||
var done;
|
||||
|
||||
|
||||
// support function as second arg
|
||||
if ('function' == typeof options) {
|
||||
@@ -305,23 +366,19 @@ res.sendfile = function(path, options, fn){
|
||||
|
||||
// clean up
|
||||
cleanup();
|
||||
if (!self.headerSent) self.removeHeader('Content-Disposition');
|
||||
|
||||
// callback available
|
||||
if (fn) return fn(err);
|
||||
|
||||
// list in limbo if there's no callback
|
||||
if (self.headerSent) return;
|
||||
|
||||
// delegate
|
||||
next(err);
|
||||
}
|
||||
|
||||
// streaming
|
||||
function stream() {
|
||||
function stream(stream) {
|
||||
if (done) return;
|
||||
cleanup();
|
||||
if (fn) self.on('finish', fn);
|
||||
if (fn) stream.on('end', fn);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
@@ -330,12 +387,25 @@ res.sendfile = function(path, options, fn){
|
||||
}
|
||||
|
||||
// transfer
|
||||
var file = send(req, path);
|
||||
if (options.root) file.root(options.root);
|
||||
file.maxage(options.maxAge || 0);
|
||||
var file = send(req, path, options);
|
||||
file.on('error', error);
|
||||
file.on('directory', next);
|
||||
file.on('stream', stream);
|
||||
|
||||
if (options.headers) {
|
||||
// set headers on successful transfer
|
||||
file.on('headers', function headers(res) {
|
||||
var obj = options.headers;
|
||||
var keys = Object.keys(obj);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var k = keys[i];
|
||||
res.setHeader(k, obj[k]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// pipe
|
||||
file.pipe(this);
|
||||
this.on('finish', cleanup);
|
||||
};
|
||||
@@ -346,13 +416,10 @@ res.sendfile = function(path, options, fn){
|
||||
* Optionally providing an alternate attachment `filename`,
|
||||
* and optional callback `fn(err)`. The callback is invoked
|
||||
* when the data transfer is complete, or when an error has
|
||||
* ocurred. Be sure to check `res.headerSent` if you plan to respond.
|
||||
* ocurred. Be sure to check `res.headersSent` if you plan to respond.
|
||||
*
|
||||
* This method uses `res.sendfile()`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {String|Function} filename or fn
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
@@ -364,8 +431,13 @@ res.download = function(path, filename, fn){
|
||||
}
|
||||
|
||||
filename = filename || path;
|
||||
this.set('Content-Disposition', 'attachment; filename="' + basename(filename) + '"');
|
||||
return this.sendfile(path, fn);
|
||||
|
||||
// set Content-Disposition when file is sent
|
||||
var headers = {
|
||||
'Content-Disposition': contentDisposition(filename)
|
||||
};
|
||||
|
||||
return this.sendfile(path, { headers: headers }, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -450,8 +522,8 @@ res.type = function(type){
|
||||
*/
|
||||
|
||||
res.format = function(obj){
|
||||
var req = this.req
|
||||
, next = req.next;
|
||||
var req = this.req;
|
||||
var next = req.next;
|
||||
|
||||
var fn = obj.default;
|
||||
if (fn) delete obj.default;
|
||||
@@ -459,7 +531,7 @@ res.format = function(obj){
|
||||
|
||||
var key = req.accepts(keys);
|
||||
|
||||
this.set('Vary', 'Accept');
|
||||
this.vary("Accept");
|
||||
|
||||
if (key) {
|
||||
this.set('Content-Type', normalizeType(key).value);
|
||||
@@ -486,9 +558,7 @@ res.format = function(obj){
|
||||
|
||||
res.attachment = function(filename){
|
||||
if (filename) this.type(extname(filename));
|
||||
this.set('Content-Disposition', filename
|
||||
? 'attachment; filename="' + basename(filename) + '"'
|
||||
: 'attachment');
|
||||
this.set('Content-Disposition', contentDisposition(filename));
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -511,10 +581,14 @@ 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)) {
|
||||
var charset = mime.charsets.lookup(val.split(';')[0]);
|
||||
if (charset) val += '; charset=' + charset.toLowerCase();
|
||||
}
|
||||
this.setHeader(field, val);
|
||||
} else {
|
||||
for (var key in field) {
|
||||
@@ -541,14 +615,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
|
||||
? utils.merge(opts, options)
|
||||
? mixin(opts, options)
|
||||
: opts);
|
||||
};
|
||||
|
||||
@@ -572,14 +646,15 @@ 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 = utils.merge({}, options);
|
||||
options = mixin({}, options);
|
||||
var secret = this.req.secret;
|
||||
var signed = options.signed;
|
||||
if (signed && !secret) throw new Error('connect.cookieParser("secret") required for signed cookies');
|
||||
if (signed && !secret) throw new Error('cookieParser("secret") required for signed cookies');
|
||||
if ('number' == typeof val) val = val.toString();
|
||||
if ('object' == typeof val) val = 'j:' + JSON.stringify(val);
|
||||
if (signed) val = 's:' + sign(val, secret);
|
||||
@@ -588,7 +663,18 @@ res.cookie = function(name, val, options){
|
||||
options.maxAge /= 1000;
|
||||
}
|
||||
if (null == options.path) options.path = '/';
|
||||
this.set('Set-Cookie', cookie.serialize(name, String(val), options));
|
||||
var headerVal = cookie.serialize(name, String(val), options);
|
||||
|
||||
// supports multiple 'res.cookie' calls by getting previous value
|
||||
var prev = this.get('Set-Cookie');
|
||||
if (prev) {
|
||||
if (Array.isArray(prev)) {
|
||||
headerVal = prev.concat(headerVal);
|
||||
} else {
|
||||
headerVal = [prev, headerVal];
|
||||
}
|
||||
}
|
||||
this.set('Set-Cookie', headerVal);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -596,57 +682,25 @@ res.cookie = function(name, val, options){
|
||||
/**
|
||||
* Set the location header to `url`.
|
||||
*
|
||||
* The given `url` can also be the name of a mapped url, for
|
||||
* example by default express supports "back" which redirects
|
||||
* The given `url` can also be "back", which redirects
|
||||
* to the _Referrer_ or _Referer_ headers or "/".
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* res.location('/foo/bar').;
|
||||
* res.location('http://example.com');
|
||||
* res.location('../login'); // /blog/post/1 -> /blog/login
|
||||
*
|
||||
* Mounting:
|
||||
*
|
||||
* When an application is mounted and `res.location()`
|
||||
* is given a path that does _not_ lead with "/" it becomes
|
||||
* relative to the mount-point. For example if the application
|
||||
* is mounted at "/blog", the following would become "/blog/login".
|
||||
*
|
||||
* res.location('login');
|
||||
*
|
||||
* While the leading slash would result in a location of "/login":
|
||||
*
|
||||
* res.location('/login');
|
||||
* res.location('../login');
|
||||
*
|
||||
* @param {String} url
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.location = function(url){
|
||||
var app = this.app
|
||||
, req = this.req;
|
||||
var req = this.req;
|
||||
|
||||
// setup redirect map
|
||||
var map = { back: req.get('Referrer') || '/' };
|
||||
|
||||
// perform redirect
|
||||
url = map[url] || url;
|
||||
|
||||
// relative
|
||||
if (!~url.indexOf('://') && 0 != url.indexOf('//')) {
|
||||
var path
|
||||
|
||||
// relative to path
|
||||
if ('.' == url[0]) {
|
||||
path = req.originalUrl.split('?')[0]
|
||||
url = path + ('/' == path[path.length - 1] ? '' : '/') + url;
|
||||
// relative to mount-point
|
||||
} else if ('/' != url[0]) {
|
||||
path = app.path();
|
||||
url = path + '/' + url;
|
||||
}
|
||||
}
|
||||
// "back" is an alias for the referrer
|
||||
if ('back' == url) url = req.get('Referrer') || '/';
|
||||
|
||||
// Respond
|
||||
this.set('Location', url);
|
||||
@@ -666,33 +720,30 @@ res.location = function(url){
|
||||
* res.redirect('/foo/bar');
|
||||
* res.redirect('http://example.com');
|
||||
* res.redirect(301, 'http://example.com');
|
||||
* res.redirect('http://example.com', 301);
|
||||
* res.redirect('../login'); // /blog/post/1 -> /blog/login
|
||||
*
|
||||
* @param {String} url
|
||||
* @param {Number} code
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.redirect = function(url){
|
||||
var app = this.app
|
||||
, head = 'HEAD' == this.req.method
|
||||
, status = 302
|
||||
, body;
|
||||
res.redirect = function redirect(url) {
|
||||
var address = url;
|
||||
var body;
|
||||
var status = 302;
|
||||
|
||||
// allow status / url
|
||||
if (2 == arguments.length) {
|
||||
if ('number' == typeof url) {
|
||||
status = url;
|
||||
url = arguments[1];
|
||||
if (arguments.length === 2) {
|
||||
if (typeof arguments[0] === 'number') {
|
||||
status = arguments[0];
|
||||
address = arguments[1];
|
||||
} else {
|
||||
deprecate('res.redirect(ur, status): Use res.redirect(status, url) instead');
|
||||
status = arguments[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Set location header
|
||||
this.location(url);
|
||||
url = this.get('Location');
|
||||
this.location(address);
|
||||
address = this.get('Location');
|
||||
|
||||
// Support text/{plain,html} by default
|
||||
this.format({
|
||||
@@ -701,7 +752,7 @@ res.redirect = function(url){
|
||||
},
|
||||
|
||||
html: function(){
|
||||
var u = utils.escape(url);
|
||||
var u = escapeHtml(url);
|
||||
body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
|
||||
},
|
||||
|
||||
@@ -713,7 +764,33 @@ 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add `field` to Vary. If already present in the Vary set, then
|
||||
* this call is simply ignored.
|
||||
*
|
||||
* @param {Array|String} field
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.vary = function(field){
|
||||
// checks for back-compat
|
||||
if (!field || (Array.isArray(field) && !field.length)) {
|
||||
deprecate('res.vary(): Provide a field name');
|
||||
return this;
|
||||
}
|
||||
|
||||
vary(this, field);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -726,17 +803,14 @@ res.redirect = function(url){
|
||||
* - `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
|
||||
*/
|
||||
|
||||
res.render = function(view, options, fn){
|
||||
var self = this
|
||||
, options = options || {}
|
||||
, req = this.req
|
||||
, app = req.app;
|
||||
options = options || {};
|
||||
var self = this;
|
||||
var req = this.req;
|
||||
var app = req.app;
|
||||
|
||||
// support callback function as second arg
|
||||
if ('function' == typeof options) {
|
||||
|
||||
@@ -2,48 +2,79 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Route = require('./route')
|
||||
, utils = require('../utils')
|
||||
, methods = require('methods')
|
||||
, debug = require('debug')('express:router')
|
||||
, parse = require('connect').utils.parseUrl;
|
||||
|
||||
/**
|
||||
* Expose `Router` constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = Router;
|
||||
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 slice = Array.prototype.slice;
|
||||
var toString = Object.prototype.toString;
|
||||
var utils = require('../utils');
|
||||
|
||||
/**
|
||||
* Initialize a new `Router` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Router(options) {
|
||||
options = options || {};
|
||||
var self = this;
|
||||
this.map = {};
|
||||
this.params = {};
|
||||
this._params = [];
|
||||
this.caseSensitive = options.caseSensitive;
|
||||
this.strict = options.strict;
|
||||
this.middleware = function router(req, res, next){
|
||||
self._dispatch(req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a param callback `fn` for the given `name`.
|
||||
*
|
||||
* @param {String|Function} name
|
||||
* @param {Function} fn
|
||||
* @return {Router} for chaining
|
||||
* @return {Router} which is an callable function
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.param = function(name, fn){
|
||||
var proto = module.exports = function(options) {
|
||||
options = options || {};
|
||||
|
||||
function router(req, res, next) {
|
||||
router.handle(req, res, next);
|
||||
}
|
||||
|
||||
// mixin Router class functions
|
||||
router.__proto__ = proto;
|
||||
|
||||
router.params = {};
|
||||
router._params = [];
|
||||
router.caseSensitive = options.caseSensitive;
|
||||
router.mergeParams = options.mergeParams;
|
||||
router.strict = options.strict;
|
||||
router.stack = [];
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
/**
|
||||
* Map the given param placeholder `name`(s) to the given callback.
|
||||
*
|
||||
* Parameter mapping is used to provide pre-conditions to routes
|
||||
* which use normalized placeholders. For example a _:user_id_ parameter
|
||||
* could automatically load a user's information from the database without
|
||||
* any additional code,
|
||||
*
|
||||
* The callback uses the same signature as middleware, the only difference
|
||||
* being that the value of the placeholder is passed, in this case the _id_
|
||||
* of the user. Once the `next()` function is invoked, just like middleware
|
||||
* it will continue on to execute the route, or subsequent parameter functions.
|
||||
*
|
||||
* Just like in middleware, you must either respond to the request or call next
|
||||
* to avoid stalling the request.
|
||||
*
|
||||
* app.param('user_id', function(req, res, next, id){
|
||||
* User.find(id, function(err, user){
|
||||
* if (err) {
|
||||
* return next(err);
|
||||
* } else if (!user) {
|
||||
* return next(new Error('failed to load user'));
|
||||
* }
|
||||
* req.user = user;
|
||||
* next();
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} fn
|
||||
* @return {app} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
proto.param = function(name, fn){
|
||||
// param logic
|
||||
if ('function' == typeof name) {
|
||||
this._params.push(name);
|
||||
@@ -51,9 +82,13 @@ Router.prototype.param = function(name, fn){
|
||||
}
|
||||
|
||||
// apply param functions
|
||||
var params = this._params
|
||||
, len = params.length
|
||||
, ret;
|
||||
var params = this._params;
|
||||
var len = params.length;
|
||||
var ret;
|
||||
|
||||
if (name[0] === ':') {
|
||||
name = name.substr(1);
|
||||
}
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (ret = params[i](name, fn)) {
|
||||
@@ -72,202 +107,442 @@ Router.prototype.param = function(name, fn){
|
||||
};
|
||||
|
||||
/**
|
||||
* Route dispatcher aka the route "middleware".
|
||||
* Dispatch a req, res into the router.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @param {ServerResponse} res
|
||||
* @param {Function} next
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Router.prototype._dispatch = function(req, res, next){
|
||||
var params = this.params
|
||||
, self = this;
|
||||
proto.handle = function(req, res, done) {
|
||||
var self = this;
|
||||
|
||||
debug('dispatching %s %s (%s)', req.method, req.url, req.originalUrl);
|
||||
debug('dispatching %s %s', req.method, req.url);
|
||||
|
||||
// route dispatch
|
||||
(function pass(i, err){
|
||||
var paramCallbacks
|
||||
, paramIndex = 0
|
||||
, paramVal
|
||||
, route
|
||||
, keys
|
||||
, key;
|
||||
var search = 1 + req.url.indexOf('?');
|
||||
var pathlength = search ? search - 1 : req.url.length;
|
||||
var fqdn = 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 = {};
|
||||
|
||||
// match next route
|
||||
function nextRoute(err) {
|
||||
pass(req._route_index + 1, err);
|
||||
// store options for OPTIONS request
|
||||
// only used if OPTIONS request
|
||||
var options = [];
|
||||
|
||||
// middleware and routes
|
||||
var stack = self.stack;
|
||||
|
||||
// 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 (req.method === 'OPTIONS') {
|
||||
done = wrap(done, function(old, err) {
|
||||
if (err || options.length === 0) return old(err);
|
||||
|
||||
var body = options.join(',');
|
||||
return res.set('Allow', body).send(body);
|
||||
});
|
||||
}
|
||||
|
||||
// setup basic req values
|
||||
req.baseUrl = parentUrl;
|
||||
req.originalUrl = req.originalUrl || req.url;
|
||||
|
||||
next();
|
||||
|
||||
function next(err) {
|
||||
var layerError = err === 'route'
|
||||
? null
|
||||
: err;
|
||||
|
||||
var layer = stack[idx++];
|
||||
|
||||
if (slashAdded) {
|
||||
req.url = req.url.substr(1);
|
||||
slashAdded = false;
|
||||
}
|
||||
|
||||
// match route
|
||||
req.route = route = self.matchRequest(req, i);
|
||||
if (removed.length !== 0) {
|
||||
req.baseUrl = parentUrl;
|
||||
req.url = protohost + removed + req.url.substr(protohost.length);
|
||||
removed = '';
|
||||
}
|
||||
|
||||
// no route
|
||||
if (!route) return next(err);
|
||||
debug('matched %s %s', route.method, route.path);
|
||||
if (!layer) {
|
||||
return done(layerError);
|
||||
}
|
||||
|
||||
// we have a route
|
||||
// start at param 0
|
||||
req.params = route.params;
|
||||
keys = route.keys;
|
||||
i = 0;
|
||||
|
||||
// param callbacks
|
||||
function param(err) {
|
||||
paramIndex = 0;
|
||||
key = keys[i++];
|
||||
paramVal = key && req.params[key.name];
|
||||
paramCallbacks = key && params[key.name];
|
||||
|
||||
try {
|
||||
if ('route' == err) {
|
||||
nextRoute();
|
||||
} else if (err) {
|
||||
i = 0;
|
||||
callbacks(err);
|
||||
} else if (paramCallbacks && undefined !== paramVal) {
|
||||
paramCallback();
|
||||
} else if (key) {
|
||||
param();
|
||||
} else {
|
||||
i = 0;
|
||||
callbacks();
|
||||
}
|
||||
} catch (err) {
|
||||
param(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;
|
||||
|
||||
// if final route, then we support options
|
||||
if (route) {
|
||||
// we don't run any routes with error first
|
||||
if (layerError) {
|
||||
return next(layerError);
|
||||
}
|
||||
|
||||
var method = req.method;
|
||||
var has_method = route._handles_method(method);
|
||||
|
||||
// build up automatic options response
|
||||
if (!has_method && method === 'OPTIONS') {
|
||||
options.push.apply(options, route._options());
|
||||
}
|
||||
|
||||
// don't even bother
|
||||
if (!has_method && method !== 'HEAD') {
|
||||
return next();
|
||||
}
|
||||
|
||||
// we can now dispatch to the route
|
||||
req.route = route;
|
||||
}
|
||||
|
||||
// Capture one-time layer values
|
||||
req.params = self.mergeParams
|
||||
? mergeParams(layer.params, parentParams)
|
||||
: layer.params;
|
||||
var layerPath = layer.path;
|
||||
|
||||
// this should be done for the layer
|
||||
self.process_params(layer, paramcalled, req, res, function (err) {
|
||||
if (err) {
|
||||
return next(layerError || err);
|
||||
}
|
||||
|
||||
if (route) {
|
||||
return layer.handle_request(req, res, next);
|
||||
}
|
||||
|
||||
trim_prefix(layer, layerError, layerPath, path);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function trim_prefix(layer, layerError, layerPath, path) {
|
||||
var c = path[layerPath.length];
|
||||
if (c && '/' !== c && '.' !== c) return next(layerError);
|
||||
|
||||
// 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] !== '/') {
|
||||
req.url = '/' + req.url;
|
||||
slashAdded = true;
|
||||
}
|
||||
|
||||
// Setup base URL (no trailing slash)
|
||||
req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
|
||||
? removed.substring(0, removed.length - 1)
|
||||
: removed);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Match request to a layer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
proto.match_layer = function match_layer(layer, req, res, done) {
|
||||
var error = null;
|
||||
var path;
|
||||
|
||||
try {
|
||||
path = parseUrl(req).pathname;
|
||||
|
||||
if (!layer.match(path)) {
|
||||
path = undefined;
|
||||
}
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
done(error, path);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 layer, keys and values
|
||||
var keys = layer.keys;
|
||||
|
||||
// fast track
|
||||
if (!keys || keys.length === 0) {
|
||||
return 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
|
||||
function param(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (i >= keys.length ) {
|
||||
return done();
|
||||
}
|
||||
|
||||
paramIndex = 0;
|
||||
key = keys[i++];
|
||||
|
||||
if (!key) {
|
||||
return 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
|
||||
};
|
||||
|
||||
param(err);
|
||||
paramCallback();
|
||||
}
|
||||
|
||||
// single param callbacks
|
||||
function paramCallback(err) {
|
||||
var fn = paramCallbacks[paramIndex++];
|
||||
if (err || !fn) return param(err);
|
||||
// single param callbacks
|
||||
function paramCallback(err) {
|
||||
var fn = paramCallbacks[paramIndex++];
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// invoke route callbacks
|
||||
function callbacks(err) {
|
||||
var fn = route.callbacks[i++];
|
||||
try {
|
||||
if ('route' == err) {
|
||||
nextRoute();
|
||||
} else if (err && fn) {
|
||||
if (fn.length < 4) return callbacks(err);
|
||||
fn(err, req, res, callbacks);
|
||||
} else if (fn) {
|
||||
if (fn.length < 4) return fn(req, res, callbacks);
|
||||
callbacks();
|
||||
} else {
|
||||
nextRoute(err);
|
||||
}
|
||||
} catch (err) {
|
||||
callbacks(err);
|
||||
}
|
||||
}
|
||||
})(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt to match a route for `req`
|
||||
* with optional starting index of `i`
|
||||
* defaulting to 0.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @param {Number} i
|
||||
* @return {Route}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Router.prototype.matchRequest = function(req, i, head){
|
||||
var method = req.method.toLowerCase()
|
||||
, url = parse(req)
|
||||
, path = url.pathname
|
||||
, routes = this.map
|
||||
, i = i || 0
|
||||
, route;
|
||||
|
||||
// HEAD support
|
||||
if (!head && 'head' == method) {
|
||||
route = this.matchRequest(req, i, true);
|
||||
if (route) return route;
|
||||
method = 'get';
|
||||
}
|
||||
|
||||
// routes for this method
|
||||
if (routes = routes[method]) {
|
||||
|
||||
// matching routes
|
||||
for (var len = routes.length; i < len; ++i) {
|
||||
route = routes[i];
|
||||
if (route.match(path)) {
|
||||
req._route_index = i;
|
||||
return route;
|
||||
}
|
||||
} catch (e) {
|
||||
paramCallback(e);
|
||||
}
|
||||
}
|
||||
|
||||
param();
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt to match a route for `method`
|
||||
* and `url` with optional starting
|
||||
* index of `i` defaulting to 0.
|
||||
* Use the given middleware function, with optional path, defaulting to "/".
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} url
|
||||
* @param {Number} i
|
||||
* @return {Route}
|
||||
* @api private
|
||||
* Use (like `.all`) will run for any http METHOD, but it will not add
|
||||
* handlers for those methods so OPTIONS requests will not consider `.use`
|
||||
* functions even if they could respond.
|
||||
*
|
||||
* The other difference is that _route_ path is stripped and not visible
|
||||
* to the handler function. The main effect of this feature is that mounted
|
||||
* handlers can operate without any code changes regardless of the "prefix"
|
||||
* pathname.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.match = function(method, url, i, head){
|
||||
var req = { method: method, url: url };
|
||||
return this.matchRequest(req, i, head);
|
||||
};
|
||||
proto.use = function use(fn) {
|
||||
var offset = 0;
|
||||
var path = '/';
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* Route `method`, `path`, and one or more callbacks.
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
* @param {Function} callback...
|
||||
* @return {Router} for chaining
|
||||
* @api private
|
||||
*/
|
||||
// default path to '/'
|
||||
if (typeof fn !== 'function') {
|
||||
offset = 1;
|
||||
path = fn;
|
||||
}
|
||||
|
||||
Router.prototype.route = function(method, path, callbacks){
|
||||
var method = method.toLowerCase()
|
||||
, callbacks = utils.flatten([].slice.call(arguments, 2));
|
||||
var callbacks = utils.flatten(slice.call(arguments, offset));
|
||||
|
||||
// ensure path was given
|
||||
if (!path) throw new Error('Router#' + method + '() requires a path');
|
||||
if (callbacks.length === 0) {
|
||||
throw new TypeError('Router.use() requires callback function');
|
||||
}
|
||||
|
||||
// ensure all callbacks are functions
|
||||
callbacks.forEach(function(fn, i){
|
||||
if ('function' == typeof fn) return;
|
||||
var type = {}.toString.call(fn);
|
||||
var msg = '.' + method + '() requires callback functions but got a ' + type;
|
||||
throw new Error(msg);
|
||||
callbacks.forEach(function (fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
var type = toString.call(fn);
|
||||
var msg = 'Router.use() requires callback function but got a ' + type;
|
||||
throw new TypeError(msg);
|
||||
}
|
||||
|
||||
// add the middleware
|
||||
debug('use %s %s', path, fn.name || '<anonymous>');
|
||||
|
||||
var layer = new Layer(path, {
|
||||
sensitive: self.caseSensitive,
|
||||
strict: false,
|
||||
end: false
|
||||
}, fn);
|
||||
|
||||
layer.route = undefined;
|
||||
|
||||
self.stack.push(layer);
|
||||
});
|
||||
|
||||
// create the route
|
||||
debug('defined %s %s', method, path);
|
||||
var route = new Route(method, path, callbacks, {
|
||||
sensitive: this.caseSensitive,
|
||||
strict: this.strict
|
||||
});
|
||||
|
||||
// add it
|
||||
(this.map[method] = this.map[method] || []).push(route);
|
||||
return this;
|
||||
};
|
||||
|
||||
methods.forEach(function(method){
|
||||
Router.prototype[method] = function(path){
|
||||
var args = [method].concat([].slice.call(arguments));
|
||||
this.route.apply(this, args);
|
||||
/**
|
||||
* Create a new Route for the given path.
|
||||
*
|
||||
* Each route contains a separate middleware stack and VERB handlers.
|
||||
*
|
||||
* See the Route api documentation for details on adding handlers
|
||||
* and middleware to routes.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Route}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
proto.route = function(path){
|
||||
var route = new Route(path);
|
||||
|
||||
var layer = new Layer(path, {
|
||||
sensitive: this.caseSensitive,
|
||||
strict: this.strict,
|
||||
end: true
|
||||
}, route.dispatch.bind(route));
|
||||
|
||||
layer.route = route;
|
||||
|
||||
this.stack.push(layer);
|
||||
return route;
|
||||
};
|
||||
|
||||
// create Router#VERB functions
|
||||
methods.concat('all').forEach(function(method){
|
||||
proto[method] = function(path){
|
||||
var route = this.route(path)
|
||||
route[method].apply(route, slice.call(arguments, 1));
|
||||
return this;
|
||||
};
|
||||
});
|
||||
|
||||
// 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);
|
||||
};
|
||||
}
|
||||
|
||||
151
lib/router/layer.js
Normal file
151
lib/router/layer.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var pathRegexp = require('path-to-regexp');
|
||||
var debug = require('debug')('express:router:layer');
|
||||
|
||||
/**
|
||||
* Expose `Layer`.
|
||||
*/
|
||||
|
||||
module.exports = Layer;
|
||||
|
||||
function Layer(path, options, fn) {
|
||||
if (!(this instanceof Layer)) {
|
||||
return new Layer(path, options, fn);
|
||||
}
|
||||
|
||||
debug('new %s', path);
|
||||
options = 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`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Layer.prototype.match = function match(path) {
|
||||
if (this.regexp.fast_slash) {
|
||||
// fast path non-ending match for / (everything matches)
|
||||
this.params = {};
|
||||
this.path = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
var m = this.regexp.exec(path);
|
||||
|
||||
if (!m) {
|
||||
this.params = undefined;
|
||||
this.path = undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
// store values
|
||||
this.params = {};
|
||||
this.path = m[0];
|
||||
|
||||
var keys = this.keys;
|
||||
var params = this.params;
|
||||
var n = 0;
|
||||
var key;
|
||||
var val;
|
||||
|
||||
for (var i = 1, len = m.length; i < len; ++i) {
|
||||
key = keys[i - 1];
|
||||
val = decode_param(m[i]);
|
||||
|
||||
if (key) {
|
||||
params[key.name] = val;
|
||||
} else {
|
||||
params[n++] = 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;
|
||||
}
|
||||
}
|
||||
61
lib/router/match.js
Normal file
61
lib/router/match.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var pathRegexp = require('path-to-regexp');
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var debug = require('debug')('express:router:route');
|
||||
var Layer = require('./layer');
|
||||
var methods = require('methods');
|
||||
var utils = require('../utils');
|
||||
|
||||
/**
|
||||
@@ -12,61 +14,162 @@ var utils = require('../utils');
|
||||
module.exports = Route;
|
||||
|
||||
/**
|
||||
* Initialize `Route` with the given HTTP `method`, `path`,
|
||||
* and an array of `callbacks` and `options`.
|
||||
* Initialize `Route` with the given `path`,
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `sensitive` enable case-sensitive routes
|
||||
* - `strict` enable strict matching for trailing slashes
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
* @param {Array} callbacks
|
||||
* @param {Object} options.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Route(method, path, callbacks, options) {
|
||||
options = options || {};
|
||||
function Route(path) {
|
||||
debug('new %s', path);
|
||||
this.path = path;
|
||||
this.method = method;
|
||||
this.callbacks = callbacks;
|
||||
this.regexp = utils.pathRegexp(path
|
||||
, this.keys = []
|
||||
, options.sensitive
|
||||
, options.strict);
|
||||
this.stack = [];
|
||||
|
||||
// route handlers for various http methods
|
||||
this.methods = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this route matches `path`, if so
|
||||
* populate `.params`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Route.prototype.match = function(path){
|
||||
var keys = this.keys
|
||||
, params = this.params = []
|
||||
, m = this.regexp.exec(path);
|
||||
|
||||
if (!m) return false;
|
||||
|
||||
for (var i = 1, len = m.length; i < len; ++i) {
|
||||
var key = keys[i - 1];
|
||||
|
||||
var val = 'string' == typeof m[i]
|
||||
? decodeURIComponent(m[i])
|
||||
: m[i];
|
||||
|
||||
if (key) {
|
||||
params[key.name] = val;
|
||||
} else {
|
||||
params.push(val);
|
||||
}
|
||||
Route.prototype._handles_method = function _handles_method(method) {
|
||||
if (this.methods._all) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
Route.prototype._options = function(){
|
||||
return Object.keys(this.methods).map(function(method) {
|
||||
return method.toUpperCase();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* dispatch req, res into this route
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Route.prototype.dispatch = function(req, res, done){
|
||||
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 = this;
|
||||
|
||||
next();
|
||||
|
||||
function next(err) {
|
||||
if (err && err === 'route') {
|
||||
return done();
|
||||
}
|
||||
|
||||
var layer = stack[idx++];
|
||||
if (!layer) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (layer.method && layer.method !== method) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
layer.handle_error(err, req, res, next);
|
||||
} else {
|
||||
layer.handle_request(req, res, next);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a handler for all HTTP verbs to this route.
|
||||
*
|
||||
* Behaves just like middleware and can respond or call `next`
|
||||
* to continue processing.
|
||||
*
|
||||
* You can use multiple `.all` call to add multiple handlers.
|
||||
*
|
||||
* function check_something(req, res, next){
|
||||
* next();
|
||||
* };
|
||||
*
|
||||
* function validate_user(req, res, next){
|
||||
* next();
|
||||
* };
|
||||
*
|
||||
* route
|
||||
* .all(validate_user)
|
||||
* .all(check_something)
|
||||
* .get(function(req, res, next){
|
||||
* res.send('hello world');
|
||||
* });
|
||||
*
|
||||
* @param {function} handler
|
||||
* @return {Route} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Route.prototype.all = function(){
|
||||
var self = this;
|
||||
var callbacks = utils.flatten([].slice.call(arguments));
|
||||
callbacks.forEach(function(fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
var type = {}.toString.call(fn);
|
||||
var msg = 'Route.all() requires callback functions but got a ' + type;
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
var layer = Layer('/', {}, fn);
|
||||
layer.method = undefined;
|
||||
|
||||
self.methods._all = true;
|
||||
self.stack.push(layer);
|
||||
});
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
methods.forEach(function(method){
|
||||
Route.prototype[method] = function(){
|
||||
var self = this;
|
||||
var callbacks = utils.flatten([].slice.call(arguments));
|
||||
|
||||
callbacks.forEach(function(fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
var type = {}.toString.call(fn);
|
||||
var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
debug('%s %s', method, self.path);
|
||||
|
||||
var layer = Layer('/', {}, fn);
|
||||
layer.method = method;
|
||||
|
||||
self.methods[method] = true;
|
||||
self.stack.push(layer);
|
||||
});
|
||||
return self;
|
||||
};
|
||||
});
|
||||
|
||||
343
lib/utils.js
343
lib/utils.js
@@ -1,46 +1,58 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var mime = require('connect').mime
|
||||
, crc32 = require('buffer-crc32');
|
||||
var mime = require('send').mime;
|
||||
var crc32 = require('buffer-crc32');
|
||||
var crypto = require('crypto');
|
||||
var basename = require('path').basename;
|
||||
var proxyaddr = require('proxy-addr');
|
||||
var qs = require('qs');
|
||||
var querystring = require('querystring');
|
||||
var typer = require('media-typer');
|
||||
|
||||
/**
|
||||
* toString ref.
|
||||
*/
|
||||
|
||||
var toString = {}.toString;
|
||||
|
||||
/**
|
||||
* 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 etag(body, encoding){
|
||||
if (body.length === 0) {
|
||||
// fast-path empty body
|
||||
return '"1B2M2Y8AsgTpgAmY7PhCfg=="'
|
||||
}
|
||||
|
||||
var hash = crypto
|
||||
.createHash('md5')
|
||||
.update(body, encoding)
|
||||
.digest('base64')
|
||||
return '"' + hash + '"'
|
||||
};
|
||||
|
||||
/**
|
||||
* Make `locals()` bound to the given `obj`.
|
||||
* Return weak ETag for `body`.
|
||||
*
|
||||
* This is used for `app.locals` and `res.locals`.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @return {Function}
|
||||
* @param {String|Buffer} body
|
||||
* @param {String} [encoding]
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.locals = function(obj){
|
||||
function locals(obj){
|
||||
for (var key in obj) locals[key] = obj[key];
|
||||
return obj;
|
||||
};
|
||||
exports.wetag = function wetag(body, encoding){
|
||||
if (body.length === 0) {
|
||||
// fast-path empty body
|
||||
return 'W/"0-0"'
|
||||
}
|
||||
|
||||
return locals;
|
||||
var buf = Buffer.isBuffer(body)
|
||||
? body
|
||||
: new Buffer(body, encoding)
|
||||
var len = buf.length
|
||||
return 'W/"' + len.toString(16) + '-' + crc32.unsigned(buf) + '"'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -54,6 +66,7 @@ exports.locals = function(obj){
|
||||
exports.isAbsolute = function(path){
|
||||
if ('/' == path[0]) return true;
|
||||
if (':' == path[1] && '\\' == path[2]) return true;
|
||||
if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -65,8 +78,8 @@ exports.isAbsolute = function(path){
|
||||
*/
|
||||
|
||||
exports.flatten = function(arr, ret){
|
||||
var ret = ret || []
|
||||
, len = arr.length;
|
||||
ret = ret || [];
|
||||
var len = arr.length;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (Array.isArray(arr[i])) {
|
||||
exports.flatten(arr[i], ret);
|
||||
@@ -110,125 +123,25 @@ exports.normalizeTypes = function(types){
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the acceptable type in `types`, if any.
|
||||
* Generate Content-Disposition header appropriate for the filename.
|
||||
* non-ascii filenames are urlencoded and a filename* parameter is added
|
||||
*
|
||||
* @param {Array} types
|
||||
* @param {String} str
|
||||
* @param {String} filename
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.acceptsArray = function(types, str){
|
||||
// accept anything when Accept is not present
|
||||
if (!str) return types[0];
|
||||
|
||||
// parse
|
||||
var accepted = exports.parseAccept(str)
|
||||
, normalized = exports.normalizeTypes(types)
|
||||
, len = accepted.length;
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
for (var j = 0, jlen = types.length; j < jlen; ++j) {
|
||||
if (exports.accept(normalized[j], accepted[i])) {
|
||||
return types[j];
|
||||
}
|
||||
}
|
||||
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 + '"';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if `type(s)` are acceptable based on
|
||||
* the given `str`.
|
||||
*
|
||||
* @param {String|Array} type(s)
|
||||
* @param {String} str
|
||||
* @return {Boolean|String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.accepts = function(type, str){
|
||||
if ('string' == typeof type) type = type.split(/ *, */);
|
||||
return exports.acceptsArray(type, str);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if `type` array is acceptable for `other`.
|
||||
*
|
||||
* @param {Object} type
|
||||
* @param {Object} other
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.accept = function(type, other){
|
||||
var t = type.value.split('/');
|
||||
return (t[0] == other.type || '*' == other.type)
|
||||
&& (t[1] == other.subtype || '*' == other.subtype)
|
||||
&& paramsEqual(type.params, other.params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if accept params are equal.
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function paramsEqual(a, b){
|
||||
return !Object.keys(a).some(function(k) {
|
||||
return a[k] != b[k];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse accept `str`, returning
|
||||
* an array objects containing
|
||||
* `.type` and `.subtype` along
|
||||
* with the values provided by
|
||||
* `parseQuality()`.
|
||||
*
|
||||
* @param {Type} name
|
||||
* @return {Type}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseAccept = function(str){
|
||||
return exports
|
||||
.parseParams(str)
|
||||
.map(function(obj){
|
||||
var parts = obj.value.split('/');
|
||||
obj.type = parts[0];
|
||||
obj.subtype = parts[1];
|
||||
return obj;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse quality `str`, returning an
|
||||
* array of objects with `.value`,
|
||||
* `.quality` and optional `.params`
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseParams = function(str){
|
||||
return str
|
||||
.split(/ *, */)
|
||||
.map(acceptParams)
|
||||
.filter(function(obj){
|
||||
return obj.quality;
|
||||
})
|
||||
.sort(function(a, b){
|
||||
if (a.quality === b.quality) {
|
||||
return a.originalIndex - b.originalIndex;
|
||||
} else {
|
||||
return b.quality - a.quality;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -258,56 +171,132 @@ function acceptParams(str, index) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters in the given string of html.
|
||||
* Compile "etag" value to function.
|
||||
*
|
||||
* @param {String} html
|
||||
* @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.escape = function(html) {
|
||||
return String(html)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize the given path string,
|
||||
* returning a regular expression.
|
||||
* Return new empty objet.
|
||||
*
|
||||
* An empty array should be passed,
|
||||
* which will contain the placeholder
|
||||
* key names. For example "/user/:id" will
|
||||
* then contain ["id"].
|
||||
*
|
||||
* @param {String|RegExp|Array} path
|
||||
* @param {Array} keys
|
||||
* @param {Boolean} sensitive
|
||||
* @param {Boolean} strict
|
||||
* @return {RegExp}
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.pathRegexp = function(path, keys, sensitive, strict) {
|
||||
if (toString.call(path) == '[object RegExp]') return path;
|
||||
if (Array.isArray(path)) path = '(' + path.join('|') + ')';
|
||||
path = path
|
||||
.concat(strict ? '' : '/?')
|
||||
.replace(/\/\(/g, '(?:/')
|
||||
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){
|
||||
keys.push({ name: key, optional: !! optional });
|
||||
slash = slash || '';
|
||||
return ''
|
||||
+ (optional ? '' : slash)
|
||||
+ '(?:'
|
||||
+ (optional ? slash : '')
|
||||
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
|
||||
+ (optional || '')
|
||||
+ (star ? '(/*)?' : '');
|
||||
})
|
||||
.replace(/([\/.])/g, '\\$1')
|
||||
.replace(/\*/g, '(.*)');
|
||||
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
|
||||
function newObject() {
|
||||
return {};
|
||||
}
|
||||
|
||||
16
lib/view.js
16
lib/view.js
@@ -2,14 +2,14 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var path = require('path')
|
||||
, fs = require('fs')
|
||||
, utils = require('./utils')
|
||||
, dirname = path.dirname
|
||||
, basename = path.basename
|
||||
, extname = path.extname
|
||||
, exists = fs.existsSync || path.existsSync
|
||||
, join = path.join;
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var utils = require('./utils');
|
||||
var dirname = path.dirname;
|
||||
var basename = path.basename;
|
||||
var extname = path.extname;
|
||||
var exists = fs.existsSync || path.existsSync;
|
||||
var join = path.join;
|
||||
|
||||
/**
|
||||
* Expose `View`.
|
||||
|
||||
109
package.json
109
package.json
@@ -1,50 +1,17 @@
|
||||
{
|
||||
"name": "express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "3.3.4",
|
||||
"description": "Fast, unopinionated, minimalist web framework",
|
||||
"version": "4.7.4",
|
||||
"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": "Guillermo Rauch",
|
||||
"email": "rauchg@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": {
|
||||
"connect": "2.8.4",
|
||||
"commander": "1.2.0",
|
||||
"range-parser": "0.0.4",
|
||||
"mkdirp": "0.3.5",
|
||||
"cookie": "0.1.0",
|
||||
"buffer-crc32": "0.2.1",
|
||||
"fresh": "0.1.0",
|
||||
"methods": "0.0.1",
|
||||
"send": "0.1.3",
|
||||
"cookie-signature": "1.0.1",
|
||||
"debug": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ejs": "*",
|
||||
"mocha": "*",
|
||||
"jade": "0.30.0",
|
||||
"hjs": "*",
|
||||
"stylus": "*",
|
||||
"should": "*",
|
||||
"connect-redis": "*",
|
||||
"marked": "*",
|
||||
"supertest": "0.6.0"
|
||||
},
|
||||
"keywords": [
|
||||
"express",
|
||||
"framework",
|
||||
@@ -56,16 +23,58 @@
|
||||
"app",
|
||||
"api"
|
||||
],
|
||||
"repository": "git://github.com/visionmedia/express",
|
||||
"main": "index",
|
||||
"bin": {
|
||||
"express": "./bin/express"
|
||||
"repository": "visionmedia/express",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.0.7",
|
||||
"buffer-crc32": "0.2.3",
|
||||
"debug": "1.0.4",
|
||||
"depd": "0.4.4",
|
||||
"escape-html": "1.0.1",
|
||||
"finalhandler": "0.1.0",
|
||||
"media-typer": "0.2.0",
|
||||
"methods": "1.1.0",
|
||||
"parseurl": "~1.2.0",
|
||||
"path-to-regexp": "0.1.3",
|
||||
"proxy-addr": "1.0.1",
|
||||
"range-parser": "1.0.0",
|
||||
"send": "0.7.4",
|
||||
"serve-static": "~1.4.4",
|
||||
"type-is": "~1.3.2",
|
||||
"vary": "0.1.0",
|
||||
"cookie": "0.1.2",
|
||||
"fresh": "0.2.2",
|
||||
"cookie-signature": "1.0.4",
|
||||
"merge-descriptors": "0.0.2",
|
||||
"qs": "0.6.6",
|
||||
"utils-merge": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"after": "0.8.1",
|
||||
"istanbul": "0.3.0",
|
||||
"mocha": "~1.21.0",
|
||||
"should": "~4.0.4",
|
||||
"supertest": "~0.13.0",
|
||||
"connect-redis": "~2.0.0",
|
||||
"ejs": "~1.0.0",
|
||||
"marked": "0.3.2",
|
||||
"hjs": "~0.0.6",
|
||||
"body-parser": "~1.5.2",
|
||||
"cookie-parser": "~1.3.1",
|
||||
"express-session": "~1.7.2",
|
||||
"jade": "~1.5.0",
|
||||
"method-override": "~2.1.1",
|
||||
"morgan": "~1.2.2",
|
||||
"multiparty": "~3.3.1",
|
||||
"vhost": "2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublish": "npm prune",
|
||||
"test": "make test"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"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-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ var app = express()
|
||||
, blog = express()
|
||||
, admin = express();
|
||||
|
||||
// app.use(express.logger('dev'))
|
||||
blog.use('/admin', admin);
|
||||
app.use('/blog', blog);
|
||||
app.set('views', __dirname + '/views');
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
NODE_ENV=production node ./support/app &
|
||||
pid=$!
|
||||
|
||||
bench() {
|
||||
ab -n 5000 -c 50 -k -q http://127.0.0.1:8000$1 \
|
||||
| grep "Requests per" \
|
||||
| cut -d ' ' -f 7 \
|
||||
| xargs echo "$2:"
|
||||
}
|
||||
|
||||
bench_conditional() {
|
||||
ab -n 5000 -c 50 -H "If-None-Match: $3" -k -q http://127.0.0.1:8000$1 \
|
||||
| grep "Requests per" \
|
||||
| cut -d ' ' -f 7 \
|
||||
| xargs echo "$2:"
|
||||
}
|
||||
|
||||
sleep .5
|
||||
bench / "Hello World"
|
||||
bench /blog "Mounted Hello World"
|
||||
bench /blog/admin "Mounted 2 Hello World"
|
||||
bench /middleware "Middleware"
|
||||
bench /match "Router"
|
||||
bench /render "Render"
|
||||
bench /json "JSON tiny"
|
||||
bench /json/15 "JSON small"
|
||||
bench /json/50 "JSON medium"
|
||||
bench /json/150 "JSON large"
|
||||
|
||||
kill -9 $pid
|
||||
21
support/docs
21
support/docs
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var buf = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', function(chunk){
|
||||
buf += chunk;
|
||||
}).on('end', function(){
|
||||
var comments = JSON.parse(buf);
|
||||
comments.forEach(function(comment){
|
||||
if (comment.ignore) return;
|
||||
if (comment.isPrivate) return;
|
||||
if (!comment.ctx) return;
|
||||
if (!comment.description.full.indexOf('Module dep')) return;
|
||||
var ctx = comment.ctx;
|
||||
console.log();
|
||||
console.log('# %s', ctx.string);
|
||||
console.log();
|
||||
console.log(comment.description.full.trim().replace(/^/gm, ' '));
|
||||
});
|
||||
console.log();
|
||||
}).resume();
|
||||
238
test/Route.js
Normal file
238
test/Route.js
Normal file
@@ -0,0 +1,238 @@
|
||||
|
||||
var after = require('after');
|
||||
var should = require('should');
|
||||
var express = require('../')
|
||||
, Route = express.Route
|
||||
, methods = require('methods')
|
||||
, assert = require('assert');
|
||||
|
||||
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) {
|
||||
req.called = true;
|
||||
next();
|
||||
});
|
||||
|
||||
route.dispatch(req, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
should(req.called).be.ok;
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('should handle VERBS', function(done) {
|
||||
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) {
|
||||
var req = { method: method, url: '/' };
|
||||
route.dispatch(req, {}, cb);
|
||||
});
|
||||
})
|
||||
|
||||
it('should stack', function(done) {
|
||||
var req = { count: 0, method: 'GET', url: '/' };
|
||||
var route = new Route('/foo');
|
||||
|
||||
route.all(function(req, res, next) {
|
||||
req.count++;
|
||||
next();
|
||||
});
|
||||
|
||||
route.all(function(req, res, next) {
|
||||
req.count++;
|
||||
next();
|
||||
});
|
||||
|
||||
route.dispatch(req, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
req.count.should.equal(2);
|
||||
done();
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe('.VERB', function(){
|
||||
it('should support .get', function(done){
|
||||
var req = { method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
route.get(function(req, res, next) {
|
||||
req.called = true;
|
||||
next();
|
||||
})
|
||||
|
||||
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) {
|
||||
throw new Error('not me!');
|
||||
})
|
||||
|
||||
route.post(function(req, res, next) {
|
||||
req.called = true;
|
||||
next();
|
||||
})
|
||||
|
||||
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('');
|
||||
|
||||
route.get(function(req, res, next) {
|
||||
req.order += 'a';
|
||||
next();
|
||||
})
|
||||
|
||||
route.all(function(req, res, next) {
|
||||
req.order += 'b';
|
||||
next();
|
||||
});
|
||||
|
||||
route.get(function(req, res, next) {
|
||||
req.order += 'c';
|
||||
next();
|
||||
})
|
||||
|
||||
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('');
|
||||
|
||||
route.all(function(req, res, next){
|
||||
next(new Error('foobar'));
|
||||
});
|
||||
|
||||
route.all(function(req, res, next){
|
||||
req.order += '0';
|
||||
next();
|
||||
});
|
||||
|
||||
route.all(function(err, req, res, next){
|
||||
req.order += 'a';
|
||||
next(err);
|
||||
});
|
||||
|
||||
route.dispatch(req, {}, function (err) {
|
||||
should(err).be.ok;
|
||||
should(err.message).equal('foobar');
|
||||
req.order.should.equal('a');
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('should handle throw', function(done) {
|
||||
var req = { order: '', method: 'GET', url: '/' };
|
||||
var route = new Route('');
|
||||
|
||||
route.all(function(req, res, next){
|
||||
throw new Error('foobar');
|
||||
});
|
||||
|
||||
route.all(function(req, res, next){
|
||||
req.order += '0';
|
||||
next();
|
||||
});
|
||||
|
||||
route.all(function(err, req, res, next){
|
||||
req.order += 'a';
|
||||
next(err);
|
||||
});
|
||||
|
||||
route.dispatch(req, {}, function (err) {
|
||||
should(err).be.ok;
|
||||
should(err.message).equal('foobar');
|
||||
req.order.should.equal('a');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
})
|
||||
})
|
||||
445
test/Router.js
445
test/Router.js
@@ -1,103 +1,426 @@
|
||||
|
||||
var after = require('after');
|
||||
var express = require('../')
|
||||
, Router = express.Router
|
||||
, request = require('./support/http')
|
||||
, methods = require('methods')
|
||||
, assert = require('assert');
|
||||
|
||||
describe('Router', function(){
|
||||
var router, app;
|
||||
it('should return a function with router methods', function() {
|
||||
var router = Router();
|
||||
assert(typeof router == 'function');
|
||||
|
||||
beforeEach(function(){
|
||||
router = new Router;
|
||||
app = express();
|
||||
})
|
||||
var router = new Router();
|
||||
assert(typeof router == 'function');
|
||||
|
||||
describe('.match(method, url, i)', function(){
|
||||
it('should match based on index', function(){
|
||||
router.route('get', '/foo', function(){});
|
||||
router.route('get', '/foob?', function(){});
|
||||
router.route('get', '/bar', function(){});
|
||||
assert(typeof router.get == 'function');
|
||||
assert(typeof router.handle == 'function');
|
||||
assert(typeof router.use == 'function');
|
||||
});
|
||||
|
||||
var method = 'GET';
|
||||
var url = '/foo?bar=baz';
|
||||
it('should support .use of other routers', function(done){
|
||||
var router = new Router();
|
||||
var another = new Router();
|
||||
|
||||
var route = router.match(method, url, 0);
|
||||
route.constructor.name.should.equal('Route');
|
||||
route.method.should.equal('get');
|
||||
route.path.should.equal('/foo');
|
||||
another.get('/bar', function(req, res){
|
||||
res.end();
|
||||
});
|
||||
router.use('/foo', another);
|
||||
|
||||
var route = router.match(method, url, 1);
|
||||
route.path.should.equal('/foob?');
|
||||
router.handle({ url: '/foo/bar', method: 'GET' }, { end: done });
|
||||
});
|
||||
|
||||
var route = router.match(method, url, 2);
|
||||
assert(!route);
|
||||
it('should support dynamic routes', function(done){
|
||||
var router = new Router();
|
||||
var another = new Router();
|
||||
|
||||
url = '/bar';
|
||||
var route = router.match(method, url);
|
||||
route.path.should.equal('/bar');
|
||||
})
|
||||
})
|
||||
|
||||
describe('.matchRequest(req, i)', function(){
|
||||
it('should match based on index', function(){
|
||||
router.route('get', '/foo', function(){});
|
||||
router.route('get', '/foob?', function(){});
|
||||
router.route('get', '/bar', function(){});
|
||||
var req = { method: 'GET', url: '/foo?bar=baz' };
|
||||
another.get('/:bar', function(req, res){
|
||||
req.params.bar.should.equal('route');
|
||||
res.end();
|
||||
});
|
||||
router.use('/:foo', another);
|
||||
|
||||
var route = router.matchRequest(req, 0);
|
||||
route.constructor.name.should.equal('Route');
|
||||
route.method.should.equal('get');
|
||||
route.path.should.equal('/foo');
|
||||
router.handle({ url: '/test/route', method: 'GET' }, { end: done });
|
||||
});
|
||||
|
||||
var route = router.matchRequest(req, 1);
|
||||
req._route_index.should.equal(1);
|
||||
route.path.should.equal('/foob?');
|
||||
|
||||
var route = router.matchRequest(req, 2);
|
||||
assert(!route);
|
||||
|
||||
req.url = '/bar';
|
||||
var route = router.matchRequest(req);
|
||||
route.path.should.equal('/bar');
|
||||
})
|
||||
})
|
||||
|
||||
describe('.middleware', function(){
|
||||
describe('.handle', function(){
|
||||
it('should dispatch', function(done){
|
||||
router.route('get', '/foo', function(req, res){
|
||||
var router = new Router();
|
||||
|
||||
router.route('/foo').get(function(req, res){
|
||||
res.send('foo');
|
||||
});
|
||||
|
||||
app.use(router.middleware);
|
||||
|
||||
request(app)
|
||||
.get('/foo')
|
||||
.expect('foo', done);
|
||||
var res = {
|
||||
send: function(val) {
|
||||
val.should.equal('foo');
|
||||
done();
|
||||
}
|
||||
}
|
||||
router.handle({ url: '/foo', method: 'GET' }, res);
|
||||
})
|
||||
})
|
||||
|
||||
describe('.multiple callbacks', function(){
|
||||
it('should throw if a callback is null', function(){
|
||||
assert.throws(function () {
|
||||
router.route('get', '/foo', null, function(){});
|
||||
var router = new Router();
|
||||
router.route('/foo').all(null);
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw if a callback is undefined', function(){
|
||||
assert.throws(function () {
|
||||
router.route('get', '/foo', undefined, function(){});
|
||||
var router = new Router();
|
||||
router.route('/foo').all(undefined);
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw if a callback is not a function', function(){
|
||||
assert.throws(function () {
|
||||
router.route('get', '/foo', 'not a function', function(){});
|
||||
var router = new Router();
|
||||
router.route('/foo').all('not a function');
|
||||
})
|
||||
})
|
||||
|
||||
it('should not throw if all callbacks are functions', function(){
|
||||
router.route('get', '/foo', function(){}, function(){});
|
||||
var router = new Router();
|
||||
router.route('/foo').all(function(){}).all(function(){});
|
||||
})
|
||||
})
|
||||
|
||||
describe('error', function(){
|
||||
it('should skip non error middleware', function(done){
|
||||
var router = new Router();
|
||||
|
||||
router.get('/foo', function(req, res, next){
|
||||
next(new Error('foo'));
|
||||
});
|
||||
|
||||
router.get('/bar', function(req, res, next){
|
||||
next(new Error('bar'));
|
||||
});
|
||||
|
||||
router.use(function(req, res, next){
|
||||
assert(false);
|
||||
});
|
||||
|
||||
router.use(function(err, req, res, next){
|
||||
assert.equal(err.message, 'foo');
|
||||
done();
|
||||
});
|
||||
|
||||
router.handle({ url: '/foo', method: 'GET' }, {}, done);
|
||||
});
|
||||
|
||||
it('should handle throwing inside routes with params', function(done) {
|
||||
var router = new Router();
|
||||
|
||||
router.get('/foo/:id', function(req, res, next){
|
||||
throw new Error('foo');
|
||||
});
|
||||
|
||||
router.use(function(req, res, next){
|
||||
assert(false);
|
||||
});
|
||||
|
||||
router.use(function(err, req, res, next){
|
||||
assert.equal(err.message, 'foo');
|
||||
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 adjust FQDN req.url', function (done) {
|
||||
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
|
||||
var router = new Router();
|
||||
|
||||
router.use('/blog', function (req, res, next) {
|
||||
assert.equal(req.hit++, 0);
|
||||
assert.equal(req.url, 'http://example.com/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.handle(request, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
assert.equal(request.hit, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should adjust FQDN req.url with multiple handlers', function (done) {
|
||||
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
|
||||
var router = new Router();
|
||||
|
||||
router.use(function (req, res, next) {
|
||||
assert.equal(req.hit++, 0);
|
||||
assert.equal(req.url, 'http://example.com/blog/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.use('/blog', function (req, res, next) {
|
||||
assert.equal(req.hit++, 1);
|
||||
assert.equal(req.url, 'http://example.com/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.handle(request, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
assert.equal(request.hit, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should adjust FQDN req.url with multiple routed handlers', function (done) {
|
||||
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
|
||||
var router = new Router();
|
||||
|
||||
router.use('/blog', function (req, res, next) {
|
||||
assert.equal(req.hit++, 0);
|
||||
assert.equal(req.url, 'http://example.com/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.use('/blog', function (req, res, next) {
|
||||
assert.equal(req.hit++, 1);
|
||||
assert.equal(req.url, 'http://example.com/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.use(function (req, res, next) {
|
||||
assert.equal(req.hit++, 2);
|
||||
assert.equal(req.url, 'http://example.com/blog/post/1');
|
||||
next();
|
||||
});
|
||||
|
||||
router.handle(request, {}, function (err) {
|
||||
if (err) return done(err);
|
||||
assert.equal(request.hit, 3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
describe('.all', function() {
|
||||
it('should support using .all to capture all http verbs', function(done){
|
||||
var router = new Router();
|
||||
|
||||
var count = 0;
|
||||
router.all('/foo', function(){ count++; });
|
||||
|
||||
var url = '/foo?bar=baz';
|
||||
|
||||
methods.forEach(function testMethod(method) {
|
||||
router.handle({ url: url, method: method }, {}, function() {});
|
||||
});
|
||||
|
||||
assert.equal(count, methods.length);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
describe('.param', function() {
|
||||
it('should call param function when routing VERBS', function(done) {
|
||||
var router = new Router();
|
||||
|
||||
router.param('id', function(req, res, next, id) {
|
||||
assert.equal(id, '123');
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/foo/:id/bar', function(req, res, next) {
|
||||
assert.equal(req.params.id, '123');
|
||||
next();
|
||||
});
|
||||
|
||||
router.handle({ url: '/foo/123/bar', method: 'get' }, {}, done);
|
||||
});
|
||||
|
||||
it('should call param function when routing middleware', function(done) {
|
||||
var router = new Router();
|
||||
|
||||
router.param('id', function(req, res, next, id) {
|
||||
assert.equal(id, '123');
|
||||
next();
|
||||
});
|
||||
|
||||
router.use('/foo/:id/bar', function(req, res, next) {
|
||||
assert.equal(req.params.id, '123');
|
||||
assert.equal(req.url, '/baz');
|
||||
next();
|
||||
});
|
||||
|
||||
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('../support/http');
|
||||
|
||||
function redirects(to, fn){
|
||||
return function(err, res){
|
||||
res.statusCode.should.equal(302)
|
||||
res.headers.should.have.property('location').match(to);
|
||||
fn()
|
||||
}
|
||||
}
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
var request = require('../support/http')
|
||||
var request = require('supertest')
|
||||
, app = require('../../examples/content-negotiation');
|
||||
|
||||
describe('content-negotiation', function(){
|
||||
@@ -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,6 +1,6 @@
|
||||
|
||||
var app = require('../../examples/cookies/app')
|
||||
, request = require('../support/http');
|
||||
, request = require('supertest');
|
||||
|
||||
describe('cookies', function(){
|
||||
describe('GET /', 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,6 +1,6 @@
|
||||
|
||||
var app = require('../../examples/downloads/app')
|
||||
, request = require('../support/http');
|
||||
, request = require('supertest');
|
||||
|
||||
describe('downloads', function(){
|
||||
describe('GET /', function(){
|
||||
@@ -30,4 +30,4 @@ describe('downloads', function(){
|
||||
.expect(404, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
var request = require('../support/http')
|
||||
var request = require('supertest')
|
||||
, app = require('../../examples/ejs');
|
||||
|
||||
describe('ejs', function(){
|
||||
@@ -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/error-pages')
|
||||
, request = require('../support/http');
|
||||
, request = require('supertest');
|
||||
|
||||
describe('error-pages', function(){
|
||||
describe('GET /', function(){
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user