Compare commits

...

289 Commits
4.4.2 ... 4.9.3

Author SHA1 Message Date
Douglas Christopher Wilson
bc38d896ea 4.9.3 2014-09-18 10:45:35 -07:00
Douglas Christopher Wilson
b2518fe135 Merge tag '3.17.3' 2014-09-18 10:43:35 -07:00
Douglas Christopher Wilson
63286e1192 3.17.3 2014-09-18 10:38:31 -07:00
Douglas Christopher Wilson
c00f2f8596 deps: proxy-addr@~1.0.2 2014-09-18 10:36:21 -07:00
Douglas Christopher Wilson
91891e3aee 4.9.2 2014-09-17 20:49:01 -07:00
Douglas Christopher Wilson
728917164c Fix router.use to accept array of middleware without path 2014-09-17 19:18:13 -07:00
Douglas Christopher Wilson
bf1980f1b4 Improve error message for bad app.use arguments 2014-09-17 19:18:10 -07:00
Douglas Christopher Wilson
3c1a964362 Fix regression for empty string path in app.use
fixes #2361
fixes #2362
2014-09-17 19:18:01 -07:00
Douglas Christopher Wilson
947fb8b274 4.9.1 2014-09-16 23:31:57 -07:00
Douglas Christopher Wilson
c5193536e5 deps: update example dependencies 2014-09-16 23:24:23 -07:00
Douglas Christopher Wilson
d08fd64190 deps: serve-static@~1.6.2 2014-09-16 23:23:14 -07:00
Douglas Christopher Wilson
2470ae6c72 deps: etag@~1.3.1 2014-09-16 23:21:45 -07:00
Douglas Christopher Wilson
916a75cf19 Merge tag '3.17.2' 2014-09-16 23:08:03 -07:00
Douglas Christopher Wilson
daacb749c1 deps: remove un-used buffer-crc32 dependency 2014-09-16 23:03:04 -07:00
Douglas Christopher Wilson
f29399c4e1 3.17.2 2014-09-16 00:14:41 -07:00
Douglas Christopher Wilson
f6ac068ab0 Use crc instead of buffer-crc32 for speed 2014-09-16 00:11:08 -07:00
Douglas Christopher Wilson
7eb65eeca2 deps: send@0.9.2 2014-09-15 23:59:02 -07:00
Douglas Christopher Wilson
178fe15091 deps: depd@0.4.5 2014-09-15 23:57:41 -07:00
Douglas Christopher Wilson
381f278d0a deps: connect@2.26.1 2014-09-15 23:56:36 -07:00
Douglas Christopher Wilson
534fa181c6 Add test-tap npm script for TAP output
closes #2359
2014-09-15 21:41:35 -07:00
Julien Gilli
80847d8c82 tests: fix broken tests with node v0.12 branch
Currently, test/req.ip.js assumes that the connection between the client
and the server is an IPv4 connection. However, depending on the
configuration of the host where this test runs, the connection can be an
IPv4 one or an IPv6 one using an IPv4 mapped address. In the future, it
could also be a "full" IPv6 connection.

This change makes this test handle any type of address.

fixes #2342
2014-09-15 21:41:34 -07:00
Fei Yao
bb8abf1f90 Remove unused require in router match
closes #2358
2014-09-14 09:14:24 -07:00
Douglas Christopher Wilson
cf41a8f254 Fix app.use to accept array of middleware without path
fixes #2356
2014-09-12 17:46:49 -04:00
Douglas Christopher Wilson
1716e3b067 4.9.0 2014-09-09 00:32:17 -04:00
Seth Samuel
12f92a50dc Add res.sendStatus
closes #2269
closes #2297
closes #2340
2014-09-09 00:24:11 -04:00
Douglas Christopher Wilson
51d33edb79 Use etag to generate ETag headers 2014-09-09 00:13:49 -04:00
Douglas Christopher Wilson
2a0c35a108 Invoke callback for sendfile when client aborts
fixes #2189
fixes #2300
closes #2303
closes #2305
2014-09-09 00:12:49 -04:00
Douglas Christopher Wilson
d4de82b853 deps: accepts@~1.1.0 2014-09-09 00:12:10 -04:00
Douglas Christopher Wilson
e2102263ce deps: serve-static@~1.6.1 2014-09-09 00:11:04 -04:00
Douglas Christopher Wilson
d2b1a89b4a deps: type-is@~1.5.1 2014-09-09 00:10:02 -04:00
Douglas Christopher Wilson
d9937c628a deps: finalhandler@0.2.0 2014-09-09 00:08:07 -04:00
Douglas Christopher Wilson
276db8c49a deps: update example dependencies 2014-09-09 00:06:27 -04:00
Douglas Christopher Wilson
1768d94a1a deps: qs@2.2.3 2014-09-09 00:04:08 -04:00
Douglas Christopher Wilson
6bc7574ab5 docs: typo fixes
closes #2316
2014-09-08 23:53:21 -04:00
Douglas Christopher Wilson
3dca534995 Merge tag '3.17.1' 2014-09-08 23:48:59 -04:00
Douglas Christopher Wilson
4b1b8e420f 3.17.1 2014-09-08 23:45:38 -04:00
Douglas Christopher Wilson
70767b19ac Fix error in req.subdomains on empty host 2014-09-08 23:45:34 -04:00
Douglas Christopher Wilson
7d277c1c15 docs: remove erroneous from history 2014-09-08 23:44:24 -04:00
Douglas Christopher Wilson
fa1fcd9fec 3.17.0 2014-09-08 23:07:02 -04:00
Douglas Christopher Wilson
2de6514b4b Support IP address host in req.subdomains
fixes #2308
2014-09-08 23:04:06 -04:00
Douglas Christopher Wilson
d07c06363f Support X-Forwarded-Host in req.subdomains 2014-09-08 23:02:42 -04:00
Douglas Christopher Wilson
4e97533fd2 deps: jade@~1.6.0 2014-09-08 22:48:47 -04:00
Douglas Christopher Wilson
d7d6219a1e deps: range-parser@~1.0.2 2014-09-08 22:47:54 -04:00
Douglas Christopher Wilson
9b18461bbc deps: vary@~1.0.0 2014-09-08 21:18:28 -04:00
Douglas Christopher Wilson
b77aa38c98 deps: cookie-signature@1.0.5 2014-09-08 21:17:03 -04:00
Douglas Christopher Wilson
cbb251377e deps: fresh@0.2.4 2014-09-08 21:14:27 -04:00
Douglas Christopher Wilson
d6ed469de3 deps: send@0.9.1 2014-09-08 21:13:20 -04:00
Douglas Christopher Wilson
49284c236b deps: debug@~2.0.0 2014-09-08 21:11:41 -04:00
Douglas Christopher Wilson
be18487f7d deps: media-typer@0.3.0 2014-09-08 21:10:57 -04:00
Douglas Christopher Wilson
094ff11949 deps: connect@2.26.0 2014-09-08 21:09:22 -04:00
lemmy
d2d0afff64 Remove unused variable
closes #2345
2014-09-08 20:59:03 -04:00
Douglas Christopher Wilson
33bb8fc4b6 examples: fix up route-separation code
closes #2341
2014-09-08 20:51:32 -04:00
Douglas Christopher Wilson
b97f6eb506 examples: fix github view example
closes #2344
2014-09-08 19:31:58 -04:00
Douglas Christopher Wilson
621d074bd8 4.8.8 2014-09-05 02:26:12 -04:00
Douglas Christopher Wilson
d7e7b2e7d7 deps: update example dependencies 2014-09-05 02:24:02 -04:00
Douglas Christopher Wilson
2d6b735b4f deps: serve-static@~1.5.4 2014-09-05 02:21:27 -04:00
Douglas Christopher Wilson
a3115882d4 Merge tag '3.16.10' 2014-09-05 02:20:02 -04:00
Douglas Christopher Wilson
3d188fe13e 3.16.10 2014-09-05 02:17:55 -04:00
Douglas Christopher Wilson
8327708ec2 deps: istanbul@0.3.2 2014-09-05 01:57:02 -04:00
Douglas Christopher Wilson
c8640b3465 deps: send@0.8.5 2014-09-05 01:56:27 -04:00
Douglas Christopher Wilson
3ce5f9b493 deps: connect@2.25.10 2014-09-05 01:55:24 -04:00
Douglas Christopher Wilson
46f0bfc65f 4.8.7 2014-08-30 01:37:52 -04:00
Douglas Christopher Wilson
0b49e7f1fd deps: update example dependencies 2014-08-30 01:33:05 -04:00
Douglas Christopher Wilson
f6f47f428c deps: qs@2.2.2 2014-08-30 01:30:56 -04:00
Douglas Christopher Wilson
20635d03fc Merge tag '3.16.9' 2014-08-30 01:28:38 -04:00
Douglas Christopher Wilson
4d032cda05 3.16.9 2014-08-30 01:22:06 -04:00
Douglas Christopher Wilson
4127ba10b0 deps: connect@2.25.9 2014-08-30 01:19:53 -04:00
Douglas Christopher Wilson
b6ae091bdf 4.8.6 2014-08-27 21:51:37 -04:00
Douglas Christopher Wilson
a206b4e273 deps: update example dependencies 2014-08-27 21:25:56 -04:00
Douglas Christopher Wilson
2b2733c235 deps: qs@2.2.0 2014-08-27 21:22:41 -04:00
Douglas Christopher Wilson
7fb7bcc0f7 Merge tag '3.16.8' 2014-08-27 21:20:53 -04:00
Douglas Christopher Wilson
0299bee8fa 3.16.8 2014-08-27 21:09:18 -04:00
Douglas Christopher Wilson
6a581c9961 deps: connect@2.25.8 2014-08-27 21:04:41 -04:00
Douglas Christopher Wilson
4986b1cb4c tests: add some res.sendfile directory tests 2014-08-24 13:28:39 -04:00
Douglas Christopher Wilson
3de4e4276d docs: fix logo link 2014-08-24 10:05:24 -04:00
Douglas Christopher Wilson
27f195374d 4.8.5 2014-08-18 23:05:16 -04:00
Douglas Christopher Wilson
ff0de5eb27 deps: update example dependencies 2014-08-18 23:00:01 -04:00
Douglas Christopher Wilson
f4ddef1570 deps: serve-static@~1.5.3 2014-08-18 22:58:47 -04:00
Douglas Christopher Wilson
9eafaa23d8 Merge tag '3.16.7' 2014-08-18 22:54:25 -04:00
Douglas Christopher Wilson
0b12cc0cac 3.16.7 2014-08-18 22:45:17 -04:00
Douglas Christopher Wilson
fdd0ccabe8 docs: flatten badges for github look 2014-08-18 22:45:13 -04:00
Douglas Christopher Wilson
8c36eab679 docs: use shields.io badges 2014-08-18 22:19:21 -04:00
Douglas Christopher Wilson
5c145b5490 build: add link to homepage 2014-08-18 22:17:25 -04:00
Raymond Feng
d7bef52591 build: move repository
closes #2268
closes #2270
2014-08-18 22:07:35 -04:00
Douglas Christopher Wilson
1576a95e87 deps: send@0.8.3 2014-08-18 21:57:29 -04:00
Douglas Christopher Wilson
7f92fe66e0 deps: connect@2.25.7 2014-08-18 21:41:14 -04:00
Douglas Christopher Wilson
0cf02d4667 4.8.4 2014-08-15 00:26:39 -04:00
Douglas Christopher Wilson
ef52b80d75 deps: update example dependencies 2014-08-15 00:24:28 -04:00
Douglas Christopher Wilson
1ca01c0c47 tests: add router.use validation tests
closes #2299
2014-08-15 00:22:54 -04:00
Douglas Christopher Wilson
fbceae2716 tests: add additional res.sendFile test
closes #2298
2014-08-15 00:18:54 -04:00
Douglas Christopher Wilson
ad3ca25c58 deps: qs@1.2.2 2014-08-15 00:16:20 -04:00
Douglas Christopher Wilson
666ffc62d8 deps: serve-static@~1.5.2 2014-08-15 00:15:28 -04:00
Douglas Christopher Wilson
6680132392 Merge tag '3.16.6' 2014-08-15 00:14:42 -04:00
Douglas Christopher Wilson
f13f4652da 3.16.6 2014-08-14 23:53:32 -04:00
Douglas Christopher Wilson
059c068c7b deps: send@0.8.2
fixes #2300
2014-08-14 23:52:07 -04:00
Douglas Christopher Wilson
49947f1476 deps: connect@2.25.6 2014-08-14 23:49:39 -04:00
Douglas Christopher Wilson
0dddd772c0 3.16.5 2014-08-11 22:30:09 -04:00
Douglas Christopher Wilson
0f87c6f392 deps: connect@2.25.5 2014-08-11 22:29:35 -04:00
Douglas Christopher Wilson
1643ae442c 4.8.3 2014-08-10 22:30:07 -04:00
Douglas Christopher Wilson
2594f3103b deps: serve-static@~1.5.1 2014-08-10 22:29:24 -04:00
Douglas Christopher Wilson
8473b3c338 deps: qs@1.2.1 2014-08-10 22:27:42 -04:00
Douglas Christopher Wilson
59cb99e9be Merge tag '3.16.4' 2014-08-10 22:26:49 -04:00
Douglas Christopher Wilson
7119f2b16d 3.16.4 2014-08-10 22:22:58 -04:00
Douglas Christopher Wilson
a57efea173 Fix original URL parsing in res.location 2014-08-10 22:20:54 -04:00
Douglas Christopher Wilson
4a4ca7347a deps: parseurl@~1.3.0 2014-08-10 22:19:10 -04:00
Douglas Christopher Wilson
570f60d36e deps: connect@2.25.4 2014-08-10 22:18:12 -04:00
Douglas Christopher Wilson
d13e613584 3.16.3 2014-08-07 22:32:16 -04:00
Douglas Christopher Wilson
9204e1f42a deps: connect@2.25.3 2014-08-07 22:31:53 -04:00
Douglas Christopher Wilson
22ca953e96 4.8.2 2014-08-07 12:05:02 -04:00
Douglas Christopher Wilson
7989c883fe deps: qs@1.2.0 2014-08-07 12:03:53 -04:00
Douglas Christopher Wilson
e05a52078a Merge tag '3.16.2' 2014-08-07 12:01:08 -04:00
Douglas Christopher Wilson
ddac571fdf 3.16.2 2014-08-07 11:59:54 -04:00
Douglas Christopher Wilson
982d24b475 deps: connect@2.25.2 2014-08-07 11:53:40 -04:00
Douglas Christopher Wilson
552b441f8a 4.8.1 2014-08-06 18:21:55 -04:00
Douglas Christopher Wilson
e8f8ea7e05 docs: update examples for deprecations 2014-08-06 18:13:54 -04:00
Douglas Christopher Wilson
4f5b27dd81 deps: update example dependencies 2014-08-06 18:10:32 -04:00
Douglas Christopher Wilson
cca88a7c47 Merge tag '3.16.1' 2014-08-06 18:09:15 -04:00
Douglas Christopher Wilson
ea427c1bb4 3.16.1 2014-08-06 18:07:31 -04:00
Douglas Christopher Wilson
0bd6c311cf deps: mocha@~1.21.4 2014-08-06 18:06:19 -04:00
Douglas Christopher Wilson
7f606ebf29 deps: connect@2.25.1 2014-08-06 18:05:50 -04:00
Douglas Christopher Wilson
a3b5adcf4a deps: qs@1.1.0 2014-08-06 17:50:08 -04:00
Douglas Christopher Wilson
1150ca7264 Fix incorrect deprecation warnings on res.download
fixes #2284
2014-08-06 17:48:36 -04:00
Douglas Christopher Wilson
4aea02310a 4.8.0 2014-08-06 02:50:59 -04:00
Fabien Franzen
17cea29013 Support mounted app as any argument to app.use()
fixes #2277
2014-08-06 02:49:06 -04:00
Douglas Christopher Wilson
8449f23f0d Deprecate res.sendfile 2014-08-06 02:27:16 -04:00
Douglas Christopher Wilson
2cb029f896 Add res.sendFile
fixes #1906
closes #2266
2014-08-06 02:26:51 -04:00
Douglas Christopher Wilson
7e32fa1be6 deps: update example dependencies 2014-08-06 01:59:09 -04:00
Douglas Christopher Wilson
1168d0bb8b deps: qs@1.0.2 2014-08-06 01:58:32 -04:00
Douglas Christopher Wilson
7d0f1c3db9 deps: serve-static@~1.5.0 2014-08-06 01:57:15 -04:00
Douglas Christopher Wilson
19abf7684b Merge tag '3.16.0' 2014-08-06 01:55:45 -04:00
Douglas Christopher Wilson
c652cf7eed 3.16.0 2014-08-06 01:40:46 -04:00
Douglas Christopher Wilson
19fd6f85b0 deps: connect-redis@~1.5.0 2014-08-06 01:39:12 -04:00
Douglas Christopher Wilson
b6c5b0511f deps: jade@~1.5.0 2014-08-06 01:37:58 -04:00
Douglas Christopher Wilson
0e42a37edd deps: send@0.8.1 2014-08-06 01:37:13 -04:00
Douglas Christopher Wilson
b24ed15878 deps: connect@2.25.0 2014-08-06 01:35:00 -04:00
Douglas Christopher Wilson
12e070e39a tests: add encoding tests for res.sendfile 2014-08-04 17:38:04 -04:00
Douglas Christopher Wilson
b886eb52cf 4.7.4 2014-08-04 17:36:37 -04:00
Douglas Christopher Wilson
d8237b976b Merge tag '3.15.3' into 4.7.x 2014-08-04 17:35:42 -04:00
Douglas Christopher Wilson
15590d75b2 3.15.3 2014-08-04 17:31:52 -04:00
Douglas Christopher Wilson
e8b471ff4f fix res.sendfile regression for serving directory index files 2014-08-04 17:30:25 -04:00
Douglas Christopher Wilson
767db01b79 deps: connect@2.24.3 2014-08-04 17:26:43 -04:00
Douglas Christopher Wilson
df413a41f3 deps: serve-static@~1.4.4 2014-08-04 17:14:32 -04:00
Douglas Christopher Wilson
52775a52ad 4.7.3 2014-08-04 16:14:29 -04:00
Douglas Christopher Wilson
112bc92d78 deps: serve-static@~1.4.3 2014-08-04 16:11:52 -04:00
Douglas Christopher Wilson
d8df26680f deps: send@0.7.3 2014-08-04 16:10:32 -04:00
Douglas Christopher Wilson
1854a5d35f 4.7.2 2014-07-27 16:00:54 -04:00
Douglas Christopher Wilson
54d3ffa9a0 deps: update example dependencies 2014-07-27 16:00:17 -04:00
Douglas Christopher Wilson
0ee4dd82b5 deps: serve-static@~1.4.2 2014-07-27 15:59:19 -04:00
Douglas Christopher Wilson
454c4b2350 Merge tag '3.15.2' 2014-07-27 15:58:45 -04:00
Douglas Christopher Wilson
696e150f0a 3.15.2 2014-07-27 15:53:45 -04:00
Douglas Christopher Wilson
819265c7ae deps: send@0.7.2 2014-07-27 15:41:54 -04:00
Douglas Christopher Wilson
baf8b14a71 deps: depd@0.4.4
fixes #2262
2014-07-27 15:41:19 -04:00
Douglas Christopher Wilson
06e7685d65 deps: connect@2.24.2 2014-07-27 15:40:19 -04:00
Douglas Christopher Wilson
8858f20d93 4.7.1 2014-07-26 18:53:26 -04:00
Douglas Christopher Wilson
65f67e2ec0 deps: serve-static@~1.4.1 2014-07-26 18:08:02 -04:00
Douglas Christopher Wilson
8e63521f68 Merge tag '3.15.1' 2014-07-26 18:06:11 -04:00
Douglas Christopher Wilson
9f4968aaa3 3.15.1 2014-07-26 17:42:18 -04:00
Douglas Christopher Wilson
afea3c0ae8 deps: mocha@~1.21.0 2014-07-26 17:36:42 -04:00
Douglas Christopher Wilson
edaabe66cf deps: send@0.7.1 2014-07-26 17:36:40 -04:00
Douglas Christopher Wilson
f724730e1a Adjust wording on deprecation messages 2014-07-26 17:36:39 -04:00
Douglas Christopher Wilson
e49d0dc9e3 deps: depd@0.4.3
fixes #2262
2014-07-26 17:36:24 -04:00
Douglas Christopher Wilson
e7a3fbaf48 deps: connect@2.24.1 2014-07-26 17:30:13 -04:00
Douglas Christopher Wilson
1a9a837c45 4.7.0 2014-07-25 20:26:11 -04:00
Douglas Christopher Wilson
ab8d116f42 tests: bail on failed test for developer 2014-07-23 18:17:31 -04:00
Douglas Christopher Wilson
d0b6b3dfcf tests: change Route tests to use callback 2014-07-23 17:28:14 -04:00
Douglas Christopher Wilson
f34944c539 Merge tag '3.15.0' 2014-07-23 16:15:00 -04:00
Ashley Streb
11c74d72eb Fix req.protocol for proxy-direct connections
fixes #2252
2014-07-23 16:08:20 -04:00
Douglas Christopher Wilson
035685918c deps: serve-static@~1.4.0 2014-07-23 14:56:27 -04:00
Douglas Christopher Wilson
88cffadcaa deps: send@0.7.0 2014-07-23 14:54:33 -04:00
Douglas Christopher Wilson
3b7ca43170 deps: jade@~1.5.0 2014-07-23 14:01:44 -04:00
Douglas Christopher Wilson
7cd86a01da deps: update example dependencies 2014-07-23 13:04:07 -04:00
Douglas Christopher Wilson
dc054d190a deps: parseurl@~1.2.0 2014-07-23 12:55:56 -04:00
Douglas Christopher Wilson
9019424725 deps: depd@0.4.2 2014-07-23 12:53:29 -04:00
Douglas Christopher Wilson
928952e7f0 3.15.0 2014-07-23 00:53:19 -04:00
Ashley Streb
a28b7a85cf Fix req.protocol for proxy-direct connections
fixes #2252
2014-07-23 00:19:10 -04:00
Douglas Christopher Wilson
3fc8dc54ee docs: replace Gittip badge 2014-07-23 00:13:21 -04:00
Douglas Christopher Wilson
0d77305a1a Pass options from res.sendfile to send
fixes #2017
2014-07-23 00:08:15 -04:00
Douglas Christopher Wilson
323c185079 deps: send@0.7.0 2014-07-23 00:05:45 -04:00
Douglas Christopher Wilson
1d0da9036b deps: parseurl@~1.2.0 2014-07-22 23:53:50 -04:00
Douglas Christopher Wilson
683ba1cd75 deps: depd@0.4.2 2014-07-22 23:52:44 -04:00
Douglas Christopher Wilson
e4ff5281c9 deps: connect@2.24.0 2014-07-22 23:50:36 -04:00
Douglas Christopher Wilson
7414a1f463 deps: debug@1.0.4 2014-07-18 14:18:35 -04:00
Douglas Christopher Wilson
fd3b40533b Deprecate combined status, response signatures
closes #2227
2014-07-17 22:49:41 -04:00
Douglas Christopher Wilson
21bb2ef30e deps: finalhandler@0.1.0 2014-07-16 20:47:24 -04:00
Douglas Christopher Wilson
da4c639954 deps: debug@1.0.4 2014-07-16 20:44:17 -04:00
Douglas Christopher Wilson
d046208ca2 build: add Young Jae Sim as contributor 2014-07-16 13:31:35 -04:00
Young Jae Sim
04f383087f docs: add expressjs.kr(expressjs.com in Korean)
closes #2242
closes #2244
2014-07-16 13:31:34 -04:00
Vasilyev Dmitry
fd86ab8da2 build: fix up jsdoc return values
closes #2243
2014-07-16 09:05:57 -04:00
Douglas Christopher Wilson
e29fa25bb4 Add configurable query parser
closes #2215
2014-07-13 23:15:00 -04:00
Douglas Christopher Wilson
b43205ca98 perf: prevent multiple Buffer creation in res.send 2014-07-13 21:14:30 -04:00
Douglas Christopher Wilson
112dbb2ab4 4.6.1 2014-07-12 22:11:48 -04:00
Douglas Christopher Wilson
3e32721e24 Fix subapp.mountpath regression for app.use(subapp)
fixes #2233
2014-07-12 22:07:43 -04:00
Douglas Christopher Wilson
8ba3f39b33 4.6.0 2014-07-11 23:31:30 -04:00
Douglas Christopher Wilson
82bdbad5e0 deps: finalhandler@0.0.3 2014-07-11 23:00:09 -04:00
Douglas Christopher Wilson
d29cf4d5e3 deps: serve-static@~1.3.2 2014-07-11 22:49:49 -04:00
Douglas Christopher Wilson
1623936a25 deps: send@0.6.0 2014-07-11 22:48:42 -04:00
Douglas Christopher Wilson
fa6a40526a deps: update example dependencies 2014-07-11 22:46:25 -04:00
Douglas Christopher Wilson
c6e6203020 perf: fix arguments reassign deopt in some res methods 2014-07-11 17:13:07 -04:00
Douglas Christopher Wilson
997a558a73 Accept multiple callbacks to app.use()
fixes #2224
2014-07-11 17:13:06 -04:00
Douglas Christopher Wilson
a01326adac Catch errors in multiple req.param(name, fn) handlers 2014-07-11 17:12:57 -04:00
Douglas Christopher Wilson
76e8bfa1dc router: refactor location of try blocks 2014-07-11 16:38:03 -04:00
Douglas Christopher Wilson
8dc67af606 router: speed up standard app.use(fn) 2014-07-11 16:37:10 -04:00
Douglas Christopher Wilson
996d319263 router: fix optimization on router exit 2014-07-11 16:36:20 -04:00
Douglas Christopher Wilson
1c3bd36be6 Support non-string path in app.use(path, fn)
fixes #2207
2014-07-11 16:35:37 -04:00
Douglas Christopher Wilson
4ea6f21b02 Merge tag '3.14.0' 2014-07-11 16:28:37 -04:00
Douglas Christopher Wilson
916c53737d 3.14.0 2014-07-11 13:11:31 -04:00
Douglas Christopher Wilson
ec5b9f5c61 tests: add test for error handler in route
closes #2228
2014-07-11 09:35:20 -04:00
Douglas Christopher Wilson
b2382a7336 Remove unnecessary escaping in res.jsonp 2014-07-11 00:20:11 -04:00
Douglas Christopher Wilson
f684a64df7 Add explicit "Rosetta Flash JSONP abuse" protection 2014-07-11 00:15:55 -04:00
Douglas Christopher Wilson
5d03d0eac8 Deprecate res.redirect(url, status) 2014-07-10 23:21:16 -04:00
Yad Smood
544c6665f5 Fix res.send(status, num) to send num as json
fixes #2226
2014-07-10 23:19:57 -04:00
Douglas Christopher Wilson
cf8005e63f deps: basic-auth@1.0.0 2014-07-10 23:07:45 -04:00
Douglas Christopher Wilson
25ef8425d2 deps: methods@1.1.0 2014-07-10 23:06:15 -04:00
Douglas Christopher Wilson
577cc1d1a0 deps: parseurl@~1.1.3 2014-07-10 23:03:26 -04:00
Douglas Christopher Wilson
3c87a6aede deps: debug@1.0.3 2014-07-10 23:02:24 -04:00
Douglas Christopher Wilson
7c1f90bf16 deps: istanbul@0.3.0 2014-07-10 22:57:08 -04:00
Douglas Christopher Wilson
7bcf5f5085 deps: connect@2.23.0 2014-07-10 22:56:31 -04:00
Ryan Seys
4fe1073f10 docs: fix nodejs.org link
closes #2222
2014-07-09 00:50:33 -04:00
Douglas Christopher Wilson
968b00c3d7 tests: add missing FQDN router URL tests 2014-07-08 19:40:22 -04:00
Douglas Christopher Wilson
ef497fdae4 tests: add mounting to strict routing test 2014-07-07 23:25:05 -04:00
Douglas Christopher Wilson
bcdeee2df5 4.5.1 2014-07-06 19:48:18 -04:00
Douglas Christopher Wilson
23a49ff61e Fix routing regression when altering req.method 2014-07-06 19:44:43 -04:00
Douglas Christopher Wilson
b4b2efee0f tests: add more app.use tests 2014-07-05 11:32:44 -04:00
Douglas Christopher Wilson
92c45199bd 4.5.0 2014-07-04 21:03:07 -04:00
Fishrock123
bd6908516d docs: cleanup readme, update package description
closes #2210
2014-07-04 17:05:48 -04:00
Douglas Christopher Wilson
e6eeec3f03 Add req.hostname
closes #2179
2014-07-04 14:00:37 -04:00
Douglas Christopher Wilson
269dc5323f Invoke router.param() only when route matches
fixes #2206
2014-07-04 13:46:23 -04:00
Douglas Christopher Wilson
efbc3f95ee deps: accepts@~1.0.7 2014-07-04 12:51:44 -04:00
Douglas Christopher Wilson
99e839a274 deps: update example dependencies 2014-07-04 01:24:42 -04:00
Douglas Christopher Wilson
32dbda1460 deps: istanbul@0.2.14 2014-07-04 01:23:14 -04:00
Douglas Christopher Wilson
bcee730354 Merge tag '3.13.0' 2014-07-04 01:14:40 -04:00
Douglas Christopher Wilson
abe0ffa311 3.13.0 2014-07-04 00:40:01 -04:00
Douglas Christopher Wilson
b601d64203 tests: fix inconsistent test 2014-07-04 00:38:59 -04:00
Douglas Christopher Wilson
f381f2d9b6 add deprecation message to req.auth 2014-07-04 00:24:01 -04:00
Douglas Christopher Wilson
12507cfcd0 deps: connect@2.22.0 2014-07-03 23:09:44 -04:00
Douglas Christopher Wilson
185e327e29 add deprecation message to app.configure 2014-07-03 14:13:39 -04:00
Douglas Christopher Wilson
c468f5ff20 deps: send@0.5.0 2014-07-03 13:10:33 -04:00
Douglas Christopher Wilson
ce3d1fe07e Add headers option to res.sendfile 2014-07-03 12:53:49 -04:00
Douglas Christopher Wilson
3136e98dce deps: send@0.5.0 2014-07-03 11:18:35 -04:00
Douglas Christopher Wilson
d1b1dfd472 deps: serve-static@~1.3.0 2014-07-03 11:11:18 -04:00
Douglas Christopher Wilson
ca306eace1 deps: update example dependencies 2014-07-03 10:49:53 -04:00
Douglas Christopher Wilson
fd35351594 Add mergeParams option to Router
fixes #2153
fixes #2203
2014-07-03 01:13:09 -04:00
Douglas Christopher Wilson
8a15f83d72 Merge tag '3.12.1' 2014-06-26 20:31:51 -04:00
Douglas Christopher Wilson
d91bf81c31 Merge tag '4.4.5' 2014-06-26 20:29:54 -04:00
Douglas Christopher Wilson
fd27f1f4e1 4.4.5 2014-06-26 20:26:43 -04:00
Douglas Christopher Wilson
f0ad557987 deps: cookie-signature@1.0.4 2014-06-26 20:25:20 -04:00
Douglas Christopher Wilson
9bb47fba30 3.12.1 2014-06-26 18:35:54 -04:00
Douglas Christopher Wilson
78d489d730 deps: cookie-signature@1.0.4 2014-06-26 18:26:34 -04:00
Douglas Christopher Wilson
8ffb9f9477 deps: connect@2.21.1 2014-06-26 18:25:48 -04:00
Douglas Christopher Wilson
9cb147370e deps: istanbul@0.2.12 2014-06-26 18:21:26 -04:00
Douglas Christopher Wilson
81036aa639 deps: finalhandler@0.0.2 2014-06-24 21:19:03 -04:00
Douglas Christopher Wilson
e7caaf6757 deps: type-is@~1.3.2 2014-06-24 21:18:12 -04:00
Douglas Christopher Wilson
a525252690 deps: istanbul@0.2.11 2014-06-24 21:15:43 -04:00
Douglas Christopher Wilson
0ebfc6b7bf tests: add test for OPTIONS in app.all 2014-06-23 20:35:44 -04:00
Douglas Christopher Wilson
381b3b0f77 add deprecation message to res.vary() 2014-06-23 16:26:28 -04:00
Douglas Christopher Wilson
ba8a4c5a8d add deprecation message to res.send(body, status) 2014-06-23 16:26:00 -04:00
Douglas Christopher Wilson
2cccbc186e add deprecation message to non-plural req.accepts* 2014-06-23 16:17:44 -04:00
Carlos Souza
83bbf0902d examples: tweak cors example
The PUT method needs clearance from the server, unlike GET, HEAD
and POST, so this demonstrates a case where the method in the
response is important.

closes #2195
2014-06-23 13:37:30 -04:00
Douglas Christopher Wilson
10618ced22 Reduce try-catch de-op area in param matching 2014-06-22 12:02:41 -04:00
Douglas Christopher Wilson
7f26cfca91 Fix handling when route.all is only route 2014-06-22 12:02:40 -04:00
Douglas Christopher Wilson
b89a597029 Restore req.params after invoking Router
fixes #2163
2014-06-22 12:02:36 -04:00
Guy Ellis Monster
746044b6c2 Replace __defineGetter__ with Object.defineProperty
closes #2162
2014-06-22 12:02:33 -04:00
Douglas Christopher Wilson
bdfd288eec fix behavior when handling request without routes
fixes #2159
2014-06-22 12:02:32 -04:00
Douglas Christopher Wilson
7e01531e50 use finalhandler for final response handling 2014-06-22 12:01:55 -04:00
Douglas Christopher Wilson
3ffceff3ed Merge tag '3.12.0' 2014-06-22 11:42:15 -04:00
Douglas Christopher Wilson
75422c16bf 3.12.0 2014-06-21 21:57:02 -04:00
Douglas Christopher Wilson
e66667e465 Use media-typer to alter content-type charset 2014-06-21 21:09:12 -04:00
Douglas Christopher Wilson
7d6208e0af deps: connect@2.21.0 2014-06-21 21:09:10 -04:00
Douglas Christopher Wilson
f498b660da 4.4.4 2014-06-20 16:56:51 -04:00
刘星
e606d99dc8 Fix res.attachment Unicode filenames in Safari
closes #2188
2014-06-20 16:55:05 -04:00
Douglas Christopher Wilson
6aba1b4c49 deps: accepts@~1.0.5 2014-06-20 16:44:50 -04:00
Douglas Christopher Wilson
6ee9433f29 deps: update example dependencies 2014-06-20 16:43:04 -04:00
Douglas Christopher Wilson
ffe663aedf deps: buffer-crc32@0.2.3 2014-06-20 00:35:38 -04:00
Douglas Christopher Wilson
2a105df9f2 3.11.0 2014-06-19 23:36:00 -04:00
Douglas Christopher Wilson
9c731f1883 deprecate things with depd module 2014-06-19 23:34:58 -04:00
Douglas Christopher Wilson
5a4e9125de deps: connect@2.20.2 2014-06-19 23:24:07 -04:00
Douglas Christopher Wilson
9db1367c2d deps: buffer-crc32@0.2.3 2014-06-19 22:55:21 -04:00
Douglas Christopher Wilson
8258ce14f4 fix "trim prefix" debug message in express:router
closes #2177
2014-06-14 12:57:32 -04:00
Douglas Christopher Wilson
ac573cf830 4.4.3 2014-06-12 00:41:24 -04:00
Douglas Christopher Wilson
e799c0fb7b Merge tag '3.10.5' 2014-06-12 00:38:29 -04:00
Douglas Christopher Wilson
73c5533e66 3.10.5 2014-06-12 00:28:08 -04:00
Douglas Christopher Wilson
3b1f747f96 deps: send@0.4.3 2014-06-12 00:24:21 -04:00
Douglas Christopher Wilson
9e9827d236 deps: debug@1.0.2 2014-06-12 00:23:15 -04:00
Douglas Christopher Wilson
a76d508424 deps: connect@2.19.6 2014-06-12 00:20:30 -04:00
Douglas Christopher Wilson
c361a06bd4 deps: serve-static@1.2.3 2014-06-12 00:17:28 -04:00
Douglas Christopher Wilson
3428543bb8 deps: accepts@1.0.3 2014-06-12 00:16:05 -04:00
Douglas Christopher Wilson
9cdbc80522 deps: send@0.4.3 2014-06-12 00:14:36 -04:00
Douglas Christopher Wilson
6775658ed5 Fix persistence of req.params from app.params
fixes #2170
2014-06-11 18:15:09 -04:00
Douglas Christopher Wilson
7df7f7a575 deps: debug@1.0.2 2014-06-11 17:53:56 -04:00
63 changed files with 4204 additions and 1099 deletions

View File

@@ -1,7 +1,7 @@
## Website Issues
Issues for the expressjs.com website go here https://github.com/visionmedia/expressjs.com
Issues for the expressjs.com website go here https://github.com/strongloop/expressjs.com
## PRs and Code contributions

View File

@@ -1,3 +1,325 @@
4.9.3 / 2014-09-18
==================
* deps: proxy-addr@~1.0.2
- Fix a global leak when multiple subnets are trusted
- deps: ipaddr.js@0.1.3
4.9.2 / 2014-09-17
==================
* Fix regression for empty string `path` in `app.use`
* Fix `router.use` to accept array of middleware without path
* Improve error message for bad `app.use` arguments
4.9.1 / 2014-09-16
==================
* Fix `app.use` to accept array of middleware without path
* deps: depd@0.4.5
* deps: etag@~1.3.1
* deps: send@0.9.2
- deps: depd@0.4.5
- deps: etag@~1.3.1
- deps: range-parser@~1.0.2
* deps: serve-static@~1.6.2
- deps: send@0.9.2
4.9.0 / 2014-09-08
==================
* Add `res.sendStatus`
* Invoke callback for sendfile when client aborts
- Applies to `res.sendFile`, `res.sendfile`, and `res.download`
- `err` will be populated with request aborted error
* Support IP address host in `req.subdomains`
* Use `etag` to generate `ETag` headers
* deps: accepts@~1.1.0
- update `mime-types`
* deps: cookie-signature@1.0.5
* deps: debug@~2.0.0
* deps: finalhandler@0.2.0
- Set `X-Content-Type-Options: nosniff` header
- deps: debug@~2.0.0
* deps: fresh@0.2.4
* deps: media-typer@0.3.0
- Throw error when parameter format invalid on parse
* deps: qs@2.2.3
- Fix issue where first empty value in array is discarded
* deps: range-parser@~1.0.2
* deps: send@0.9.1
- Add `lastModified` option
- Use `etag` to generate `ETag` header
- deps: debug@~2.0.0
- deps: fresh@0.2.4
* deps: serve-static@~1.6.1
- Add `lastModified` option
- deps: send@0.9.1
* deps: type-is@~1.5.1
- fix `hasbody` to be true for `content-length: 0`
- deps: media-typer@0.3.0
- deps: mime-types@~2.0.1
* deps: vary@~1.0.0
- Accept valid `Vary` header string as `field`
4.8.8 / 2014-09-04
==================
* deps: send@0.8.5
- Fix a path traversal issue when using `root`
- Fix malicious path detection for empty string path
* deps: serve-static@~1.5.4
- deps: send@0.8.5
4.8.7 / 2014-08-29
==================
* deps: qs@2.2.2
- Remove unnecessary cloning
4.8.6 / 2014-08-27
==================
* deps: qs@2.2.0
- Array parsing fix
- Performance improvements
4.8.5 / 2014-08-18
==================
* deps: send@0.8.3
- deps: destroy@1.0.3
- deps: on-finished@2.1.0
* deps: serve-static@~1.5.3
- deps: send@0.8.3
4.8.4 / 2014-08-14
==================
* deps: qs@1.2.2
* deps: send@0.8.2
- Work around `fd` leak in Node.js 0.10 for `fs.ReadStream`
* deps: serve-static@~1.5.2
- deps: send@0.8.2
4.8.3 / 2014-08-10
==================
* deps: parseurl@~1.3.0
* deps: qs@1.2.1
* deps: serve-static@~1.5.1
- Fix parsing of weird `req.originalUrl` values
- deps: parseurl@~1.3.0
- deps: utils-merge@1.0.0
4.8.2 / 2014-08-07
==================
* deps: qs@1.2.0
- Fix parsing array of objects
4.8.1 / 2014-08-06
==================
* fix incorrect deprecation warnings on `res.download`
* deps: qs@1.1.0
- Accept urlencoded square brackets
- Accept empty values in implicit array notation
4.8.0 / 2014-08-05
==================
* add `res.sendFile`
- accepts a file system path instead of a URL
- requires an absolute path or `root` option specified
* deprecate `res.sendfile` -- use `res.sendFile` instead
* support mounted app as any argument to `app.use()`
* deps: qs@1.0.2
- Complete rewrite
- Limits array length to 20
- Limits object depth to 5
- Limits parameters to 1,000
* deps: send@0.8.1
- Add `extensions` option
* deps: serve-static@~1.5.0
- Add `extensions` option
- deps: send@0.8.1
4.7.4 / 2014-08-04
==================
* fix `res.sendfile` regression for serving directory index files
* deps: send@0.7.4
- Fix incorrect 403 on Windows and Node.js 0.11
- Fix serving index files without root dir
* deps: serve-static@~1.4.4
- deps: send@0.7.4
4.7.3 / 2014-08-04
==================
* deps: send@0.7.3
- Fix incorrect 403 on Windows and Node.js 0.11
* deps: serve-static@~1.4.3
- Fix incorrect 403 on Windows and Node.js 0.11
- deps: send@0.7.3
4.7.2 / 2014-07-27
==================
* deps: depd@0.4.4
- Work-around v8 generating empty stack traces
* deps: send@0.7.2
- deps: depd@0.4.4
* deps: serve-static@~1.4.2
4.7.1 / 2014-07-26
==================
* deps: depd@0.4.3
- Fix exception when global `Error.stackTraceLimit` is too low
* deps: send@0.7.1
- deps: depd@0.4.3
* deps: serve-static@~1.4.1
4.7.0 / 2014-07-25
==================
* fix `req.protocol` for proxy-direct connections
* configurable query parser with `app.set('query parser', parser)`
- `app.set('query parser', 'extended')` parse with "qs" module
- `app.set('query parser', 'simple')` parse with "querystring" core module
- `app.set('query parser', false)` disable query string parsing
- `app.set('query parser', true)` enable simple parsing
* deprecate `res.json(status, obj)` -- use `res.status(status).json(obj)` instead
* deprecate `res.jsonp(status, obj)` -- use `res.status(status).jsonp(obj)` instead
* deprecate `res.send(status, body)` -- use `res.status(status).send(body)` instead
* deps: debug@1.0.4
* deps: depd@0.4.2
- Add `TRACE_DEPRECATION` environment variable
- Remove non-standard grey color from color output
- Support `--no-deprecation` argument
- Support `--trace-deprecation` argument
* deps: finalhandler@0.1.0
- Respond after request fully read
- deps: debug@1.0.4
* deps: parseurl@~1.2.0
- Cache URLs based on original value
- Remove no-longer-needed URL mis-parse work-around
- Simplify the "fast-path" `RegExp`
* deps: send@0.7.0
- Add `dotfiles` option
- Cap `maxAge` value to 1 year
- deps: debug@1.0.4
- deps: depd@0.4.2
* deps: serve-static@~1.4.0
- deps: parseurl@~1.2.0
- deps: send@0.7.0
* perf: prevent multiple `Buffer` creation in `res.send`
4.6.1 / 2014-07-12
==================
* fix `subapp.mountpath` regression for `app.use(subapp)`
4.6.0 / 2014-07-11
==================
* accept multiple callbacks to `app.use()`
* add explicit "Rosetta Flash JSONP abuse" protection
- previous versions are not vulnerable; this is just explicit protection
* catch errors in multiple `req.param(name, fn)` handlers
* deprecate `res.redirect(url, status)` -- use `res.redirect(status, url)` instead
* fix `res.send(status, num)` to send `num` as json (not error)
* remove unnecessary escaping when `res.jsonp` returns JSON response
* support non-string `path` in `app.use(path, fn)`
- supports array of paths
- supports `RegExp`
* router: fix optimization on router exit
* router: refactor location of `try` blocks
* router: speed up standard `app.use(fn)`
* deps: debug@1.0.3
- Add support for multiple wildcards in namespaces
* deps: finalhandler@0.0.3
- deps: debug@1.0.3
* deps: methods@1.1.0
- add `CONNECT`
* deps: parseurl@~1.1.3
- faster parsing of href-only URLs
* deps: path-to-regexp@0.1.3
* deps: send@0.6.0
- deps: debug@1.0.3
* deps: serve-static@~1.3.2
- deps: parseurl@~1.1.3
- deps: send@0.6.0
* perf: fix arguments reassign deopt in some `res` methods
4.5.1 / 2014-07-06
==================
* fix routing regression when altering `req.method`
4.5.0 / 2014-07-04
==================
* add deprecation message to non-plural `req.accepts*`
* add deprecation message to `res.send(body, status)`
* add deprecation message to `res.vary()`
* add `headers` option to `res.sendfile`
- use to set headers on successful file transfer
* add `mergeParams` option to `Router`
- merges `req.params` from parent routes
* add `req.hostname` -- correct name for what `req.host` returns
* deprecate things with `depd` module
* deprecate `req.host` -- use `req.hostname` instead
* fix behavior when handling request without routes
* fix handling when `route.all` is only route
* invoke `router.param()` only when route matches
* restore `req.params` after invoking router
* use `finalhandler` for final response handling
* use `media-typer` to alter content-type charset
* deps: accepts@~1.0.7
* deps: send@0.5.0
- Accept string for `maxage` (converted by `ms`)
- Include link in default redirect response
* deps: serve-static@~1.3.0
- Accept string for `maxAge` (converted by `ms`)
- Add `setHeaders` option
- Include HTML link in redirect response
- deps: send@0.5.0
* deps: type-is@~1.3.2
4.4.5 / 2014-06-26
==================
* deps: cookie-signature@1.0.4
- fix for timing attacks
4.4.4 / 2014-06-20
==================
* fix `res.attachment` Unicode filenames in Safari
* fix "trim prefix" debug message in `express:router`
* deps: accepts@~1.0.5
* deps: buffer-crc32@0.2.3
4.4.3 / 2014-06-11
==================
* fix persistence of modified `req.params[name]` from `app.param()`
* deps: accepts@1.0.3
- deps: negotiator@0.4.6
* deps: debug@1.0.2
* deps: send@0.4.3
- Do not throw un-catchable error on file open race condition
- Use `escape-html` for HTML escaping
- deps: debug@1.0.2
- deps: finished@1.2.2
- deps: fresh@0.2.2
* deps: serve-static@1.2.3
- Do not throw un-catchable error on file open race condition
- deps: send@0.4.3
4.4.2 / 2014-06-09
==================
@@ -171,6 +493,355 @@
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route
- Router & Route - public API
3.17.3 / 2014-09-18
===================
* deps: proxy-addr@~1.0.2
- Fix a global leak when multiple subnets are trusted
- deps: ipaddr.js@0.1.3
3.17.2 / 2014-09-15
===================
* Use `crc` instead of `buffer-crc32` for speed
* deps: connect@2.26.1
- deps: body-parser@~1.8.2
- deps: depd@0.4.5
- deps: express-session@~1.8.2
- deps: morgan@~1.3.1
- deps: serve-favicon@~2.1.3
- deps: serve-static@~1.6.2
* deps: depd@0.4.5
* deps: send@0.9.2
- deps: depd@0.4.5
- deps: etag@~1.3.1
- deps: range-parser@~1.0.2
3.17.1 / 2014-09-08
===================
* Fix error in `req.subdomains` on empty host
3.17.0 / 2014-09-08
===================
* Support `X-Forwarded-Host` in `req.subdomains`
* Support IP address host in `req.subdomains`
* deps: connect@2.26.0
- deps: body-parser@~1.8.1
- deps: compression@~1.1.0
- deps: connect-timeout@~1.3.0
- deps: cookie-parser@~1.3.3
- deps: cookie-signature@1.0.5
- deps: csurf@~1.6.1
- deps: debug@~2.0.0
- deps: errorhandler@~1.2.0
- deps: express-session@~1.8.1
- deps: finalhandler@0.2.0
- deps: fresh@0.2.4
- deps: media-typer@0.3.0
- deps: method-override@~2.2.0
- deps: morgan@~1.3.0
- deps: qs@2.2.3
- deps: serve-favicon@~2.1.3
- deps: serve-index@~1.2.1
- deps: serve-static@~1.6.1
- deps: type-is@~1.5.1
- deps: vhost@~3.0.0
* deps: cookie-signature@1.0.5
* deps: debug@~2.0.0
* deps: fresh@0.2.4
* deps: media-typer@0.3.0
- Throw error when parameter format invalid on parse
* deps: range-parser@~1.0.2
* deps: send@0.9.1
- Add `lastModified` option
- Use `etag` to generate `ETag` header
- deps: debug@~2.0.0
- deps: fresh@0.2.4
* deps: vary@~1.0.0
- Accept valid `Vary` header string as `field`
3.16.10 / 2014-09-04
====================
* deps: connect@2.25.10
- deps: serve-static@~1.5.4
* deps: send@0.8.5
- Fix a path traversal issue when using `root`
- Fix malicious path detection for empty string path
3.16.9 / 2014-08-29
===================
* deps: connect@2.25.9
- deps: body-parser@~1.6.7
- deps: qs@2.2.2
3.16.8 / 2014-08-27
===================
* deps: connect@2.25.8
- deps: body-parser@~1.6.6
- deps: csurf@~1.4.1
- deps: qs@2.2.0
3.16.7 / 2014-08-18
===================
* deps: connect@2.25.7
- deps: body-parser@~1.6.5
- deps: express-session@~1.7.6
- deps: morgan@~1.2.3
- deps: serve-static@~1.5.3
* deps: send@0.8.3
- deps: destroy@1.0.3
- deps: on-finished@2.1.0
3.16.6 / 2014-08-14
===================
* deps: connect@2.25.6
- deps: body-parser@~1.6.4
- deps: qs@1.2.2
- deps: serve-static@~1.5.2
* deps: send@0.8.2
- Work around `fd` leak in Node.js 0.10 for `fs.ReadStream`
3.16.5 / 2014-08-11
===================
* deps: connect@2.25.5
- Fix backwards compatibility in `logger`
3.16.4 / 2014-08-10
===================
* Fix original URL parsing in `res.location`
* deps: connect@2.25.4
- Fix `query` middleware breaking with argument
- deps: body-parser@~1.6.3
- deps: compression@~1.0.11
- deps: connect-timeout@~1.2.2
- deps: express-session@~1.7.5
- deps: method-override@~2.1.3
- deps: on-headers@~1.0.0
- deps: parseurl@~1.3.0
- deps: qs@1.2.1
- deps: response-time@~2.0.1
- deps: serve-index@~1.1.6
- deps: serve-static@~1.5.1
* deps: parseurl@~1.3.0
3.16.3 / 2014-08-07
===================
* deps: connect@2.25.3
- deps: multiparty@3.3.2
3.16.2 / 2014-08-07
===================
* deps: connect@2.25.2
- deps: body-parser@~1.6.2
- deps: qs@1.2.0
3.16.1 / 2014-08-06
===================
* deps: connect@2.25.1
- deps: body-parser@~1.6.1
- deps: qs@1.1.0
3.16.0 / 2014-08-05
===================
* deps: connect@2.25.0
- deps: body-parser@~1.6.0
- deps: compression@~1.0.10
- deps: csurf@~1.4.0
- deps: express-session@~1.7.4
- deps: qs@1.0.2
- deps: serve-static@~1.5.0
* deps: send@0.8.1
- Add `extensions` option
3.15.3 / 2014-08-04
===================
* fix `res.sendfile` regression for serving directory index files
* deps: connect@2.24.3
- deps: serve-index@~1.1.5
- deps: serve-static@~1.4.4
* deps: send@0.7.4
- Fix incorrect 403 on Windows and Node.js 0.11
- Fix serving index files without root dir
3.15.2 / 2014-07-27
===================
* deps: connect@2.24.2
- deps: body-parser@~1.5.2
- deps: depd@0.4.4
- deps: express-session@~1.7.2
- deps: morgan@~1.2.2
- deps: serve-static@~1.4.2
* deps: depd@0.4.4
- Work-around v8 generating empty stack traces
* deps: send@0.7.2
- deps: depd@0.4.4
3.15.1 / 2014-07-26
===================
* deps: connect@2.24.1
- deps: body-parser@~1.5.1
- deps: depd@0.4.3
- deps: express-session@~1.7.1
- deps: morgan@~1.2.1
- deps: serve-index@~1.1.4
- deps: serve-static@~1.4.1
* deps: depd@0.4.3
- Fix exception when global `Error.stackTraceLimit` is too low
* deps: send@0.7.1
- deps: depd@0.4.3
3.15.0 / 2014-07-22
===================
* Fix `req.protocol` for proxy-direct connections
* Pass options from `res.sendfile` to `send`
* deps: connect@2.24.0
- deps: body-parser@~1.5.0
- deps: compression@~1.0.9
- deps: connect-timeout@~1.2.1
- deps: debug@1.0.4
- deps: depd@0.4.2
- deps: express-session@~1.7.0
- deps: finalhandler@0.1.0
- deps: method-override@~2.1.2
- deps: morgan@~1.2.0
- deps: multiparty@3.3.1
- deps: parseurl@~1.2.0
- deps: serve-static@~1.4.0
* deps: debug@1.0.4
* deps: depd@0.4.2
- Add `TRACE_DEPRECATION` environment variable
- Remove non-standard grey color from color output
- Support `--no-deprecation` argument
- Support `--trace-deprecation` argument
* deps: parseurl@~1.2.0
- Cache URLs based on original value
- Remove no-longer-needed URL mis-parse work-around
- Simplify the "fast-path" `RegExp`
* deps: send@0.7.0
- Add `dotfiles` option
- Cap `maxAge` value to 1 year
- deps: debug@1.0.4
- deps: depd@0.4.2
3.14.0 / 2014-07-11
===================
* add explicit "Rosetta Flash JSONP abuse" protection
- previous versions are not vulnerable; this is just explicit protection
* deprecate `res.redirect(url, status)` -- use `res.redirect(status, url)` instead
* fix `res.send(status, num)` to send `num` as json (not error)
* remove unnecessary escaping when `res.jsonp` returns JSON response
* deps: basic-auth@1.0.0
- support empty password
- support empty username
* deps: connect@2.23.0
- deps: debug@1.0.3
- deps: express-session@~1.6.4
- deps: method-override@~2.1.0
- deps: parseurl@~1.1.3
- deps: serve-static@~1.3.1
* deps: debug@1.0.3
- Add support for multiple wildcards in namespaces
* deps: methods@1.1.0
- add `CONNECT`
* deps: parseurl@~1.1.3
- faster parsing of href-only URLs
3.13.0 / 2014-07-03
===================
* add deprecation message to `app.configure`
* add deprecation message to `req.auth`
* use `basic-auth` to parse `Authorization` header
* deps: connect@2.22.0
- deps: csurf@~1.3.0
- deps: express-session@~1.6.1
- deps: multiparty@3.3.0
- deps: serve-static@~1.3.0
* deps: send@0.5.0
- Accept string for `maxage` (converted by `ms`)
- Include link in default redirect response
3.12.1 / 2014-06-26
===================
* deps: connect@2.21.1
- deps: cookie-parser@1.3.2
- deps: cookie-signature@1.0.4
- deps: express-session@~1.5.2
- deps: type-is@~1.3.2
* deps: cookie-signature@1.0.4
- fix for timing attacks
3.12.0 / 2014-06-21
===================
* use `media-typer` to alter content-type charset
* deps: connect@2.21.0
- deprecate `connect(middleware)` -- use `app.use(middleware)` instead
- deprecate `connect.createServer()` -- use `connect()` instead
- fix `res.setHeader()` patch to work with with get -> append -> set pattern
- deps: compression@~1.0.8
- deps: errorhandler@~1.1.1
- deps: express-session@~1.5.0
- deps: serve-index@~1.1.3
3.11.0 / 2014-06-19
===================
* deprecate things with `depd` module
* deps: buffer-crc32@0.2.3
* deps: connect@2.20.2
- deprecate `verify` option to `json` -- use `body-parser` npm module instead
- deprecate `verify` option to `urlencoded` -- use `body-parser` npm module instead
- deprecate things with `depd` module
- use `finalhandler` for final response handling
- use `media-typer` to parse `content-type` for charset
- deps: body-parser@1.4.3
- deps: connect-timeout@1.1.1
- deps: cookie-parser@1.3.1
- deps: csurf@1.2.2
- deps: errorhandler@1.1.0
- deps: express-session@1.4.0
- deps: multiparty@3.2.9
- deps: serve-index@1.1.2
- deps: type-is@1.3.1
- deps: vhost@2.0.0
3.10.5 / 2014-06-11
===================
* deps: connect@2.19.6
- deps: body-parser@1.3.1
- deps: compression@1.0.7
- deps: debug@1.0.2
- deps: serve-index@1.1.1
- deps: serve-static@1.2.3
* deps: debug@1.0.2
* deps: send@0.4.3
- Do not throw un-catchable error on file open race condition
- Use `escape-html` for HTML escaping
- deps: debug@1.0.2
- deps: finished@1.2.2
- deps: fresh@0.2.2
3.10.4 / 2014-06-09
===================
@@ -213,7 +884,7 @@
===================
* deps: connect@2.19.1
- deprecate `methodOverride()` -- use `method-override` module directly
- deprecate `methodOverride()` -- use `method-override` npm module instead
- deps: body-parser@1.3.0
- deps: method-override@2.0.1
- deps: multiparty@3.2.8
@@ -1377,7 +2048,7 @@ Shaw]
* Added "encoding" option to Request#render(). Closes #299
* Added "dump exceptions" setting, which is enabled by default.
* Added simple ejs template engine support
* Added error reponse support for text/plain, application/json. Closes #297
* Added error response support for text/plain, application/json. Closes #297
* Added callback function param to Request#error()
* Added Request#sendHead()
* Added Request#stream()
@@ -1591,7 +2262,7 @@ Shaw]
* Updated sample chat app to show messages on load
* Updated libxmljs parseString -> parseHtmlString
* Fixed `make init` to work with older versions of git
* Fixed specs can now run independant specs for those who cant build deps. Closes #127
* Fixed specs can now run independent specs for those who cant build deps. Closes #127
* Fixed issues introduced by the node url module changes. Closes 126.
* Fixed two assertions failing due to Collection#keys() returning strings
* Fixed faulty Collection#toArray() spec due to keys() returning strings

113
Readme.md
View File

@@ -1,48 +1,58 @@
[![express logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](http://expressjs.com/)
[![Express Logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](http://expressjs.com/)
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
[![NPM version](https://badge.fury.io/js/express.svg)](http://badge.fury.io/js/express)
[![Build Status](https://travis-ci.org/visionmedia/express.svg?branch=master)](https://travis-ci.org/visionmedia/express)
[![Coverage Status](https://img.shields.io/coveralls/visionmedia/express.svg)](https://coveralls.io/r/visionmedia/express)
[![Gittip](http://img.shields.io/gittip/visionmedia.svg)](https://www.gittip.com/visionmedia/)
[![NPM Version](https://img.shields.io/npm/v/express.svg?style=flat)](https://www.npmjs.org/package/express)
[![Build Status](https://img.shields.io/travis/strongloop/express.svg?style=flat)](https://travis-ci.org/strongloop/express)
[![Coverage Status](https://img.shields.io/coveralls/strongloop/express.svg?style=flat)](https://coveralls.io/r/strongloop/express)
[![Gittip](https://img.shields.io/gittip/dougwilson.svg?style=flat)](https://www.gittip.com/dougwilson/)
```js
var express = require('express');
var app = express();
var express = require('express')
var app = express()
app.get('/', function(req, res){
res.send('Hello World');
});
app.get('/', function (req, res) {
res.send('Hello World')
})
app.listen(3000);
app.listen(3000)
```
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/visionmedia/express/wiki/New-features-in-4.x).
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/strongloop/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/strongloop/express/wiki/New-features-in-4.x).
## Installation
### Installation
$ npm install express
```bash
$ npm install express
```
## Quick Start
The quickest way to get started with express is to utilize the executable [`express(1)`](http://github.com/expressjs/generator) to generate an application as shown below:
Install the executable. The executable's major version will match Express's:
$ npm install -g express-generator@3
The quickest way to get started with express is to utilize the executable [`express(1)`](https://github.com/expressjs/generator) to generate an application as shown below:
Create the app:
Install the executable. The executable's major version will match Express's:
$ express /tmp/foo && cd /tmp/foo
```bash
$ npm install -g express-generator@4
```
Install dependencies:
Create the app:
$ npm install
```bash
$ express /tmp/foo && cd /tmp/foo
```
Start the server:
Install dependencies:
$ npm start
```bash
$ npm install
```
Start the server:
```bash
$ npm start
```
## Features
@@ -61,53 +71,58 @@ app.listen(3000);
HTTP APIs.
Express does not force you to use any specific ORM or template engine. With support for over
14 template engines via [Consolidate.js](http://github.com/visionmedia/consolidate.js),
14 template engines via [Consolidate.js](https://github.com/visionmedia/consolidate.js),
you can quickly craft your perfect framework.
## More Information
* [Website and Documentation](http://expressjs.com/) stored at [visionmedia/expressjs.com](https://github.com/visionmedia/expressjs.com)
* Join #express on freenode
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) and [defunctzombie](https://twitter.com/defunctzombie) on twitter for updates
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
* [Website and Documentation](http://expressjs.com/) - [[website repo](https://github.com/strongloop/expressjs.com)]
* [Github Organization](https://github.com/expressjs) for Official Middleware & Modules
* [#express](https://webchat.freenode.net/?channels=express) on freenode IRC
* Visit the [Wiki](https://github.com/strongloop/express/wiki)
* [Google Group](https://groups.google.com/group/express-js) for discussion
* [Русскоязычная документация](http://jsman.ru/express/)
* [한국어 문서](http://expressjs.kr) - [[website repo](https://github.com/Hanul/expressjs.kr)]
* Run express examples [online](https://runnable.com/express)
## Viewing Examples
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
$ git clone git://github.com/visionmedia/express.git --depth 1
$ cd express
$ npm install
```bash
$ git clone git://github.com/strongloop/express.git --depth 1
$ cd express
$ npm install
```
Then run whichever tests you want:
Then run whichever example you want:
$ node examples/content-negotiation
You can also view live examples here:
You can also view live examples here:
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
## Running Tests
To run the test suite, first invoke the following command within the repo, installing the development dependencies:
To run the test suite, first invoke the following command within the repo, installing the development dependencies:
$ npm install
```bash
$ npm install
```
Then run the tests:
Then run the tests:
```sh
```bash
$ npm test
```
## Contributors
Author: [TJ Holowaychuk](http://github.com/visionmedia)
Lead Maintainer: [Roman Shtylman](https://github.com/defunctzombie)
Contributors: https://github.com/visionmedia/express/graphs/contributors
### Contributors
## License
* Author: [TJ Holowaychuk](https://github.com/visionmedia)
* Lead Maintainer: [Douglas Christopher Wilson](https://github.com/dougwilson)
* [All Contributors](https://github.com/strongloop/express/graphs/contributors)
MIT
### License
[MIT](LICENSE)

View File

@@ -5,7 +5,6 @@
var express = require('../..');
var hash = require('./pass').hash;
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var app = module.exports = express();
@@ -17,9 +16,12 @@ app.set('views', __dirname + '/views');
// middleware
app.use(bodyParser());
app.use(cookieParser('shhhh, very secret'));
app.use(session());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'shhhh, very secret'
}));
// Session-persisted message middleware

View File

@@ -3,15 +3,11 @@
*/
var express = require('../../');
var cookie-parser = require('cookie-parser');
var app = module.exports = express();
// pass a secret to cookieParser() for signed cookies
app.use(cookieParser('manny is cool'));
// add req.session cookie support
app.use(cookieSession());
app.use(cookieSession({ secret: 'manny is cool' }));
// do something with the session
app.use(count);

View File

@@ -17,8 +17,8 @@ if ('test' != process.env.NODE_ENV) app.use(logger(':method :url'));
// for signing the cookies.
app.use(cookieParser('my secret here'));
// parses json, x-www-form-urlencoded, and multipart/form-data
app.use(bodyParser());
// parses x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
app.get('/', function(req, res){
if (req.cookies.remember) {

View File

@@ -15,7 +15,7 @@ app.use(express.static(__dirname + '/public'));
// api middleware
api.use(logger('dev'));
api.use(bodyParser());
api.use(bodyParser.json());
/**
* CORS support.
@@ -25,7 +25,7 @@ api.all('*', function(req, res, next){
if (!req.get('Origin')) return next();
// use "*" here to accept any origin
res.set('Access-Control-Allow-Origin', 'http://localhost:3000');
res.set('Access-Control-Allow-Methods', 'GET, POST');
res.set('Access-Control-Allow-Methods', 'PUT');
res.set('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
// res.set('Access-Control-Allow-Max-Age', 3600);
if ('OPTIONS' == req.method) return res.send(200);
@@ -33,12 +33,12 @@ api.all('*', function(req, res, next){
});
/**
* POST a user.
* PUT an existing user.
*/
api.post('/user', function(req, res){
api.put('/user/:id', function(req, res){
console.log(req.body);
res.send(201);
res.send(204);
});
app.listen(3000);

View File

@@ -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>

View File

@@ -10,6 +10,7 @@ app.get('/', function(req, res){
+ '<li>Download <a href="/files/amazing.txt">amazing.txt</a>.</li>'
+ '<li>Download <a href="/files/utf-8 한中日.txt">utf-8 한中日.txt</a>.</li>'
+ '<li>Download <a href="/files/missing.txt">missing.txt</a>.</li>'
+ '<li>Download <a href="/files/CCTV大赛上海分赛区.txt">CCTV大赛上海分赛区.txt</a>.</li>'
+ '</ul>');
});

View File

@@ -0,0 +1,2 @@
Only for test.
The file name is faked.

View File

@@ -20,7 +20,8 @@ function error(err, req, res, next) {
if (!test) console.error(err.stack);
// respond with 500 "Internal Server Error".
res.send(500);
res.status(500);
res.send('Internal Server Error');
}
app.get('/', function(req, res){

View File

@@ -5,7 +5,6 @@
var express = require('../..');
var logger = require('morgan');
var session = require('express-session');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
@@ -38,11 +37,14 @@ if (!module.parent) app.use(logger('dev'));
app.use(express.static(__dirname + '/public'));
// session support
app.use(cookieParser('some secret here'));
app.use(session());
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'some secret here'
}));
// parse request bodies (req.body)
app.use(bodyParser());
app.use(bodyParser.urlencoded({ extended: true }));
// allow overriding methods in query (?_method=put)
app.use(methodOverride('_method'));

View File

@@ -7,18 +7,26 @@ var app = express();
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var site = require('./site');
var post = require('./post');
var user = require('./user');
module.exports = app;
// Config
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');
app.use(logger('dev'));
/* istanbul ignore next */
if (!module.parent) {
app.use(express.logger('dev'));
}
app.use(methodOverride('_method'));
app.use(cookieParser());
app.use(bodyParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(__dirname + '/public'));
// General
@@ -27,7 +35,7 @@ app.get('/', site.index);
// User
app.all('/users', user.list);
app.get('/users', user.list);
app.all('/user/:id/:op?', user.load);
app.get('/user/:id', user.view);
app.get('/user/:id/view', user.view);

View File

@@ -15,7 +15,9 @@ exports.load = function(req, res, next){
if (req.user) {
next();
} else {
next(new Error('cannot find user ' + id));
var err = new Error('cannot find user ' + id);
err.status = 404;
next(err);
}
};
@@ -40,4 +42,4 @@ exports.update = function(req, res){
req.user.name = user.name;
req.user.email = user.email;
res.redirect('back');
};
};

View File

@@ -47,14 +47,14 @@ app.get('/search/:query?', function(req, res){
});
/**
* GET client javascript. Here we use sendfile()
* GET client javascript. Here we use sendFile()
* because serving __dirname with the static() middleware
* would also mean serving our server "index.js" and the "search.jade"
* template.
*/
app.get('/client.js', function(req, res){
res.sendfile(__dirname + '/client.js');
res.sendFile(__dirname + '/client.js');
});
/* istanbul ignore next */

View File

@@ -3,18 +3,16 @@
// $ redis-server
var express = require('../..');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var app = express();
// Required by session() middleware
// pass the secret for signed cookies
// (required by session())
app.use(cookieParser('keyboard cat'));
// Populates req.session
app.use(session());
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'keyboard cat'
}));
app.get('/', function(req, res){
var body = '';

View File

@@ -4,7 +4,6 @@
var express = require('../..');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var session = require('express-session');
// pass the express to the connect redis module
@@ -15,13 +14,13 @@ var app = express();
app.use(logger('dev'));
// Required by session() middleware
// pass the secret for signed cookies
// (required by session())
app.use(cookieParser('keyboard cat'));
// Populates req.session
app.use(session({ store: new RedisStore }));
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'keyboard cat',
store: new RedisStore
}));
app.get('/', function(req, res){
var body = '';

View File

@@ -34,13 +34,13 @@ function GithubView(name, options){
GithubView.prototype.render = function(options, fn){
var self = this;
var opts = {
host: 'rawgithub.com',
port: 80,
host: 'raw.githubusercontent.com',
port: 443,
path: this.path,
method: 'GET'
};
http.request(opts, function(res) {
https.request(opts, function(res) {
var buf = '';
res.setEncoding('utf8');
res.on('data', function(str){ buf += str });

View File

@@ -23,7 +23,7 @@ app.engine('md', function(str, options, fn){
});
// pointing to a particular github repo to load files from it
app.set('views', 'visionmedia/express');
app.set('views', 'strongloop/express');
// register a new view constructor
app.set('view', GithubView);
@@ -36,7 +36,7 @@ app.get('/', function(req, res){
});
app.get('/Readme.md', function(req, res){
// rendering a view from https://github.com/visionmedia/express/blob/master/Readme.md
// rendering a view from https://github.com/strongloop/express/blob/master/Readme.md
res.render('Readme.md');
});

View File

@@ -28,7 +28,7 @@ function error(status, msg) {
app.use('/api', function(req, res, next){
var key = req.query['api-key'];
// key isnt present
// key isn't present
if (!key) return next(error(400, 'api key required'));
// key is invalid
@@ -49,7 +49,7 @@ var apiKeys = ['foo', 'bar', 'baz'];
// these two objects will serve as our faux database
var repos = [
{ name: 'express', url: 'http://github.com/visionmedia/express' }
{ name: 'express', url: 'http://github.com/strongloop/express' }
, { name: 'stylus', url: 'http://github.com/learnboost/stylus' }
, { name: 'cluster', url: 'http://github.com/learnboost/cluster' }
];
@@ -93,14 +93,16 @@ app.get('/api/user/:name/repos', function(req, res, next){
app.use(function(err, req, res, next){
// whatever you want here, feel free to populate
// properties on `err` to treat it differently in here.
res.send(err.status || 500, { error: err.message });
res.status(err.status || 500);
res.send({ error: err.message });
});
// our custom JSON 404 middleware. Since it's placed last
// it will be the last middleware called, if all others
// invoke next() and do not respond.
app.use(function(req, res){
res.send(404, { error: "Lame, can't find that" });
res.status(404);
res.send({ error: "Lame, can't find that" });
});
/* istanbul ignore next */

View File

@@ -2,8 +2,9 @@
* Module dependencies.
*/
var finalhandler = require('finalhandler');
var flatten = require('./utils').flatten;
var mixin = require('utils-merge');
var escapeHtml = require('escape-html');
var Router = require('./router');
var methods = require('methods');
var middleware = require('./middleware/init');
@@ -12,9 +13,11 @@ var debug = require('debug')('express:application');
var View = require('./view');
var http = require('http');
var compileETag = require('./utils').compileETag;
var compileQueryParser = require('./utils').compileQueryParser;
var compileTrust = require('./utils').compileTrust;
var deprecate = require('./utils').deprecate;
var deprecate = require('depd')('express');
var resolve = require('path').resolve;
var slice = Array.prototype.slice;
/**
* Application prototype.
@@ -51,6 +54,7 @@ app.defaultConfiguration = function(){
this.set('etag', 'weak');
var env = process.env.NODE_ENV || 'development';
this.set('env', env);
this.set('query parser', 'extended');
this.set('subdomain offset', 2);
this.set('trust proxy', false);
@@ -104,7 +108,7 @@ app.lazyrouter = function() {
strict: this.enabled('strict routing')
});
this._router.use(query());
this._router.use(query(this.get('query parser fn')));
this._router.use(middleware.init(this));
}
};
@@ -119,45 +123,22 @@ app.lazyrouter = function() {
*/
app.handle = function(req, res, done) {
var env = this.get('env');
var router = this._router;
this._router.handle(req, res, function(err) {
if (done) {
return done(err);
}
// unhandled error
if (err) {
// default to 500
if (res.statusCode < 400) res.statusCode = 500;
debug('default %s', res.statusCode);
// respect err.status
if (err.status) res.statusCode = err.status;
// production gets a basic error message
var msg = 'production' == env
? http.STATUS_CODES[res.statusCode]
: err.stack || err.toString();
msg = escapeHtml(msg);
// log to stderr in a non-test env
if ('test' != env) console.error(err.stack || err.toString());
if (res.headersSent) return req.socket.destroy();
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', Buffer.byteLength(msg));
if ('HEAD' == req.method) return res.end();
res.end(msg);
return;
}
// 404
debug('default 404');
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
if ('HEAD' == req.method) return res.end();
res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
// final handler
done = done || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
// no routes
if (!router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);
};
/**
@@ -170,37 +151,60 @@ app.handle = function(req, res, done) {
* @api public
*/
app.use = function(route, fn){
var mount_app;
app.use = function use(fn) {
var offset = 0;
var path = '/';
var self = this;
// default route to '/'
if ('string' != typeof route) fn = route, route = '/';
// default path to '/'
// disambiguate app.use([fn])
if (typeof fn !== 'function') {
var arg = fn;
// express app
if (fn.handle && fn.set) mount_app = fn;
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// restore .app property on req and res
if (mount_app) {
debug('.use app under %s', route);
mount_app.mountpath = route;
fn = function(req, res, next) {
// first arg is the path
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
var fns = flatten(slice.call(arguments, offset));
if (fns.length === 0) {
throw new TypeError('app.use() requires middleware functions');
}
// setup router
this.lazyrouter();
var router = this._router;
fns.forEach(function (fn) {
// non-express app
if (!fn || !fn.handle || !fn.set) {
return router.use(path, fn);
}
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = self;
// restore .app property on req and res
router.use(path, function mounted_app(req, res, next) {
var orig = req.app;
mount_app.handle(req, res, function(err) {
fn.handle(req, res, function (err) {
req.__proto__ = orig.request;
res.__proto__ = orig.response;
next(err);
});
};
}
});
this.lazyrouter();
this._router.use(route, fn);
// mounted an app
if (mount_app) {
mount_app.parent = this;
mount_app.emit('mount', this);
}
// mounted an app
fn.emit('mount', self);
});
return this;
};
@@ -318,6 +322,10 @@ app.set = function(setting, val){
debug('compile etag %s', val);
this.set('etag fn', compileETag(val));
break;
case 'query parser':
debug('compile query parser %s', val);
this.set('query parser fn', compileQueryParser(val));
break;
case 'trust proxy':
debug('compile trust proxy %s', val);
this.set('trust proxy fn', compileTrust(val));
@@ -420,7 +428,7 @@ methods.forEach(function(method){
this.lazyrouter();
var route = this._router.route(path);
route[method].apply(route, [].slice.call(arguments, 1));
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
@@ -439,7 +447,7 @@ app.all = function(path){
this.lazyrouter();
var route = this._router.route(path);
var args = [].slice.call(arguments, 1);
var args = slice.call(arguments, 1);
methods.forEach(function(method){
route[method].apply(route, args);
});
@@ -449,7 +457,7 @@ app.all = function(path){
// del -> delete alias
app.del = deprecate(app.delete, 'app.del: Use app.delete instead');
app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead');
/**
* Render the given view `name` name with `options`
@@ -547,3 +555,14 @@ app.listen = function(){
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
/**
* Log error using console.error.
*
* @param {Error} err
* @api public
*/
function logerror(err){
if (this.get('env') !== 'test') console.error(err.stack || err.toString());
}

View File

@@ -2,36 +2,27 @@
* Module dependencies.
*/
var qs = require('qs');
var parseUrl = require('parseurl');
var qs = require('qs');
/**
* Query:
*
* Automatically parse the query-string when available,
* populating the `req.query` object using
* [qs](https://github.com/visionmedia/node-querystring).
*
* Examples:
*
* .use(connect.query())
* .use(function(req, res){
* res.end(JSON.stringify(req.query));
* });
*
* The `options` passed are provided to qs.parse function.
*
* @param {Object} options
* @return {Function}
* @api public
*/
module.exports = function query(options){
module.exports = function query(options) {
var queryparse = qs.parse;
if (typeof options === 'function') {
queryparse = options;
options = undefined;
}
return function query(req, res, next){
if (!req.query) {
req.query = ~req.url.indexOf('?')
? qs.parse(parseUrl(req).query, options)
: {};
var val = parseUrl(req).query;
req.query = queryparse(val, options);
}
next();

View File

@@ -3,6 +3,8 @@
*/
var accepts = require('accepts');
var deprecate = require('depd')('express');
var isIP = require('net').isIP;
var typeis = require('type-is');
var http = require('http');
var fresh = require('fresh');
@@ -106,53 +108,55 @@ req.accepts = function(){
};
/**
* Check if the given `encoding` is accepted.
* Check if the given `encoding`s are accepted.
*
* @param {String} encoding
* @param {String} ...encoding
* @return {Boolean}
* @api public
*/
req.acceptsEncoding = // backwards compatibility
req.acceptsEncodings = function(){
var accept = accepts(this);
return accept.encodings.apply(accept, arguments);
};
req.acceptsEncoding = deprecate.function(req.acceptsEncodings,
'req.acceptsEncoding: Use acceptsEncodings instead');
/**
* To do: update docs.
*
* Check if the given `charset` is acceptable,
* Check if the given `charset`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
* @param {String} charset
* @param {String} ...charset
* @return {Boolean}
* @api public
*/
req.acceptsCharset = // backwards compatibility
req.acceptsCharsets = function(){
var accept = accepts(this);
return accept.charsets.apply(accept, arguments);
};
req.acceptsCharset = deprecate.function(req.acceptsCharsets,
'req.acceptsCharset: Use acceptsCharsets instead');
/**
* To do: update docs.
*
* Check if the given `lang` is acceptable,
* Check if the given `lang`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
* @param {String} lang
* @param {String} ...lang
* @return {Boolean}
* @api public
*/
req.acceptsLanguage = // backwards compatibility
req.acceptsLanguages = function(){
var accept = accepts(this);
return accept.languages.apply(accept, arguments);
};
req.acceptsLanguage = deprecate.function(req.acceptsLanguages,
'req.acceptsLanguage: Use acceptsLanguages instead');
/**
* Parse Range header field,
* capping to the given `size`.
@@ -241,7 +245,9 @@ req.is = function(types){
* Return the protocol string "http" or "https"
* when requested with TLS. When the "trust proxy"
* setting trusts the socket address, the
* "X-Forwarded-Proto" header field will be trusted.
* "X-Forwarded-Proto" header field will be trusted
* and used if present.
*
* If you're running behind a reverse proxy that
* supplies https for you this may be enabled.
*
@@ -249,18 +255,19 @@ req.is = function(types){
* @api public
*/
req.__defineGetter__('protocol', function(){
defineGetter(req, 'protocol', function protocol(){
var proto = this.connection.encrypted
? 'https'
: 'http';
var trust = this.app.get('trust proxy fn');
if (!trust(this.connection.remoteAddress)) {
return this.connection.encrypted
? 'https'
: 'http';
return proto;
}
// Note: X-Forwarded-Proto is normally only ever a
// single value, but this is to be safe.
var proto = this.get('X-Forwarded-Proto') || 'http';
proto = this.get('X-Forwarded-Proto') || proto;
return proto.split(/\s*,\s*/)[0];
});
@@ -273,7 +280,7 @@ req.__defineGetter__('protocol', function(){
* @api public
*/
req.__defineGetter__('secure', function(){
defineGetter(req, 'secure', function secure(){
return 'https' == this.protocol;
});
@@ -287,7 +294,7 @@ req.__defineGetter__('secure', function(){
* @api public
*/
req.__defineGetter__('ip', function(){
defineGetter(req, 'ip', function ip(){
var trust = this.app.get('trust proxy fn');
return proxyaddr(this, trust);
});
@@ -304,7 +311,7 @@ req.__defineGetter__('ip', function(){
* @api public
*/
req.__defineGetter__('ips', function(){
defineGetter(req, 'ips', function ips() {
var trust = this.app.get('trust proxy fn');
var addrs = proxyaddr.all(this, trust);
return addrs.slice(1).reverse();
@@ -325,12 +332,17 @@ req.__defineGetter__('ips', function(){
* @api public
*/
req.__defineGetter__('subdomains', function(){
defineGetter(req, 'subdomains', function subdomains() {
var hostname = this.hostname;
if (!hostname) return [];
var offset = this.app.get('subdomain offset');
return (this.host || '')
.split('.')
.reverse()
.slice(offset);
var subdomains = !isIP(hostname)
? hostname.split('.').reverse()
: [hostname];
return subdomains.slice(offset);
});
/**
@@ -340,12 +352,12 @@ req.__defineGetter__('subdomains', function(){
* @api public
*/
req.__defineGetter__('path', function(){
defineGetter(req, 'path', function path() {
return parse(this).pathname;
});
/**
* Parse the "Host" header field hostname.
* Parse the "Host" header field to a hostname.
*
* When the "trust proxy" setting trusts the socket
* address, the "X-Forwarded-Host" header field will
@@ -355,7 +367,7 @@ req.__defineGetter__('path', function(){
* @api public
*/
req.__defineGetter__('host', function(){
defineGetter(req, 'hostname', function hostname(){
var trust = this.app.get('trust proxy fn');
var host = this.get('X-Forwarded-Host');
@@ -376,6 +388,12 @@ req.__defineGetter__('host', function(){
: host;
});
// TODO: change req.host to return host in next major
defineGetter(req, 'host', deprecate.function(function host(){
return this.hostname;
}, 'req.host: Use req.hostname instead'));
/**
* Check if the request is fresh, aka
* Last-Modified and/or the ETag
@@ -385,7 +403,7 @@ req.__defineGetter__('host', function(){
* @api public
*/
req.__defineGetter__('fresh', function(){
defineGetter(req, 'fresh', function(){
var method = this.method;
var s = this.res.statusCode;
@@ -409,7 +427,7 @@ req.__defineGetter__('fresh', function(){
* @api public
*/
req.__defineGetter__('stale', function(){
defineGetter(req, 'stale', function stale(){
return !this.fresh;
});
@@ -420,7 +438,23 @@ req.__defineGetter__('stale', function(){
* @api public
*/
req.__defineGetter__('xhr', function(){
defineGetter(req, 'xhr', function xhr(){
var val = this.get('X-Requested-With') || '';
return 'xmlhttprequest' == val.toLowerCase();
});
/**
* Helper function for creating a getter on an object.
*
* @param {Object} obj
* @param {String} name
* @param {Function} getter
* @api private
*/
function defineGetter(obj, name, getter) {
Object.defineProperty(obj, name, {
configurable: true,
enumerable: true,
get: getter
});
};

View File

@@ -2,8 +2,11 @@
* Module dependencies.
*/
var deprecate = require('depd')('express');
var escapeHtml = require('escape-html');
var http = require('http');
var isAbsolute = require('./utils').isAbsolute;
var onFinished = require('on-finished');
var path = require('path');
var mixin = require('utils-merge');
var sign = require('cookie-signature').sign;
@@ -11,13 +14,12 @@ var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
var setCharset = require('./utils').setCharset;
var contentDisposition = require('./utils').contentDisposition;
var deprecate = require('./utils').deprecate;
var statusCodes = http.STATUS_CODES;
var cookie = require('cookie');
var send = require('send');
var basename = path.basename;
var extname = path.extname;
var mime = send.mime;
var resolve = path.resolve;
var vary = require('vary');
/**
@@ -72,80 +74,99 @@ res.links = function(links){
* res.send(new Buffer('wahoo'));
* res.send({ some: 'json' });
* res.send('<p>some html</p>');
* res.send(404, 'Sorry, cant find that');
* res.send(404);
*
* @param {string|number|boolean|object|Buffer} body
* @api public
*/
res.send = function(body){
var req = this.req;
var head = 'HEAD' == req.method;
var type;
res.send = function send(body) {
var chunk = body;
var encoding;
var len;
var req = this.req;
var type;
// settings
var app = this.app;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.send(body, status) backwards compat
if ('number' != typeof body && 'number' == typeof arguments[1]) {
if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
deprecate('res.send(body, status): Use res.status(status).send(body) instead');
this.statusCode = arguments[1];
} else {
this.statusCode = body;
body = arguments[1];
deprecate('res.send(status, body): Use res.status(status).send(body) instead');
this.statusCode = arguments[0];
chunk = arguments[1];
}
}
switch (typeof body) {
// response status
case 'number':
this.get('Content-Type') || this.type('txt');
this.statusCode = body;
body = http.STATUS_CODES[body];
break;
// disambiguate res.send(status) and res.send(status, num)
if (typeof chunk === 'number' && arguments.length === 1) {
// res.send(status) will set status message as text string
if (!this.get('Content-Type')) {
this.type('txt');
}
deprecate('res.send(status): Use res.status(status).end() instead');
this.statusCode = chunk;
chunk = http.STATUS_CODES[chunk];
}
switch (typeof chunk) {
// string defaulting to html
case 'string':
if (!this.get('Content-Type')) this.type('html');
if (!this.get('Content-Type')) {
this.type('html');
}
break;
case 'boolean':
case 'number':
case 'object':
if (null == body) {
body = '';
} else if (Buffer.isBuffer(body)) {
this.get('Content-Type') || this.type('bin');
if (chunk === null) {
chunk = '';
} else if (Buffer.isBuffer(chunk)) {
if (!this.get('Content-Type')) {
this.type('bin');
}
} else {
return this.json(body);
return this.json(chunk);
}
break;
}
// write strings in utf-8
if ('string' === typeof body) {
if (typeof chunk === 'string') {
encoding = 'utf8';
type = this.get('Content-Type');
// reflect this in content-type
if ('string' === typeof type) {
if (typeof type === 'string') {
this.set('Content-Type', setCharset(type, 'utf-8'));
}
}
// populate Content-Length
if (undefined !== body && !this.get('Content-Length')) {
len = Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body, encoding);
if (chunk !== undefined) {
if (!Buffer.isBuffer(chunk)) {
// convert chunk to Buffer; saves later double conversions
chunk = new Buffer(chunk, encoding);
encoding = undefined;
}
len = chunk.length;
this.set('Content-Length', len);
}
// method check
var isHead = req.method === 'HEAD';
// ETag support
var etag = len !== undefined && app.get('etag fn');
if (etag && ('GET' === req.method || 'HEAD' === req.method)) {
if (!this.get('ETag')) {
etag = etag(body, encoding);
if (len !== undefined && (isHead || req.method === 'GET')) {
var etag = app.get('etag fn');
if (etag && !this.get('ETag')) {
etag = etag(chunk, encoding);
etag && this.set('ETag', etag);
}
}
@@ -158,11 +179,16 @@ res.send = function(body){
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
this.removeHeader('Transfer-Encoding');
body = '';
chunk = '';
}
// skip body for HEAD
if (isHead) {
this.end();
}
// respond
this.end((head ? null : body), encoding);
this.end(chunk, encoding);
return this;
};
@@ -174,24 +200,24 @@ res.send = function(body){
*
* res.json(null);
* res.json({ user: 'tj' });
* res.json(500, 'oh noes!');
* res.json(404, 'I dont have that');
*
* @param {string|number|boolean|object} obj
* @api public
*/
res.json = function(obj){
res.json = function json(obj) {
var val = obj;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
if (typeof arguments[1] === 'number') {
deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
this.statusCode = arguments[1];
return 'number' === typeof obj
? jsonNumDeprecated.call(this, obj)
: jsonDeprecated.call(this, obj);
} else {
this.statusCode = obj;
obj = arguments[1];
deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
this.statusCode = arguments[0];
val = arguments[1];
}
}
@@ -199,20 +225,16 @@ res.json = function(obj){
var app = this.app;
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var body = JSON.stringify(obj, replacer, spaces);
var body = JSON.stringify(val, replacer, spaces);
// content-type
this.get('Content-Type') || this.set('Content-Type', 'application/json');
if (!this.get('Content-Type')) {
this.set('Content-Type', 'application/json');
}
return this.send(body);
};
var jsonDeprecated = deprecate(res.json,
'res.json(obj, status): Use res.json(status, obj) instead');
var jsonNumDeprecated = deprecate(res.json,
'res.json(num, status): Use res.status(status).json(num) instead');
/**
* Send JSON response with JSONP callback support.
*
@@ -220,24 +242,24 @@ var jsonNumDeprecated = deprecate(res.json,
*
* res.jsonp(null);
* res.jsonp({ user: 'tj' });
* res.jsonp(500, 'oh noes!');
* res.jsonp(404, 'I dont have that');
*
* @param {string|number|boolean|object} obj
* @api public
*/
res.jsonp = function(obj){
res.jsonp = function jsonp(obj) {
var val = obj;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
if (typeof arguments[1] === 'number') {
deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
this.statusCode = arguments[1];
return 'number' === typeof obj
? jsonpNumDeprecated.call(this, obj)
: jsonpDeprecated.call(this, obj);
} else {
this.statusCode = obj;
obj = arguments[1];
deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
this.statusCode = arguments[0];
val = arguments[1];
}
}
@@ -245,13 +267,14 @@ res.jsonp = function(obj){
var app = this.app;
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var body = JSON.stringify(obj, replacer, spaces)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
var body = JSON.stringify(val, replacer, spaces);
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
this.get('Content-Type') || this.set('Content-Type', 'application/json');
if (!this.get('Content-Type')) {
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'application/json');
}
// fixup callback
if (Array.isArray(callback)) {
@@ -259,20 +282,50 @@ res.jsonp = function(obj){
}
// jsonp
if (callback && 'string' === typeof callback) {
if (typeof callback === 'string' && callback.length !== 0) {
this.charset = 'utf-8';
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'text/javascript');
var cb = callback.replace(/[^\[\]\w$.]/g, '');
body = 'typeof ' + cb + ' === \'function\' && ' + cb + '(' + body + ');';
// restrict callback charset
callback = callback.replace(/[^\[\]\w$.]/g, '');
// replace chars not allowed in JavaScript that are in JSON
body = body
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
// the typeof check is just to reduce client error noise
body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
}
return this.send(body);
};
var jsonpDeprecated = deprecate(res.json,
'res.jsonp(obj, status): Use res.jsonp(status, obj) instead');
/**
* Send given HTTP status code.
*
* Sets the response status to `statusCode` and the body of the
* response to the standard description from node's http.STATUS_CODES
* or the statusCode number if no description.
*
* Examples:
*
* res.sendStatus(200);
*
* @param {number} statusCode
* @api public
*/
var jsonpNumDeprecated = deprecate(res.json,
'res.jsonp(num, status): Use res.status(status).jsonp(num) instead');
res.sendStatus = function sendStatus(statusCode) {
var body = http.STATUS_CODES[statusCode] || String(statusCode);
this.statusCode = statusCode;
this.type('txt');
return this.send(body);
};
/**
* Transfer the file at the given `path`.
@@ -285,9 +338,88 @@ var jsonpNumDeprecated = deprecate(res.json,
*
* Options:
*
* - `maxAge` defaulting to 0
* - `root` root directory for relative filenames
* - `hidden` serve hidden files, defaulting to false
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
* - `root` root directory for relative filenames
* - `headers` object of headers to serve with file
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
*
* Other options are passed along to `send`.
*
* Examples:
*
* The following example illustrates how `res.sendFile()` may
* be used as an alternative for the `static()` middleware for
* dynamic situations. The code backing `res.sendFile()` is actually
* the same code, so HTTP cache support etc is identical.
*
* app.get('/user/:uid/photos/:file', function(req, res){
* var uid = req.params.uid
* , file = req.params.file;
*
* req.user.mayViewFilesFrom(uid, function(yes){
* if (yes) {
* res.sendFile('/uploads/' + uid + '/' + file);
* } else {
* res.send(403, 'Sorry! you cant see that.');
* }
* });
* });
*
* @api public
*/
res.sendFile = function sendFile(path, options, fn) {
var req = this.req;
var res = this;
var next = req.next;
if (!path) {
throw new TypeError('path argument is required to res.sendFile');
}
// support function as second arg
if (typeof options === 'function') {
fn = options;
options = {};
}
options = options || {};
if (!options.root && !isAbsolute(path)) {
throw new TypeError('path must be absolute or specify root to res.sendFile');
}
// create file stream
var pathname = encodeURI(path);
var file = send(req, pathname, options);
// transfer
sendfile(res, file, options, function (err) {
if (fn) return fn(err);
if (err && err.code === 'EISDIR') return next();
// next() all but aborted errors
if (err && err.code !== 'ECONNABORT') {
next(err);
}
});
};
/**
* Transfer the file at the given `path`.
*
* Automatically sets the _Content-Type_ response header field.
* The callback `fn(err)` is invoked when the transfer is complete
* or when an error occurs. Be sure to check `res.sentHeader`
* if you wish to attempt responding, as the header and some data
* may have already been transferred.
*
* Options:
*
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
* - `root` root directory for relative filenames
* - `headers` object of headers to serve with file
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
*
* Other options are passed along to `send`.
*
@@ -315,65 +447,36 @@ var jsonpNumDeprecated = deprecate(res.json,
*/
res.sendfile = function(path, options, fn){
options = options || {};
var self = this;
var req = self.req;
var next = this.req.next;
var done;
var req = this.req;
var res = this;
var next = req.next;
// support function as second arg
if ('function' == typeof options) {
if (typeof options === 'function') {
fn = options;
options = {};
}
// socket errors
req.socket.on('error', error);
options = options || {};
// errors
function error(err) {
if (done) return;
done = true;
// clean up
cleanup();
if (!self.headersSent) self.removeHeader('Content-Disposition');
// callback available
if (fn) return fn(err);
// list in limbo if there's no callback
if (self.headersSent) return;
// delegate
next(err);
}
// streaming
function stream(stream) {
if (done) return;
cleanup();
if (fn) stream.on('end', fn);
}
// cleanup
function cleanup() {
req.socket.removeListener('error', error);
}
// Back-compat
options.maxage = options.maxage || options.maxAge || 0;
// create file stream
var file = send(req, path, options);
// transfer
var file = send(req, path, options);
file.on('error', error);
file.on('directory', next);
file.on('stream', stream);
file.pipe(this);
this.on('finish', cleanup);
sendfile(res, file, options, function (err) {
if (fn) return fn(err);
if (err && err.code === 'EISDIR') return next();
// next() all but aborted errors
if (err && err.code !== 'ECONNABORT') {
next(err);
}
});
};
res.sendfile = deprecate.function(res.sendfile,
'res.sendfile: Use res.sendFile instead');
/**
* Transfer the file at the given `path` as an attachment.
*
@@ -387,16 +490,24 @@ res.sendfile = function(path, options, fn){
* @api public
*/
res.download = function(path, filename, fn){
res.download = function download(path, filename, fn) {
// support function as second arg
if ('function' == typeof filename) {
if (typeof filename === 'function') {
fn = filename;
filename = null;
}
filename = filename || path;
this.set('Content-Disposition', contentDisposition(filename));
return this.sendfile(path, fn);
// set Content-Disposition when file is sent
var headers = {
'Content-Disposition': contentDisposition(filename)
};
// Resolve the full path for sendFile
var fullPath = resolve(path);
return this.sendFile(fullPath, { headers: headers }, fn);
};
/**
@@ -540,8 +651,8 @@ res.attachment = function(filename){
*/
res.set =
res.header = function(field, val){
if (2 == arguments.length) {
res.header = function header(field, val) {
if (arguments.length === 2) {
if (Array.isArray(val)) val = val.map(String);
else val = String(val);
if ('content-type' == field.toLowerCase() && !/;\s*charset\s*=/.test(val)) {
@@ -574,7 +685,7 @@ res.get = function(field){
*
* @param {String} name
* @param {Object} options
* @param {ServerResponse} for chaining
* @return {ServerResponse} for chaining
* @api public
*/
@@ -605,6 +716,7 @@ res.clearCookie = function(name, options){
* @param {String} name
* @param {String|Object} val
* @param {Options} options
* @return {ServerResponse} for chaining
* @api public
*/
@@ -650,6 +762,7 @@ res.cookie = function(name, val, options){
* res.location('../login');
*
* @param {String} url
* @return {ServerResponse} for chaining
* @api public
*/
@@ -677,32 +790,30 @@ res.location = function(url){
* res.redirect('/foo/bar');
* res.redirect('http://example.com');
* res.redirect(301, 'http://example.com');
* res.redirect('http://example.com', 301);
* res.redirect('../login'); // /blog/post/1 -> /blog/login
*
* @param {String} url
* @param {Number} code
* @api public
*/
res.redirect = function(url){
var head = 'HEAD' == this.req.method;
var status = 302;
res.redirect = function redirect(url) {
var address = url;
var body;
var status = 302;
// allow status / url
if (2 == arguments.length) {
if ('number' == typeof url) {
status = url;
url = arguments[1];
if (arguments.length === 2) {
if (typeof arguments[0] === 'number') {
status = arguments[0];
address = arguments[1];
} else {
deprecate('res.redirect(ur, status): Use res.redirect(status, url) instead');
status = arguments[1];
}
}
// Set location header
this.location(url);
url = this.get('Location');
this.location(address);
address = this.get('Location');
// Support text/{plain,html} by default
this.format({
@@ -723,7 +834,12 @@ res.redirect = function(url){
// Respond
this.statusCode = status;
this.set('Content-Length', Buffer.byteLength(body));
this.end(head ? null : body);
if (this.req.method === 'HEAD') {
this.end();
}
this.end(body);
};
/**
@@ -731,14 +847,16 @@ res.redirect = function(url){
* this call is simply ignored.
*
* @param {Array|String} field
* @param {ServerResponse} for chaining
* @return {ServerResponse} for chaining
* @api public
*/
res.vary = function(field){
// checks for back-compat
if (!field) return this;
if (Array.isArray(field) && !field.length) return this;
if (!field || (Array.isArray(field) && !field.length)) {
deprecate('res.vary(): Provide a field name');
return this;
}
vary(this, field);
@@ -781,3 +899,69 @@ res.render = function(view, options, fn){
// render
app.render(view, options, fn);
};
// pipe the send file stream
function sendfile(res, file, options, callback) {
var done = false;
// directory
function ondirectory() {
if (done) return;
done = true;
var err = new Error('EISDIR, read');
err.code = 'EISDIR';
callback(err);
}
// errors
function onerror(err) {
if (done) return;
done = true;
callback(err);
}
// ended
function onend() {
if (done) return;
done = true;
callback();
}
// finished
function onfinish(err) {
if (err) return onerror(err);
if (done) return;
setImmediate(function () {
if (done) return;
done = true;
// response finished before end of file
var err = new Error('Request aborted');
err.code = 'ECONNABORT';
callback(err);
});
}
file.on('end', onend);
file.on('error', onerror);
file.on('directory', ondirectory);
onFinished(res, onfinish);
if (options.headers) {
// set headers on successful transfer
file.on('headers', function headers(res) {
var obj = options.headers;
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
res.setHeader(k, obj[k]);
}
});
}
// pipe
file.pipe(res);
}

View File

@@ -1,3 +1,4 @@
/**
* Module dependencies.
*/
@@ -5,9 +6,18 @@
var Route = require('./route');
var Layer = require('./layer');
var methods = require('methods');
var mixin = require('utils-merge');
var debug = require('debug')('express:router');
var parseUrl = require('parseurl');
var utils = require('../utils');
/**
* Module variables.
*/
var objectRegExp = /^\[object (\S+)\]$/;
var slice = Array.prototype.slice;
var toString = Object.prototype.toString;
/**
* Initialize a new `Router` with the given `options`.
@@ -30,6 +40,7 @@ var proto = module.exports = function(options) {
router.params = {};
router._params = [];
router.caseSensitive = options.caseSensitive;
router.mergeParams = options.mergeParams;
router.strict = options.strict;
router.stack = [];
@@ -113,8 +124,6 @@ proto.handle = function(req, res, done) {
debug('dispatching %s %s', req.method, req.url);
var method = req.method.toLowerCase();
var search = 1 + req.url.indexOf('?');
var pathlength = search ? search - 1 : req.url.length;
var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://');
@@ -132,17 +141,15 @@ proto.handle = function(req, res, done) {
var stack = self.stack;
// manage inter-router variables
var parent = req.next;
var parentParams = req.params;
var parentUrl = req.baseUrl || '';
done = wrap(done, function(old, err) {
req.baseUrl = parentUrl;
req.next = parent;
old(err);
});
done = restore(done, req, 'baseUrl', 'next', 'params');
// setup next layer
req.next = next;
// for options requests, respond with a default if nothing else responds
if (method === 'options') {
if (req.method === 'OPTIONS') {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
@@ -151,35 +158,38 @@ proto.handle = function(req, res, done) {
});
}
// setup basic req values
req.baseUrl = parentUrl;
req.originalUrl = req.originalUrl || req.url;
next();
function next(err) {
if (err === 'route') {
err = undefined;
}
var layerError = err === 'route'
? null
: err;
var layer = stack[idx++];
var layerPath;
if (!layer) {
return done(err);
}
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
req.originalUrl = req.originalUrl || req.url;
removed = '';
if (removed.length !== 0) {
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
try {
var path = parseUrl(req).pathname;
if (undefined == path) path = '/';
if (!layer) {
return done(layerError);
}
if (!layer.match(path)) return next(err);
self.match_layer(layer, req, res, function (err, path) {
if (err || path === undefined) {
return next(layerError || err);
}
// route object and not middleware
var route = layer.route;
@@ -187,46 +197,56 @@ proto.handle = function(req, res, done) {
// if final route, then we support options
if (route) {
// we don't run any routes with error first
if (err) {
return next(err);
if (layerError) {
return next(layerError);
}
req.route = route;
var method = req.method;
var has_method = route._handles_method(method);
// we can now dispatch to the route
if (method === 'options' && !route.methods['options']) {
// build up automatic options response
if (!has_method && method === 'OPTIONS') {
options.push.apply(options, route._options());
}
// don't even bother
if (!has_method && method !== 'HEAD') {
return next();
}
// we can now dispatch to the route
req.route = route;
}
// Capture one-time layer values
req.params = layer.params;
layerPath = layer.path;
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
// this should be done for the layer
return self.process_params(layer, paramcalled, req, res, function(err) {
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(err);
return next(layerError || err);
}
if (route) {
return layer.handle(req, res, next);
return layer.handle_request(req, res, next);
}
trim_prefix();
trim_prefix(layer, layerError, layerPath, path);
});
});
}
} catch (err) {
next(err);
}
function trim_prefix(layer, layerError, layerPath, path) {
var c = path[layerPath.length];
if (c && '/' !== c && '.' !== c) return next(layerError);
function trim_prefix() {
var c = path[layerPath.length];
if (c && '/' != c && '.' != c) return next(err);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', removed, req.url);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
if (layerPath.length !== 0) {
debug('trim prefix (%s) from url %s', layerPath, req.url);
removed = layerPath;
req.url = protohost + req.url.substr(protohost.length + removed.length);
@@ -237,34 +257,42 @@ proto.handle = function(req, res, done) {
}
// Setup base URL (no trailing slash)
if (removed.length && removed.substr(-1) === '/') {
req.baseUrl = parentUrl + removed.substring(0, removed.length - 1);
} else {
req.baseUrl = parentUrl + removed;
}
req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
? removed.substring(0, removed.length - 1)
: removed);
}
debug('%s %s : %s', layer.handle.name || 'anonymous', layerPath, req.originalUrl);
var arity = layer.handle.length;
try {
if (err && arity === 4) {
layer.handle(err, req, res, next);
} else if (!err && arity < 4) {
layer.handle(req, res, next);
} else {
next(err);
}
} catch (err) {
next(err);
}
debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
if (layerError) {
layer.handle_error(layerError, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
function wrap(old, fn) {
return function () {
var args = [old].concat(slice.call(arguments));
fn.apply(this, args);
};
/**
* Match request to a layer.
*
* @api private
*/
proto.match_layer = function match_layer(layer, req, res, done) {
var error = null;
var path;
try {
path = parseUrl(req).pathname;
if (!layer.match(path)) {
path = undefined;
}
} catch (err) {
error = err;
}
done(error, path);
};
/**
@@ -320,29 +348,44 @@ proto.process_params = function(layer, called, req, res, done) {
}
// param previously called with same value or error occurred
if (paramCalled && (paramCalled.err || paramCalled.val === paramVal)) {
return param(paramCalled.err);
if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) {
// restore value
req.params[name] = paramCalled.value;
// next param
return param(paramCalled.error);
}
called[name] = paramCalled = { val: paramVal };
called[name] = paramCalled = {
error: null,
match: paramVal,
value: paramVal
};
try {
return paramCallback();
} catch (err) {
return done(err);
}
paramCallback();
}
// single param callbacks
function paramCallback(err) {
if (err && paramCalled) {
var fn = paramCallbacks[paramIndex++];
// store updated value
paramCalled.value = req.params[key.name];
if (err) {
// store error
paramCalled.err = err;
paramCalled.error = err;
param(err);
return;
}
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
if (!fn) return param();
try {
fn(req, res, paramCallback, paramVal, key.name);
} catch (e) {
paramCallback(e);
}
}
param();
@@ -360,40 +403,55 @@ proto.process_params = function(layer, called, req, res, done) {
* handlers can operate without any code changes regardless of the "prefix"
* pathname.
*
* @param {String|Function} route
* @param {Function} fn
* @return {app} for chaining
* @api public
*/
proto.use = function(route, fn){
// default route to '/'
if ('string' != typeof route) {
fn = route;
route = '/';
}
proto.use = function use(fn) {
var offset = 0;
var path = '/';
var self = this;
// default path to '/'
// disambiguate router.use([fn])
if (typeof fn !== 'function') {
var type = {}.toString.call(fn);
var msg = 'Router.use() requires callback functions but got a ' + type;
throw new Error(msg);
var arg = fn;
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// first arg is the path
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
// strip trailing slash
if ('/' == route[route.length - 1]) {
route = route.slice(0, -1);
var callbacks = utils.flatten(slice.call(arguments, offset));
if (callbacks.length === 0) {
throw new TypeError('Router.use() requires middleware functions');
}
var layer = new Layer(route, {
sensitive: this.caseSensitive,
strict: this.strict,
end: false
}, fn);
callbacks.forEach(function (fn) {
if (typeof fn !== 'function') {
throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
}
// add the middleware
debug('use %s %s', route || '/', fn.name || 'anonymous');
// add the middleware
debug('use %s %s', path, fn.name || '<anonymous>');
var layer = new Layer(path, {
sensitive: self.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined;
self.stack.push(layer);
});
this.stack.push(layer);
return this;
};
@@ -429,7 +487,90 @@ proto.route = function(path){
methods.concat('all').forEach(function(method){
proto[method] = function(path){
var route = this.route(path)
route[method].apply(route, [].slice.call(arguments, 1));
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
// get type for error message
function gettype(obj) {
var type = typeof obj;
if (type !== 'object') {
return type;
}
// inspect [[Class]] for objects
return toString.call(obj)
.replace(objectRegExp, '$1');
}
// merge params with parent params
function mergeParams(params, parent) {
if (typeof parent !== 'object' || !parent) {
return params;
}
// make copy of parent for base
var obj = mixin({}, parent);
// simple non-numeric merging
if (!(0 in params) || !(0 in parent)) {
return mixin(obj, params);
}
var i = 0;
var o = 0;
// determine numeric gaps
while (i === o || o in parent) {
if (i in params) i++;
if (o in parent) o++;
}
// offset numeric indices in params before merge
for (i--; i >= 0; i--) {
params[i + o] = params[i];
// create holes for the merge when necessary
if (i < o) {
delete params[i];
}
}
return mixin(parent, params);
}
// restore obj props after function
function restore(fn, obj) {
var props = new Array(arguments.length - 2);
var vals = new Array(arguments.length - 2);
for (var i = 0; i < props.length; i++) {
props[i] = arguments[i + 2];
vals[i] = obj[props[i]];
}
return function(err){
// restore vals
for (var i = 0; i < props.length; i++) {
obj[props[i]] = vals[i];
}
return fn.apply(this, arguments);
};
}
// wrap a function
function wrap(old, fn) {
return function proxy() {
var args = new Array(arguments.length + 1);
args[0] = old;
for (var i = 0, len = arguments.length; i < len; i++) {
args[i + 1] = arguments[i];
}
fn.apply(this, args);
};
}

View File

@@ -18,10 +18,67 @@ function Layer(path, options, fn) {
debug('new %s', path);
options = options || {};
this.regexp = pathRegexp(path, this.keys = [], options);
this.handle = fn;
this.name = fn.name || '<anonymous>';
this.params = undefined;
this.path = undefined;
this.regexp = pathRegexp(path, this.keys = [], options);
if (path === '/' && options.end === false) {
this.regexp.fast_slash = true;
}
}
/**
* Handle the error for the layer.
*
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {function} next
* @api private
*/
Layer.prototype.handle_error = function handle_error(error, req, res, next) {
var fn = this.handle;
if (fn.length !== 4) {
// not a standard error handler
return next(error);
}
try {
fn(error, req, res, next);
} catch (err) {
next(err);
}
};
/**
* Handle the request for the layer.
*
* @param {Request} req
* @param {Response} res
* @param {function} next
* @api private
*/
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
/**
* Check if this route matches `path`, if so
* populate `.params`.
@@ -31,30 +88,35 @@ function Layer(path, options, fn) {
* @api private
*/
Layer.prototype.match = function(path){
var keys = this.keys;
var params = this.params = {};
Layer.prototype.match = function match(path) {
if (this.regexp.fast_slash) {
// fast path non-ending match for / (everything matches)
this.params = {};
this.path = '';
return true;
}
var m = this.regexp.exec(path);
if (!m) {
this.params = undefined;
this.path = undefined;
return false;
}
// store values
this.params = {};
this.path = m[0];
var keys = this.keys;
var params = this.params;
var n = 0;
var key;
var val;
if (!m) return false;
this.path = m[0];
for (var i = 1, len = m.length; i < len; ++i) {
key = keys[i - 1];
try {
val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
} catch(e) {
var err = new Error("Failed to decode param '" + m[i] + "'");
err.status = 400;
throw err;
}
val = decode_param(m[i]);
if (key) {
params[key.name] = val;
@@ -65,3 +127,25 @@ Layer.prototype.match = function(path){
return true;
};
/**
* Decode param value.
*
* @param {string} val
* @return {string}
* @api private
*/
function decode_param(val){
if (typeof val !== 'string') {
return val;
}
try {
return decodeURIComponent(val);
} catch (e) {
var err = new TypeError("Failed to decode param '" + val + "'");
err.status = 400;
throw err;
}
}

56
lib/router/match.js Normal file
View File

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

View File

@@ -3,6 +3,7 @@
*/
var debug = require('debug')('express:router:route');
var Layer = require('./layer');
var methods = require('methods');
var utils = require('../utils');
@@ -22,12 +23,30 @@ module.exports = Route;
function Route(path) {
debug('new %s', path);
this.path = path;
this.stack = undefined;
this.stack = [];
// route handlers for various http methods
this.methods = {};
}
/**
* @api private
*/
Route.prototype._handles_method = function _handles_method(method) {
if (this.methods._all) {
return true;
}
method = method.toLowerCase();
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
return Boolean(this.methods[method]);
};
/**
* @return {Array} supported HTTP methods
* @api private
@@ -46,28 +65,22 @@ Route.prototype._options = function(){
*/
Route.prototype.dispatch = function(req, res, done){
var self = this;
var method = req.method.toLowerCase();
var idx = 0;
var stack = this.stack;
if (stack.length === 0) {
return done();
}
var method = req.method.toLowerCase();
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
req.route = self;
req.route = this;
// single middleware route case
if (typeof this.stack === 'function') {
this.stack(req, res, done);
return;
}
next();
var stack = self.stack;
if (!stack) {
return done();
}
var idx = 0;
(function next_layer(err) {
function next(err) {
if (err && err === 'route') {
return done();
}
@@ -78,33 +91,15 @@ Route.prototype.dispatch = function(req, res, done){
}
if (layer.method && layer.method !== method) {
return next_layer(err);
return next(err);
}
var arity = layer.handle.length;
if (err) {
if (arity < 4) {
return next_layer(err);
}
try {
layer.handle(err, req, res, next_layer);
} catch (err) {
next_layer(err);
}
return;
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
if (arity > 3) {
return next_layer();
}
try {
layer.handle(req, res, next_layer);
} catch (err) {
next_layer(err);
}
})();
}
};
/**
@@ -145,15 +140,11 @@ Route.prototype.all = function(){
throw new Error(msg);
}
if (!self.stack) {
self.stack = fn;
}
else if (typeof self.stack === 'function') {
self.stack = [{ handle: self.stack }, { handle: fn }];
}
else {
self.stack.push({ handle: fn });
}
var layer = Layer('/', {}, fn);
layer.method = undefined;
self.methods._all = true;
self.stack.push(layer);
});
return self;
@@ -173,18 +164,11 @@ methods.forEach(function(method){
debug('%s %s', method, self.path);
if (!self.methods[method]) {
self.methods[method] = true;
}
var layer = Layer('/', {}, fn);
layer.method = method;
if (!self.stack) {
self.stack = [];
}
else if (typeof self.stack === 'function') {
self.stack = [{ handle: self.stack }];
}
self.stack.push({ method: method, handle: fn });
self.methods[method] = true;
self.stack.push(layer);
});
return self;
};

View File

@@ -3,40 +3,12 @@
*/
var mime = require('send').mime;
var crc32 = require('buffer-crc32');
var crypto = require('crypto');
var basename = require('path').basename;
var deprecate = require('util').deprecate;
var etag = require('etag');
var proxyaddr = require('proxy-addr');
/**
* Simple detection of charset parameter in content-type
*/
var charsetRegExp = /;\s*charset\s*=/;
/**
* Deprecate function, like core `util.deprecate`,
* but with NODE_ENV and color support.
*
* @param {Function} fn
* @param {String} msg
* @return {Function}
* @api private
*/
exports.deprecate = function(fn, msg){
if (process.env.NODE_ENV === 'test') return fn;
// prepend module name
msg = 'express: ' + msg;
if (process.stderr.isTTY) {
// colorize
msg = '\x1b[31;1m' + msg + '\x1b[0m';
}
return deprecate(fn, msg);
};
var qs = require('qs');
var querystring = require('querystring');
var typer = require('media-typer');
/**
* Return strong ETag for `body`.
@@ -47,17 +19,12 @@ exports.deprecate = function(fn, msg){
* @api private
*/
exports.etag = function etag(body, encoding){
if (body.length === 0) {
// fast-path empty body
return '"1B2M2Y8AsgTpgAmY7PhCfg=="'
}
exports.etag = function (body, encoding) {
var buf = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
: body
var hash = crypto
.createHash('md5')
.update(body, encoding)
.digest('base64')
return '"' + hash + '"'
return etag(buf, {weak: false})
};
/**
@@ -70,16 +37,11 @@ exports.etag = function etag(body, encoding){
*/
exports.wetag = function wetag(body, encoding){
if (body.length === 0) {
// fast-path empty body
return 'W/"0-0"'
}
var buf = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
: body
var buf = Buffer.isBuffer(body)
? body
: new Buffer(body, encoding)
var len = buf.length
return 'W/"' + len.toString(16) + '-' + crc32.unsigned(buf) + '"'
return etag(buf, {weak: true})
};
/**
@@ -164,7 +126,7 @@ exports.contentDisposition = function(filename){
filename = basename(filename);
// if filename contains non-ascii characters, add a utf-8 version ala RFC 5987
ret = /[^\040-\176]/.test(filename)
? 'attachment; filename=' + encodeURI(filename) + '; filename*=UTF-8\'\'' + encodeURI(filename)
? 'attachment; filename="' + encodeURI(filename) + '"; filename*=UTF-8\'\'' + encodeURI(filename)
: 'attachment; filename="' + filename + '"';
}
@@ -231,6 +193,41 @@ exports.compileETag = function(val) {
return fn;
}
/**
* Compile "query parser" value to function.
*
* @param {String|Function} val
* @return {Function}
* @api private
*/
exports.compileQueryParser = function compileQueryParser(val) {
var fn;
if (typeof val === 'function') {
return val;
}
switch (val) {
case true:
fn = querystring.parse;
break;
case false:
fn = newObject;
break;
case 'extended':
fn = qs.parse;
break;
case 'simple':
fn = querystring.parse;
break;
default:
throw new TypeError('unknown value for query parser function: ' + val);
}
return fn;
}
/**
* Compile "proxy trust" value to function.
*
@@ -272,21 +269,23 @@ exports.compileTrust = function(val) {
exports.setCharset = function(type, charset){
if (!type || !charset) return type;
var exists = charsetRegExp.test(type);
// parse type
var parsed = typer.parse(type);
// removing existing charset
if (exists) {
var parts = type.split(';');
// set charset
parsed.parameters.charset = charset;
for (var i = 1; i < parts.length; i++) {
if (charsetRegExp.test(';' + parts[i])) {
parts.splice(i, 1);
break;
}
}
type = parts.join(';');
}
return type + '; charset=' + charset;
// format type
return typer.format(parsed);
};
/**
* Return new empty objet.
*
* @return {Object}
* @api private
*/
function newObject() {
return {};
}

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "4.4.2",
"description": "Fast, unopinionated, minimalist web framework",
"version": "4.9.3",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
@@ -9,7 +9,8 @@
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Guillermo Rauch <rauchg@gmail.com>",
"Jonathan Ong <me@jongleberry.com>",
"Roman Shtylman <shtylman+expressjs@gmail.com"
"Roman Shtylman <shtylman+expressjs@gmail.com>",
"Young Jae Sim <hanul@hanul.me>"
],
"keywords": [
"express",
@@ -22,55 +23,61 @@
"app",
"api"
],
"repository": "visionmedia/express",
"repository": "strongloop/express",
"license": "MIT",
"homepage": "http://expressjs.com/",
"dependencies": {
"accepts": "1.0.2",
"buffer-crc32": "0.2.1",
"debug": "1.0.1",
"accepts": "~1.1.0",
"cookie-signature": "1.0.5",
"debug": "~2.0.0",
"depd": "0.4.5",
"escape-html": "1.0.1",
"methods": "1.0.1",
"parseurl": "1.0.1",
"proxy-addr": "1.0.1",
"range-parser": "1.0.0",
"send": "0.4.2",
"serve-static": "1.2.2",
"type-is": "1.2.1",
"vary": "0.1.0",
"etag": "~1.3.1",
"finalhandler": "0.2.0",
"fresh": "0.2.4",
"media-typer": "0.3.0",
"methods": "1.1.0",
"on-finished": "~2.1.0",
"parseurl": "~1.3.0",
"path-to-regexp": "0.1.3",
"proxy-addr": "~1.0.2",
"qs": "2.2.3",
"range-parser": "~1.0.2",
"send": "0.9.2",
"serve-static": "~1.6.2",
"type-is": "~1.5.1",
"vary": "~1.0.0",
"cookie": "0.1.2",
"fresh": "0.2.2",
"cookie-signature": "1.0.3",
"merge-descriptors": "0.0.2",
"utils-merge": "1.0.0",
"qs": "0.6.6",
"path-to-regexp": "0.1.2"
"utils-merge": "1.0.0"
},
"devDependencies": {
"after": "0.8.1",
"istanbul": "0.2.10",
"mocha": "~1.20.1",
"istanbul": "0.3.2",
"mocha": "~1.21.4",
"should": "~4.0.4",
"supertest": "~0.13.0",
"connect-redis": "~2.0.0",
"ejs": "~1.0.0",
"jade": "~1.3.1",
"marked": "0.3.2",
"multiparty": "~3.2.4",
"hjs": "~0.0.6",
"body-parser": "1.3.0",
"cookie-parser": "1.1.0",
"express-session": "1.2.1",
"method-override": "2.0.2",
"morgan": "1.1.1",
"vhost": "2.0.0"
"body-parser": "~1.8.2",
"connect-redis": "~2.1.0",
"cookie-parser": "~1.3.3",
"express-session": "~1.8.2",
"jade": "~1.6.0",
"method-override": "~2.2.0",
"morgan": "~1.3.1",
"multiparty": "~3.3.2",
"vhost": "~3.0.0"
},
"engines": {
"node": ">= 0.10.0"
},
"scripts": {
"prepublish": "npm prune",
"test": "mocha --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/"
}
}

View File

@@ -1,4 +1,6 @@
var after = require('after');
var should = require('should');
var express = require('../')
, Route = express.Route
, methods = require('methods')
@@ -8,167 +10,182 @@ describe('Route', function(){
describe('.all', function(){
it('should add handler', function(done){
var req = { method: 'GET', url: '/' };
var route = new Route('/foo');
route.all(function(req, res, next) {
assert.equal(req.a, 1);
assert.equal(res.b, 2);
req.called = true;
next();
});
route.dispatch({ a:1, method: 'GET' }, { b:2 }, done);
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.ok;
done();
});
})
it('should handle VERBS', function(done) {
var route = new Route('/foo');
var count = 0;
var route = new Route('/foo');
var cb = after(methods.length, function (err) {
if (err) return done(err);
count.should.equal(methods.length);
done();
});
route.all(function(req, res, next) {
count++;
next();
});
methods.forEach(function testMethod(method) {
route.dispatch({ method: method }, {});
var req = { method: method, url: '/' };
route.dispatch(req, {}, cb);
});
assert.equal(count, methods.length);
done();
})
it('should stack', function(done) {
var req = { count: 0, method: 'GET', url: '/' };
var route = new Route('/foo');
var count = 0;
route.all(function(req, res, next) {
count++;
req.count++;
next();
});
route.all(function(req, res, next) {
count++;
req.count++;
next();
});
route.dispatch({ method: 'GET' }, {}, function(err) {
assert.ifError(err);
count++;
route.dispatch(req, {}, function (err) {
if (err) return done(err);
req.count.should.equal(2);
done();
});
assert.equal(count, 3);
done();
})
})
describe('.VERB', function(){
it('should support .get', function(done){
var req = { method: 'GET', url: '/' };
var route = new Route('');
var count = 0;
route.get(function(req, res, next) {
count++;
req.called = true;
next();
})
route.dispatch({ method: 'GET' }, {});
assert(count);
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.ok;
done();
});
})
it('should limit to just .VERB', function(done){
var req = { method: 'POST', url: '/' };
var route = new Route('');
route.get(function(req, res, next) {
assert(false);
done();
throw new Error('not me!');
})
route.post(function(req, res, next) {
assert(true);
req.called = true;
next();
})
route.dispatch({ method: 'post' }, {});
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.true;
done();
});
})
it('should allow fallthrough', function(done){
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.get(function(req, res, next) {
order += 'a';
req.order += 'a';
next();
})
route.all(function(req, res, next) {
order += 'b';
req.order += 'b';
next();
});
route.get(function(req, res, next) {
order += 'c';
req.order += 'c';
next();
})
route.dispatch({ method: 'get' }, {});
assert.equal(order, 'abc');
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
req.order.should.equal('abc');
done();
});
})
})
describe('errors', function(){
it('should handle errors via arity 4 functions', function(done){
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.all(function(req, res, next){
next(new Error('foobar'));
});
route.all(function(req, res, next){
order += '0';
req.order += '0';
next();
});
route.all(function(err, req, res, next){
order += 'a';
req.order += 'a';
next(err);
});
route.all(function(err, req, res, next){
assert.equal(err.message, 'foobar');
assert.equal(order, 'a');
route.dispatch(req, {}, function (err) {
should(err).be.ok;
should(err.message).equal('foobar');
req.order.should.equal('a');
done();
});
route.dispatch({ method: 'get' }, {});
})
it('should handle throw', function(done) {
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.all(function(req, res, next){
throw new Error('foobar');
});
route.all(function(req, res, next){
order += '0';
req.order += '0';
next();
});
route.all(function(err, req, res, next){
order += 'a';
req.order += 'a';
next(err);
});
route.all(function(err, req, res, next){
assert.equal(err.message, 'foobar');
assert.equal(order, 'a');
route.dispatch(req, {}, function (err) {
should(err).be.ok;
should(err.message).equal('foobar');
req.order.should.equal('a');
done();
});
route.dispatch({ method: 'get' }, {});
});
it('should handle throwing inside error handlers', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.get(function(req, res, next){
@@ -180,11 +197,42 @@ describe('Route', function(){
});
route.get(function(err, req, res, next){
assert.equal(err.message, 'oops');
done();
req.message = err.message;
next();
});
route.dispatch({ url: '/', method: 'GET' }, {}, done);
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.message).equal('oops');
done();
});
});
it('should handle throw in .all', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.all(function(req, res, next){
throw new Error('boom!');
});
route.dispatch(req, {}, function(err){
should(err).be.ok;
err.message.should.equal('boom!');
done();
});
});
it('should handle single error handler', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.all(function(err, req, res, next){
// this should not execute
true.should.be.false;
});
route.dispatch(req, {}, done);
});
})
})

View File

@@ -174,6 +174,111 @@ describe('Router', function(){
});
})
describe('FQDN', function () {
it('should not obscure FQDNs', function (done) {
var request = { hit: 0, url: 'http://example.com/foo', method: 'GET' };
var router = new Router();
router.use(function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/foo');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should ignore FQDN in search', function (done) {
var request = { hit: 0, url: '/proxy?url=http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use('/proxy', function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, '/?url=http://example.com/blog/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should adjust FQDN req.url', function (done) {
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use('/blog', function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should adjust FQDN req.url with multiple handlers', function (done) {
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use(function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/blog/post/1');
next();
});
router.use('/blog', function (req, res, next) {
assert.equal(req.hit++, 1);
assert.equal(req.url, 'http://example.com/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 2);
done();
});
});
it('should adjust FQDN req.url with multiple routed handlers', function (done) {
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use('/blog', function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/post/1');
next();
});
router.use('/blog', function (req, res, next) {
assert.equal(req.hit++, 1);
assert.equal(req.url, 'http://example.com/post/1');
next();
});
router.use(function (req, res, next) {
assert.equal(req.hit++, 2);
assert.equal(req.url, 'http://example.com/blog/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 3);
done();
});
});
})
describe('.all', function() {
it('should support using .all to capture all http verbs', function(done){
var router = new Router();
@@ -192,6 +297,43 @@ describe('Router', function(){
})
})
describe('.use', function() {
it('should require arguments', function(){
var router = new Router();
router.use.bind(router).should.throw(/requires middleware function/)
})
it('should not accept non-functions', function(){
var router = new Router();
router.use.bind(router, '/', 'hello').should.throw(/requires middleware function.*string/)
router.use.bind(router, '/', 5).should.throw(/requires middleware function.*number/)
router.use.bind(router, '/', null).should.throw(/requires middleware function.*Null/)
router.use.bind(router, '/', new Date()).should.throw(/requires middleware function.*Date/)
})
it('should accept array of middleware', function(done){
var count = 0;
var router = new Router();
function fn1(req, res, next){
assert.equal(++count, 1);
next();
}
function fn2(req, res, next){
assert.equal(++count, 2);
next();
}
router.use([fn1, fn2], function(req, res){
assert.equal(++count, 3);
done();
});
router.handle({ url: '/foo', method: 'GET' }, {}, function(){});
})
})
describe('.param', function() {
it('should call param function when routing VERBS', function(done) {
var router = new Router();

View File

@@ -22,6 +22,7 @@ describe('cookies', function(){
it('should respond to cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.expect(302, function(err, res){
if (err) return done(err)
@@ -37,6 +38,7 @@ describe('cookies', function(){
it('should clear cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.expect(302, function(err, res){
if (err) return done(err)
@@ -53,6 +55,7 @@ describe('cookies', function(){
it('should set a cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.expect(302, function(err, res){
res.headers.should.have.property('set-cookie')

View File

@@ -33,6 +33,7 @@ describe('mvc', function(){
it('should update the pet', function(done){
request(app)
.put('/pet/3')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ pet: { name: 'Boots' } })
.end(function(err, res){
if (err) return done(err);
@@ -102,6 +103,7 @@ describe('mvc', function(){
it('should update the user', function(done){
request(app)
.put('/user/1')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ user: { name: 'Tobo' }})
.end(function(err, res){
if (err) return done(err);
@@ -116,6 +118,7 @@ describe('mvc', function(){
it('should create a pet for user', function(done){
request(app)
.post('/user/2/pet')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ pet: { name: 'Snickers' }})
.expect('Location', '/user/2')
.expect(302, function(err, res){

View File

@@ -0,0 +1,97 @@
var app = require('../../examples/route-separation')
var request = require('supertest')
describe('route-separation', function () {
describe('GET /', function () {
it('should respond with index', function (done) {
request(app)
.get('/')
.expect(200, /Route Separation Example/, done)
})
})
describe('GET /users', function () {
it('should list users', function (done) {
request(app)
.get('/users')
.expect(/TJ/)
.expect(/Tobi/)
.expect(200, done)
})
})
describe('GET /user/:id', function () {
it('should get a user', function (done) {
request(app)
.get('/user/0')
.expect(200, /Viewing user TJ/, done)
})
it('should 404 on missing user', function (done) {
request(app)
.get('/user/10')
.expect(404, done)
})
})
describe('GET /user/:id/view', function () {
it('should get a user', function (done) {
request(app)
.get('/user/0/view')
.expect(200, /Viewing user TJ/, done)
})
it('should 404 on missing user', function (done) {
request(app)
.get('/user/10/view')
.expect(404, done)
})
})
describe('GET /user/:id/edit', function () {
it('should get a user to edit', function (done) {
request(app)
.get('/user/0/edit')
.expect(200, /Editing user TJ/, done)
})
})
describe('PUT /user/:id/edit', function () {
it('should edit a user', function (done) {
request(app)
.put('/user/0/edit')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ user: { name: 'TJ', email: 'tj-invalid@vision-media.ca' } })
.expect(302, function (err) {
if (err) return done(err)
request(app)
.get('/user/0')
.expect(200, /tj-invalid@vision-media\.ca/, done)
})
})
})
describe('POST /user/:id/edit?_method=PUT', function () {
it('should edit a user', function (done) {
request(app)
.post('/user/1/edit?_method=PUT')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ user: { name: 'Tobi', email: 'tobi-invalid@vision-media.ca' } })
.expect(302, function (err) {
if (err) return done(err)
request(app)
.get('/user/1')
.expect(200, /tobi-invalid@vision-media\.ca/, done)
})
})
})
describe('GET /posts', function () {
it('should get a list of posts', function (done) {
request(app)
.get('/posts')
.expect(200, /Posts/, done)
})
})
})

View File

@@ -53,7 +53,7 @@ describe('web-service', function(){
.get('/api/repos?api-key=foo')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(/"name":"express"/)
.expect(/"url":"http:\/\/github.com\/visionmedia\/express"/)
.expect(/"url":"http:\/\/github.com\/strongloop\/express"/)
.expect(200, done)
})
})

View File

@@ -34,6 +34,8 @@ describe('HEAD', function(){
.get('/tobi')
.expect(200, function(err, res){
if (err) return done(err);
delete headers.date;
delete res.headers.date;
assert.deepEqual(res.headers, headers);
done();
});

View File

@@ -1,6 +1,7 @@
var express = require('../')
, assert = require('assert');
var assert = require('assert')
var express = require('..')
var request = require('supertest')
describe('app', function(){
it('should inherit from event emitter', function(done){
@@ -8,6 +9,17 @@ describe('app', function(){
app.on('foo', done);
app.emit('foo');
})
it('should be callable', function(){
var app = express();
assert(typeof app, 'function');
})
it('should 404 without routes', function(done){
request(express())
.get('/')
.expect(404, done);
})
})
describe('app.parent', function(){
@@ -27,16 +39,19 @@ describe('app.parent', function(){
describe('app.mountpath', function(){
it('should return the mounted path', function(){
var app = express()
, blog = express()
, blogAdmin = express();
var admin = express();
var app = express();
var blog = express();
var fallback = express();
app.use('/blog', blog);
blog.use('/admin', blogAdmin);
app.use(fallback);
blog.use('/admin', admin);
admin.mountpath.should.equal('/admin');
app.mountpath.should.equal('/');
blog.mountpath.should.equal('/blog');
blogAdmin.mountpath.should.equal('/admin');
fallback.mountpath.should.equal('/');
})
})

View File

@@ -16,6 +16,24 @@ describe('OPTIONS', function(){
.expect('Allow', 'GET,PUT', done);
})
it('should not be affected by app.all', function(done){
var app = express();
app.get('/', function(){});
app.get('/users', function(req, res){});
app.put('/users', function(req, res){});
app.all('/users', function(req, res, next){
res.setHeader('x-hit', '1');
next();
});
request(app)
.options('/users')
.expect('x-hit', '1')
.expect('allow', 'GET,PUT')
.expect(200, 'GET,PUT', done);
})
it('should not respond if the path is not defined', function(done){
var app = express();

View File

@@ -157,6 +157,51 @@ describe('app', function(){
.expect('2 2 foo,bob', done);
})
it('should support altering req.params across routes', function(done) {
var app = express();
app.param('user', function(req, res, next, user) {
req.params.user = 'loki';
next();
});
app.get('/:user', function(req, res, next) {
next('route');
});
app.get('/:user', function(req, res, next) {
res.send(req.params.user);
});
request(app)
.get('/bob')
.expect('loki', done);
})
it('should not invoke without route handler', function(done) {
var app = express();
app.param('thing', function(req, res, next, thing) {
req.thing = thing;
next();
});
app.param('user', function(req, res, next, user) {
next(new Error('invalid invokation'));
});
app.post('/:user', function(req, res, next) {
res.send(req.params.user);
});
app.get('/:thing', function(req, res, next) {
res.send(req.thing);
});
request(app)
.get('/bob')
.expect(200, 'bob', done);
})
it('should work with encoded values', function(done){
var app = express();
@@ -192,6 +237,27 @@ describe('app', function(){
.expect(500, done);
})
it('should catch thrown secondary error', function(done){
var app = express();
app.param('id', function(req, res, next, val){
process.nextTick(next);
});
app.param('id', function(req, res, next, id){
throw new Error('err!');
});
app.get('/user/:id', function(req, res){
var id = req.params.id;
res.send('' + id);
});
request(app)
.get('/user/123')
.expect(500, done);
})
it('should defer to next route', function(done){
var app = express();

View File

@@ -1,11 +1,39 @@
var after = require('after');
var express = require('../')
, request = require('supertest')
, assert = require('assert')
, methods = require('methods');
describe('app.router', function(){
describe('methods supported', function(){
it('should restore req.params after leaving router', function(done){
var app = express();
var router = new express.Router();
function handler1(req, res, next){
res.setHeader('x-user-id', req.params.id);
next()
}
function handler2(req, res){
res.send(req.params.id);
}
router.use(function(req, res, next){
res.setHeader('x-router', req.params.id);
next();
});
app.get('/user/:id', handler1, router, handler2);
request(app)
.get('/user/1')
.expect('x-router', 'undefined')
.expect('x-user-id', '1')
.expect(200, '1', done);
})
describe('methods', function(){
methods.concat('del').forEach(function(method){
if (method === 'connect') return;
@@ -31,6 +59,35 @@ describe('app.router', function(){
app[method].bind(app, '/', 3).should.throw(/Number/);
})
});
it('should re-route when method is altered', function (done) {
var app = express();
var cb = after(3, done);
app.use(function (req, res, next) {
if (req.method !== 'POST') return next();
req.method = 'DELETE';
res.setHeader('X-Method-Altered', '1');
next();
});
app.delete('/', function (req, res) {
res.end('deleted everything');
});
request(app)
.get('/')
.expect(404, 'Cannot GET /\n', cb);
request(app)
.delete('/')
.expect(200, 'deleted everything', cb);
request(app)
.post('/')
.expect('X-Method-Altered', '1')
.expect(200, 'deleted everything', cb);
});
})
describe('decode querystring', function(){
@@ -183,6 +240,106 @@ describe('app.router', function(){
})
})
describe('params', function(){
it('should overwrite existing req.params by default', function(done){
var app = express();
var router = new express.Router();
router.get('/:action', function(req, res){
res.send(req.params);
});
app.use('/user/:user', router);
request(app)
.get('/user/1/get')
.expect(200, '{"action":"get"}', done);
})
it('should allow merging existing req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:action', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/:user', router);
request(app)
.get('/user/tj/get')
.expect(200, '[["action","get"],["user","tj"]]', done);
})
it('should use params from router', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:thing', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/:thing', router);
request(app)
.get('/user/tj/get')
.expect(200, '[["thing","get"]]', done);
})
it('should merge numeric indices req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/*.*', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/id:(\\d+)', router);
request(app)
.get('/user/id:10/profile.json')
.expect(200, '[["0","10"],["1","profile"],["2","json"]]', done);
})
it('should merge numeric indices req.params when more in parent', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/*', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/id:(\\d+)/name:(\\w+)', router);
request(app)
.get('/user/id:10/name:tj/profile')
.expect(200, '[["0","10"],["1","tj"],["2","profile"]]', done);
})
it('should ignore invalid incoming req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:name', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/', function (req, res, next) {
req.params = 3; // wat?
router(req, res, next);
});
request(app)
.get('/user/tj')
.expect(200, '[["name","tj"]]', done);
})
})
describe('trailing slashes', function(){
it('should be optional by default', function(done){
var app = express();
@@ -211,6 +368,46 @@ describe('app.router', function(){
.expect('tj', done);
})
it('should pass-though middleware', function(done){
var app = express();
app.enable('strict routing');
app.use(function (req, res, next) {
res.setHeader('x-middleware', 'true');
next();
});
app.get('/user/', function(req, res){
res.end('tj');
});
request(app)
.get('/user/')
.expect('x-middleware', 'true')
.expect(200, 'tj', done);
})
it('should pass-though mounted middleware', function(done){
var app = express();
app.enable('strict routing');
app.use('/user/', function (req, res, next) {
res.setHeader('x-middleware', 'true');
next();
});
app.get('/user/test/', function(req, res){
res.end('tj');
});
request(app)
.get('/user/test/')
.expect('x-middleware', 'true')
.expect(200, 'tj', done);
})
it('should match no slashes', function(done){
var app = express();
@@ -225,6 +422,48 @@ describe('app.router', function(){
.expect('tj', done);
})
it('should match middleware when omitting the trailing slash', function(done){
var app = express();
app.enable('strict routing');
app.use('/user/', function(req, res){
res.end('tj');
});
request(app)
.get('/user')
.expect(200, 'tj', done);
})
it('should match middleware', function(done){
var app = express();
app.enable('strict routing');
app.use('/user', function(req, res){
res.end('tj');
});
request(app)
.get('/user')
.expect(200, 'tj', done);
})
it('should match middleware when adding the trailing slash', function(done){
var app = express();
app.enable('strict routing');
app.use('/user', function(req, res){
res.end('tj');
});
request(app)
.get('/user/')
.expect(200, 'tj', done);
})
it('should fail when omitting the trailing slash', function(done){
var app = express();
@@ -391,7 +630,7 @@ describe('app.router', function(){
.expect('', done);
})
it('should require a preceeding /', function(done){
it('should require a preceding /', function(done){
var app = express();
app.get('/file/*', function(req, res){
@@ -597,6 +836,32 @@ describe('app.router', function(){
done();
})
})
it('should call handler in same route, if exists', function(done){
var app = express();
function fn1(req, res, next) {
next(new Error('boom!'));
}
function fn2(req, res, next) {
res.send('foo here');
}
function fn3(err, req, res, next) {
res.send('route go ' + err.message);
}
app.get('/foo', fn1, fn2, fn3);
app.use(function (err, req, res, next) {
res.end('error!');
})
request(app)
.get('/foo')
.expect('route go boom!', done)
})
})
it('should allow rewriting of the url', function(done){

View File

@@ -1,6 +1,7 @@
var express = require('../')
, request = require('supertest');
var after = require('after');
var express = require('..');
var request = require('supertest');
describe('app', function(){
it('should emit "mount" when mounted', function(done){
@@ -15,11 +16,6 @@ describe('app', function(){
app.use(blog);
})
it('should reject numbers', function(){
var app = express();
app.use.bind(app, 3).should.throw(/Number/);
})
describe('.use(app)', function(){
it('should mount the app', function(done){
var blog = express()
@@ -83,5 +79,449 @@ describe('app', function(){
.get('/post/once-upon-a-time')
.expect('success', done);
})
it('should support mounted app anywhere', function(done){
var cb = after(3, done);
var blog = express()
, other = express()
, app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
blog.get('/', function(req, res){
res.end('success');
});
blog.once('mount', function (parent) {
parent.should.equal(app);
cb();
});
other.once('mount', function (parent) {
parent.should.equal(app);
cb();
});
app.use('/post/:article', fn1, other, fn2, blog);
request(app)
.get('/post/once-upon-a-time')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('success', cb);
})
})
describe('.use(middleware)', function(){
it('should accept multiple arguments', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
app.use(fn1, fn2, function fn3(req, res) {
res.setHeader('x-fn-3', 'hit');
res.end();
});
request(app)
.get('/')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should invoke middleware for all requests', function (done) {
var app = express();
var cb = after(3, done);
app.use(function (req, res) {
res.send('saw ' + req.method + ' ' + req.url);
});
request(app)
.get('/')
.expect(200, 'saw GET /', cb);
request(app)
.options('/')
.expect(200, 'saw OPTIONS /', cb);
request(app)
.post('/foo')
.expect(200, 'saw POST /foo', cb);
})
it('should accept array of middleware', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
function fn3(req, res, next) {
res.setHeader('x-fn-3', 'hit');
res.end();
}
app.use([fn1, fn2, fn3]);
request(app)
.get('/')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should accept multiple arrays of middleware', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
function fn3(req, res, next) {
res.setHeader('x-fn-3', 'hit');
res.end();
}
app.use([fn1, fn2], [fn3]);
request(app)
.get('/')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should accept nested arrays of middleware', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
function fn3(req, res, next) {
res.setHeader('x-fn-3', 'hit');
res.end();
}
app.use([[fn1], fn2], [fn3]);
request(app)
.get('/')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
})
describe('.use(path, middleware)', function(){
it('should reject missing functions', function () {
var app = express();
app.use.bind(app, '/').should.throw(/requires middleware function/);
})
it('should reject non-functions as middleware', function () {
var app = express();
app.use.bind(app, '/', 'hi').should.throw(/requires middleware function.*string/);
app.use.bind(app, '/', 5).should.throw(/requires middleware function.*number/);
app.use.bind(app, '/', null).should.throw(/requires middleware function.*Null/);
app.use.bind(app, '/', new Date()).should.throw(/requires middleware function.*Date/);
})
it('should strip path from req.url', function (done) {
var app = express();
app.use('/foo', function (req, res) {
res.send('saw ' + req.method + ' ' + req.url);
});
request(app)
.get('/foo/bar')
.expect(200, 'saw GET /bar', done);
})
it('should accept multiple arguments', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
app.use('/foo', fn1, fn2, function fn3(req, res) {
res.setHeader('x-fn-3', 'hit');
res.end();
});
request(app)
.get('/foo')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should invoke middleware for all requests starting with path', function (done) {
var app = express();
var cb = after(3, done);
app.use('/foo', function (req, res) {
res.send('saw ' + req.method + ' ' + req.url);
});
request(app)
.get('/')
.expect(404, cb);
request(app)
.post('/foo')
.expect(200, 'saw POST /', cb);
request(app)
.post('/foo/bar')
.expect(200, 'saw POST /bar', cb);
})
it('should work if path has trailing slash', function (done) {
var app = express();
var cb = after(3, done);
app.use('/foo/', function (req, res) {
res.send('saw ' + req.method + ' ' + req.url);
});
request(app)
.get('/')
.expect(404, cb);
request(app)
.post('/foo')
.expect(200, 'saw POST /', cb);
request(app)
.post('/foo/bar')
.expect(200, 'saw POST /bar', cb);
})
it('should accept array of middleware', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
function fn3(req, res, next) {
res.setHeader('x-fn-3', 'hit');
res.end();
}
app.use('/foo', [fn1, fn2, fn3]);
request(app)
.get('/foo')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should accept multiple arrays of middleware', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
function fn3(req, res, next) {
res.setHeader('x-fn-3', 'hit');
res.end();
}
app.use('/foo', [fn1, fn2], [fn3]);
request(app)
.get('/foo')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should accept nested arrays of middleware', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
function fn3(req, res, next) {
res.setHeader('x-fn-3', 'hit');
res.end();
}
app.use('/foo', [fn1, [fn2]], [fn3]);
request(app)
.get('/foo')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should support array of paths', function (done) {
var app = express();
var cb = after(3, done);
app.use(['/foo/', '/bar'], function (req, res) {
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
});
request(app)
.get('/')
.expect(404, cb);
request(app)
.get('/foo')
.expect(200, 'saw GET / through /foo', cb);
request(app)
.get('/bar')
.expect(200, 'saw GET / through /bar', cb);
})
it('should support array of paths with middleware array', function (done) {
var app = express();
var cb = after(2, done);
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
function fn3(req, res, next) {
res.setHeader('x-fn-3', 'hit');
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
}
app.use(['/foo/', '/bar'], [[fn1], fn2], [fn3]);
request(app)
.get('/foo')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, 'saw GET / through /foo', cb);
request(app)
.get('/bar')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, 'saw GET / through /bar', cb);
})
it('should support regexp path', function (done) {
var app = express();
var cb = after(4, done);
app.use(/^\/[a-z]oo/, function (req, res) {
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
});
request(app)
.get('/')
.expect(404, cb);
request(app)
.get('/foo')
.expect(200, 'saw GET / through /foo', cb);
request(app)
.get('/zoo/bear')
.expect(200, 'saw GET /bear through /zoo/bear', cb);
request(app)
.get('/get/zoo')
.expect(404, cb);
})
it('should support empty string path', function (done) {
var app = express();
app.use('', function (req, res) {
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
});
request(app)
.get('/')
.expect(200, 'saw GET / through /', done);
})
})
})

1
test/fixtures/% of dogs.txt vendored Normal file
View File

@@ -0,0 +1 @@
20%

1
test/fixtures/blog/index.html vendored Normal file
View File

@@ -0,0 +1 @@
<b>index</b>

View File

@@ -9,7 +9,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)
@@ -23,7 +23,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)
@@ -36,7 +36,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)

View File

@@ -0,0 +1,49 @@
var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsCharsets(type)', function(){
describe('when Accept-Charset is not present', function(){
it('should return true', function(done){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
});
request(app)
.get('/')
.expect('yes', done);
})
})
describe('when Accept-Charset is not present', function(){
it('should return true when present', function(done){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
});
request(app)
.get('/')
.set('Accept-Charset', 'foo, bar, utf-8')
.expect('yes', done);
})
it('should return false otherwise', function(done){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
});
request(app)
.get('/')
.set('Accept-Charset', 'foo, bar')
.expect('no', done);
})
})
})
})

View File

@@ -3,7 +3,7 @@ var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsEncodings', function(){
describe('.acceptsEncoding', function(){
it('should be true if encoding accpeted', function(done){
var app = express();

View File

@@ -0,0 +1,36 @@
var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsEncodingss', function(){
it('should be true if encoding accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsEncodings('gzip').should.be.ok;
req.acceptsEncodings('deflate').should.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Encoding', ' gzip, deflate')
.expect(200, done);
})
it('should be false if encoding not accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsEncodings('bogus').should.not.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Encoding', ' gzip, deflate')
.expect(200, done);
})
})
})

View File

@@ -0,0 +1,53 @@
var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsLanguages', function(){
it('should be true if language accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguages('en-us').should.be.ok;
req.acceptsLanguages('en').should.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
it('should be false if language not accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguages('es').should.not.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
describe('when Accept-Language is not present', function(){
it('should always return true', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguages('en').should.be.ok;
req.acceptsLanguages('es').should.be.ok;
req.acceptsLanguages('jp').should.be.ok;
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})
})
})

138
test/req.hostname.js Normal file
View File

@@ -0,0 +1,138 @@
var express = require('../')
, request = require('supertest')
, assert = require('assert');
describe('req', function(){
describe('.hostname', function(){
it('should return the Host when present', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.post('/')
.set('Host', 'example.com')
.expect('example.com', done);
})
it('should strip port number', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.post('/')
.set('Host', 'example.com:3000')
.expect('example.com', done);
})
it('should return undefined otherwise', function(done){
var app = express();
app.use(function(req, res){
req.headers.host = null;
res.end(String(req.hostname));
});
request(app)
.post('/')
.expect('undefined', done);
})
it('should work with IPv6 Host', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.post('/')
.set('Host', '[::1]')
.expect('[::1]', done);
})
it('should work with IPv6 Host and port', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.post('/')
.set('Host', '[::1]:3000')
.expect('[::1]', done);
})
describe('when "trust proxy" is enabled', function(){
it('should respect X-Forwarded-Host', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'example.com:3000')
.expect('example.com', done);
})
it('should ignore X-Forwarded-Host if socket addr not trusted', function(done){
var app = express();
app.set('trust proxy', '10.0.0.1');
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'example.com')
.expect('localhost', done);
})
it('should default to Host', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.get('/')
.set('Host', 'example.com')
.expect('example.com', done);
})
})
describe('when "trust proxy" is disabled', function(){
it('should ignore X-Forwarded-Host', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'evil')
.expect('localhost', done);
})
})
})
})

View File

@@ -45,10 +45,9 @@ describe('req', function(){
res.send(req.ip);
});
request(app)
.get('/')
.set('X-Forwarded-For', 'client, p1, p2')
.expect('127.0.0.1', done);
var test = request(app).get('/')
test.set('X-Forwarded-For', 'client, p1, p2')
test.expect(200, getExpectedClientAddress(test._server), done);
})
})
})
@@ -63,10 +62,19 @@ describe('req', function(){
res.send(req.ip);
});
request(app)
.get('/')
.expect('127.0.0.1', done);
var test = request(app).get('/')
test.expect(200, getExpectedClientAddress(test._server), done);
})
})
})
})
/**
* Get the local client address depending on AF_NET of server
*/
function getExpectedClientAddress(server) {
return server.address().address === '::'
? '::ffff:127.0.0.1'
: '127.0.0.1';
}

View File

@@ -34,7 +34,7 @@ describe('req', function(){
it('should check req.body', function(done){
var app = express();
app.use(bodyParser());
app.use(bodyParser.json());
app.use(function(req, res){
res.end(req.param('name'));

View File

@@ -32,6 +32,21 @@ describe('req', function(){
.expect('https', done);
})
it('should default to the socket addr if X-Forwarded-Proto not present', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
req.connection.encrypted = true;
res.end(req.protocol);
});
request(app)
.get('/')
.expect('https', done);
})
it('should ignore X-Forwarded-Proto if socket addr not trusted', function(done){
var app = express();

View File

@@ -5,33 +5,91 @@ var express = require('../')
describe('req', function(){
describe('.query', function(){
it('should default to {}', function(done){
var app = express();
app.use(function(req, res){
req.query.should.eql({});
res.end();
});
var app = createApp();
request(app)
.get('/')
.end(function(res){
done();
});
})
.expect(200, '{}', done);
});
it('should contain the parsed query-string', function(done){
var app = express();
app.use(function(req, res){
req.query.should.eql({ user: { name: 'tj' }});
res.end();
});
it('should default to parse complex keys', function (done) {
var app = createApp();
request(app)
.get('/?user[name]=tj')
.end(function(res){
done();
.expect(200, '{"user":{"name":"tj"}}', done);
});
describe('when "query parser" is extended', function () {
it('should parse complex keys', function (done) {
var app = createApp('extended');
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user":{"name":"tj"}}', done);
});
})
});
describe('when "query parser" is simple', function () {
it('should not parse complex keys', function (done) {
var app = createApp('simple');
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user[name]":"tj"}', done);
});
});
describe('when "query parser" is a function', function () {
it('should parse using function', function (done) {
var app = createApp(function (str) {
return {'length': (str || '').length};
});
request(app)
.get('/?user[name]=tj')
.expect(200, '{"length":17}', done);
});
});
describe('when "query parser" disabled', function () {
it('should not parse query', function (done) {
var app = createApp(false);
request(app)
.get('/?user[name]=tj')
.expect(200, '{}', done);
});
});
describe('when "query parser" disabled', function () {
it('should not parse complex keys', function (done) {
var app = createApp(true);
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user[name]":"tj"}', done);
});
});
describe('when "query parser" an unknown value', function () {
it('should throw', function () {
createApp.bind(null, 'bogus').should.throw(/unknown value.*query parser/);
});
});
})
})
function createApp(setting) {
var app = express();
if (setting !== undefined) {
app.set('query parser', setting);
}
app.use(function (req, res) {
res.send(req.query);
});
return app;
}

View File

@@ -15,7 +15,33 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'tobi.ferrets.example.com')
.expect(["ferrets","tobi"], done);
.expect(200, ['ferrets', 'tobi'], done);
})
it('should work with IPv4 address', function(done){
var app = express();
app.use(function(req, res){
res.send(req.subdomains);
});
request(app)
.get('/')
.set('Host', '127.0.0.1')
.expect(200, [], done);
})
it('should work with IPv6 address', function(done){
var app = express();
app.use(function(req, res){
res.send(req.subdomains);
});
request(app)
.get('/')
.set('Host', '[::1]')
.expect(200, [], done);
})
})
@@ -30,7 +56,7 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'example.com')
.expect([], done);
.expect(200, [], done);
})
})
@@ -45,7 +71,23 @@ describe('req', function(){
request(app)
.get('/')
.expect([], done);
.expect(200, [], done);
})
})
describe('with trusted X-Forwarded-Host', function () {
it('should return an array', function (done) {
var app = express();
app.set('trust proxy', true);
app.use(function (req, res) {
res.send(req.subdomains);
});
request(app)
.get('/')
.set('X-Forwarded-Host', 'tobi.ferrets.example.com')
.expect(200, ['ferrets', 'tobi'], done);
})
})
@@ -62,7 +104,35 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'tobi.ferrets.sub.example.com')
.expect(["com","example","sub","ferrets","tobi"], done);
.expect(200, ['com', 'example', 'sub', 'ferrets', 'tobi'], done);
})
it('should return an array with the whole IPv4', function (done) {
var app = express();
app.set('subdomain offset', 0);
app.use(function(req, res){
res.send(req.subdomains);
});
request(app)
.get('/')
.set('Host', '127.0.0.1')
.expect(200, ['127.0.0.1'], done);
})
it('should return an array with the whole IPv6', function (done) {
var app = express();
app.set('subdomain offset', 0);
app.use(function(req, res){
res.send(req.subdomains);
});
request(app)
.get('/')
.set('Host', '[::1]')
.expect(200, ['[::1]'], done);
})
})
@@ -78,7 +148,7 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'tobi.ferrets.sub.example.com')
.expect(["ferrets","tobi"], done);
.expect(200, ['ferrets', 'tobi'], done);
})
})
@@ -94,7 +164,7 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'sub.example.com')
.expect([], done);
.expect(200, [], done);
})
})
})

View File

@@ -57,7 +57,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('Content-Disposition', 'attachment;' +
' filename=%E6%97%A5%E6%9C%AC%E8%AA%9E.txt;' +
' filename="%E6%97%A5%E6%9C%AC%E8%AA%9E.txt";' +
' filename*=UTF-8\'\'%E6%97%A5%E6%9C%AC%E8%AA%9E.txt',
done);
})

View File

@@ -14,11 +14,8 @@ describe('res', function(){
request(app)
.get('/?callback=something')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
done();
})
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /something\(\{"count":1\}\);/, done);
})
it('should use first callback parameter with jsonp', function(done){
@@ -29,12 +26,9 @@ describe('res', function(){
});
request(app)
.get('/?callback=something&callback=somethingelse')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
done();
})
.get('/?callback=something&callback=somethingelse')
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /something\(\{"count":1\}\);/, done);
})
it('should ignore object callback parameter with jsonp', function(done){
@@ -61,11 +55,8 @@ describe('res', function(){
request(app)
.get('/?clb=something')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
done();
})
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /something\(\{"count":1\}\);/, done);
})
it('should allow []', function(done){
@@ -77,11 +68,8 @@ describe('res', function(){
request(app)
.get('/?callback=callbacks[123]')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof callbacks[123] === \'function\' && callbacks[123]({"count":1});');
done();
})
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /callbacks\[123\]\(\{"count":1\}\);/, done);
})
it('should disallow arbitrary js', function(done){
@@ -93,11 +81,8 @@ describe('res', function(){
request(app)
.get('/?callback=foo;bar()')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof foobar === \'function\' && foobar({});');
done();
})
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /foobar\(\{\}\);/, done);
})
it('should escape utf whitespace', function(done){
@@ -109,13 +94,37 @@ describe('res', function(){
request(app)
.get('/?callback=foo')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof foo === \'function\' && foo({"str":"\\u2028 \\u2029 woot"});');
done();
});
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /foo\(\{"str":"\\u2028 \\u2029 woot"\}\);/, done);
});
it('should not escape utf whitespace for json fallback', function(done){
var app = express();
app.use(function(req, res){
res.jsonp({ str: '\u2028 \u2029 woot' });
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"str":"\u2028 \u2029 woot"}', done);
});
it('should include security header and prologue', function (done) {
var app = express();
app.use(function(req, res){
res.jsonp({ count: 1 });
});
request(app)
.get('/?callback=something')
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect('X-Content-Type-Options', 'nosniff')
.expect(200, /^\/\*\*\//, done);
})
it('should not override previous Content-Types with no callback', function(done){
var app = express();
@@ -127,7 +136,11 @@ describe('res', function(){
request(app)
.get('/')
.expect('Content-Type', 'application/vnd.example+json; charset=utf-8')
.expect(200, '{"hello":"world"}', done);
.expect(200, '{"hello":"world"}', function (err, res) {
if (err) return done(err);
res.headers.should.not.have.property('x-content-type-options');
done();
});
})
it('should override previous Content-Types with callback', function(done){
@@ -141,6 +154,7 @@ describe('res', function(){
request(app)
.get('/?callback=cb')
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect('X-Content-Type-Options', 'nosniff')
.expect(200, /cb\(\{"hello":"world"\}\);$/, done);
})

View File

@@ -81,6 +81,21 @@ describe('res', function(){
})
})
describe('.send(code, number)', function(){
it('should send number as json', function(done){
var app = express();
app.use(function(req, res){
res.send(200, 0.123);
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '0.123', done);
})
})
describe('.send(String)', function(){
it('should send as html', function(done){
var app = express();
@@ -103,13 +118,13 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
var str = Array(1024 * 2).join('-');
var str = Array(1000).join('-');
res.send(str);
});
request(app)
.get('/')
.expect('ETag', 'W/"7ff-2796319984"')
.expect('ETag', 'W/"3e7-8084ccd1"')
.end(done);
})
@@ -117,7 +132,7 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
var str = Array(1024 * 2).join('-');
var str = Array(1000).join('-');
res.send(str);
});
@@ -192,13 +207,13 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
var str = Array(1024 * 2).join('-');
var str = Array(1000).join('-');
res.send(new Buffer(str));
});
request(app)
.get('/')
.expect('ETag', 'W/"7ff-2796319984"')
.expect('ETag', 'W/"3e7-8084ccd1"')
.end(done);
})
@@ -306,15 +321,17 @@ describe('res', function(){
it('should respond with 304 Not Modified when fresh', function(done){
var app = express();
var etag = '"asdf"';
app.use(function(req, res){
var str = Array(1024 * 2).join('-');
var str = Array(1000).join('-');
res.set('ETag', etag);
res.send(str);
});
request(app)
.get('/')
.set('If-None-Match', 'W/"7ff-2796319984"')
.set('If-None-Match', etag)
.expect(304, done);
})
@@ -360,7 +377,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('etag', 'W/"c-1525560792"', done)
.expect('etag', 'W/"c-5aee35d8"', done)
})
it('should send ETag for empty string response', function(done){
@@ -381,7 +398,7 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
var str = Array(1024 * 2).join('-');
var str = Array(1000).join('-');
res.send(str);
});
@@ -389,7 +406,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('etag', 'W/"7ff-2796319984"', done)
.expect('etag', 'W/"3e7-8084ccd1"', done)
});
it('should not override ETag when manually set', function(done){
@@ -430,7 +447,7 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
var str = Array(1024 * 2).join('-');
var str = Array(1000).join('-');
res.send(str);
});
@@ -488,7 +505,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('etag', 'W/"d-1486392595"', done)
.expect('etag', 'W/"d-58988d13"', done)
})
})
@@ -497,8 +514,10 @@ describe('res', function(){
var app = express()
app.set('etag', function(body, encoding){
body.should.equal('hello, world!')
encoding.should.equal('utf8')
var chunk = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
: body;
chunk.toString().should.equal('hello, world!')
return '"custom"'
});

597
test/res.sendFile.js Normal file
View File

@@ -0,0 +1,597 @@
var after = require('after');
var express = require('../')
, request = require('supertest')
, assert = require('assert');
var onFinished = require('on-finished');
var path = require('path');
var should = require('should');
var fixtures = path.join(__dirname, 'fixtures');
describe('res', function(){
describe('.sendFile(path)', function () {
it('should error missing path', function (done) {
var app = createApp();
request(app)
.get('/')
.expect(500, /path.*required/, done);
});
it('should transfer a file', function (done) {
var app = createApp(path.resolve(fixtures, 'name.txt'));
request(app)
.get('/')
.expect(200, 'tobi', done);
});
it('should transfer a file with special characters in string', function (done) {
var app = createApp(path.resolve(fixtures, '% of dogs.txt'));
request(app)
.get('/')
.expect(200, '20%', done);
});
it('should 404 for directory', function (done) {
var app = createApp(path.resolve(fixtures, 'blog'));
request(app)
.get('/')
.expect(404, done);
});
it('should 404 when not found', function (done) {
var app = createApp(path.resolve(fixtures, 'does-no-exist'));
app.use(function (req, res) {
res.statusCode = 200;
res.send('no!');
});
request(app)
.get('/')
.expect(404, done);
});
it('should not override manual content-types', function (done) {
var app = express();
app.use(function (req, res) {
res.contentType('application/x-bogus');
res.sendFile(path.resolve(fixtures, 'name.txt'));
});
request(app)
.get('/')
.expect('Content-Type', 'application/x-bogus')
.end(done);
})
describe('with "dotfiles" option', function () {
it('should not serve dotfiles by default', function (done) {
var app = createApp(path.resolve(__dirname, 'fixtures/.name'));
request(app)
.get('/')
.expect(404, done);
});
it('should accept dotfiles option', function(done){
var app = createApp(path.resolve(__dirname, 'fixtures/.name'), { dotfiles: 'allow' });
request(app)
.get('/')
.expect(200, 'tobi', done);
});
});
describe('with "headers" option', function () {
it('should accept headers option', function (done) {
var headers = {
'x-success': 'sent',
'x-other': 'done'
};
var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), { headers: headers });
request(app)
.get('/')
.expect('x-success', 'sent')
.expect('x-other', 'done')
.expect(200, done);
});
it('should ignore headers option on 404', function (done) {
var headers = { 'x-success': 'sent' };
var app = createApp(path.resolve(__dirname, 'fixtures/does-not-exist'), { headers: headers });
request(app)
.get('/')
.expect(404, function (err, res) {
if (err) return done(err);
res.headers.should.not.have.property('x-success');
done();
});
});
});
describe('with "root" option', function () {
it('should not transfer relative with without', function (done) {
var app = createApp('test/fixtures/name.txt');
request(app)
.get('/')
.expect(500, /must be absolute/, done);
})
it('should serve relative to "root"', function (done) {
var app = createApp('name.txt', {root: fixtures});
request(app)
.get('/')
.expect(200, 'tobi', done);
})
it('should disallow requesting out of "root"', function (done) {
var app = createApp('foo/../../user.html', {root: fixtures});
request(app)
.get('/')
.expect(403, done);
})
})
})
describe('.sendFile(path, fn)', function () {
it('should invoke the callback when complete', function (done) {
var cb = after(2, done);
var app = createApp(path.resolve(fixtures, 'name.txt'), cb);
request(app)
.get('/')
.expect(200, cb);
})
it('should invoke the callback when client aborts', function (done) {
var cb = after(1, done);
var app = express();
app.use(function (req, res) {
setImmediate(function () {
res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
should(err).be.ok;
err.code.should.equal('ECONNABORT');
cb();
});
});
test.abort();
});
var test = request(app).get('/');
test.expect(200, cb);
})
it('should invoke the callback when client already aborted', function (done) {
var cb = after(1, done);
var app = express();
app.use(function (req, res) {
onFinished(res, function () {
res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
should(err).be.ok;
err.code.should.equal('ECONNABORT');
cb();
});
});
test.abort();
});
var test = request(app).get('/');
test.expect(200, cb);
})
it('should invoke the callback on 404', function(done){
var app = express();
app.use(function (req, res) {
res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {
should(err).be.ok;
err.status.should.equal(404);
res.send('got it');
});
});
request(app)
.get('/')
.expect(200, 'got it', done);
})
})
describe('.sendfile(path, fn)', function(){
it('should invoke the callback when complete', function(done){
var app = express();
var cb = after(2, done);
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', cb)
});
request(app)
.get('/')
.expect(200, cb);
})
it('should utilize the same options as express.static()', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', { maxAge: 60000 });
});
request(app)
.get('/')
.expect('Cache-Control', 'public, max-age=60')
.end(done);
})
it('should invoke the callback when client aborts', function (done) {
var cb = after(1, done);
var app = express();
app.use(function (req, res) {
setImmediate(function () {
res.sendfile('test/fixtures/name.txt', function (err) {
should(err).be.ok;
err.code.should.equal('ECONNABORT');
cb();
});
});
test.abort();
});
var test = request(app).get('/');
test.expect(200, cb);
})
it('should invoke the callback when client already aborted', function (done) {
var cb = after(1, done);
var app = express();
app.use(function (req, res) {
onFinished(res, function () {
res.sendfile('test/fixtures/name.txt', function (err) {
should(err).be.ok;
err.code.should.equal('ECONNABORT');
cb();
});
});
test.abort();
});
var test = request(app).get('/');
test.expect(200, cb);
})
it('should invoke the callback on 404', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('test/fixtures/nope.html', function(err){
++calls;
assert(!res.headersSent);
res.send(err.message);
});
});
request(app)
.get('/')
.end(function(err, res){
assert(1 == calls, 'called too many times');
res.text.should.startWith("ENOENT, stat");
res.statusCode.should.equal(200);
done();
});
})
it('should not override manual content-types', function(done){
var app = express();
app.use(function(req, res){
res.contentType('txt');
res.sendfile('test/fixtures/user.html');
});
request(app)
.get('/')
.expect('Content-Type', 'text/plain; charset=utf-8')
.end(done);
})
it('should invoke the callback on 403', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('test/fixtures/foo/../user.html', function(err){
assert(!res.headersSent);
++calls;
res.send(err.message);
});
});
request(app)
.get('/')
.expect('Forbidden')
.expect(200, done);
})
it('should invoke the callback on socket error', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', function(err){
assert(!res.headersSent);
req.socket.listeners('error').should.have.length(1); // node's original handler
done();
});
req.socket.emit('error', new Error('broken!'));
});
request(app)
.get('/')
.end(function(){});
})
})
describe('.sendfile(path)', function(){
it('should not serve dotfiles', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/.name');
});
request(app)
.get('/')
.expect(404, done);
})
it('should accept dotfiles option', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/.name', { dotfiles: 'allow' });
});
request(app)
.get('/')
.expect(200, 'tobi', done);
})
it('should accept headers option', function(done){
var app = express();
var headers = {
'x-success': 'sent',
'x-other': 'done'
};
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', { headers: headers });
});
request(app)
.get('/')
.expect('x-success', 'sent')
.expect('x-other', 'done')
.expect(200, done);
})
it('should ignore headers option on 404', function(done){
var app = express();
var headers = { 'x-success': 'sent' };
app.use(function(req, res){
res.sendfile('test/fixtures/user.nothing', { headers: headers });
});
request(app)
.get('/')
.expect(404, function (err, res) {
if (err) return done(err);
res.headers.should.not.have.property('x-success');
done();
});
})
it('should transfer a file', function (done) {
var app = express();
app.use(function (req, res) {
res.sendfile('test/fixtures/name.txt');
});
request(app)
.get('/')
.expect(200, 'tobi', done);
});
it('should transfer a directory index file', function (done) {
var app = express();
app.use(function (req, res) {
res.sendfile('test/fixtures/blog/');
});
request(app)
.get('/')
.expect(200, '<b>index</b>', done);
});
it('should 404 for directory without trailing slash', function (done) {
var app = express();
app.use(function (req, res) {
res.sendfile('test/fixtures/blog');
});
request(app)
.get('/')
.expect(404, done);
});
it('should transfer a file with urlencoded name', function (done) {
var app = express();
app.use(function (req, res) {
res.sendfile('test/fixtures/%25%20of%20dogs.txt');
});
request(app)
.get('/')
.expect(200, '20%', done);
});
describe('with an absolute path', function(){
it('should transfer the file', function(done){
var app = express();
app.use(function(req, res){
res.sendfile(__dirname + '/fixtures/user.html');
});
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
})
})
describe('with a relative path', function(){
it('should transfer the file', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/user.html');
});
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
})
it('should serve relative to "root"', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('user.html', { root: 'test/fixtures/' });
});
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
})
it('should consider ../ malicious when "root" is not set', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/foo/../user.html');
});
request(app)
.get('/')
.expect(403, done);
})
it('should allow ../ when "root" is set', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('foo/../user.html', { root: 'test/fixtures' });
});
request(app)
.get('/')
.expect(200, done);
})
it('should disallow requesting out of "root"', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('foo/../../user.html', { root: 'test/fixtures' });
});
request(app)
.get('/')
.expect(403, done);
})
it('should next(404) when not found', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('user.html');
});
app.use(function(req, res){
assert(0, 'this should not be called');
});
app.use(function(err, req, res, next){
++calls;
next(err);
});
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(404);
calls.should.equal(1);
done();
});
})
describe('with non-GET', function(){
it('should still serve', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile(__dirname + '/fixtures/name.txt');
});
request(app)
.get('/')
.expect('tobi', done);
})
})
})
})
})
function createApp(path, options, fn) {
var app = express();
app.use(function (req, res) {
res.sendFile(path, options, fn);
});
return app;
}

32
test/res.sendStatus.js Normal file
View File

@@ -0,0 +1,32 @@
var assert = require('assert')
var express = require('..')
var request = require('supertest')
describe('res', function () {
describe('.sendStatus(statusCode)', function () {
it('should send the status code and message as body', function (done) {
var app = express();
app.use(function(req, res){
res.sendStatus(201);
});
request(app)
.get('/')
.expect(201, 'Created', done);
})
it('should work with unknown code', function (done) {
var app = express();
app.use(function(req, res){
res.sendStatus(599);
});
request(app)
.get('/')
.expect(599, '599', done);
})
})
})

View File

@@ -1,262 +0,0 @@
var express = require('../')
, request = require('supertest')
, assert = require('assert');
describe('res', function(){
describe('.sendfile(path, fn)', function(){
it('should invoke the callback when complete', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', done)
});
request(app)
.get('/')
.expect(200)
.end(function(){});
})
it('should utilize the same options as express.static()', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', { maxAge: 60000 });
});
request(app)
.get('/')
.expect('Cache-Control', 'public, max-age=60')
.end(done);
})
it('should invoke the callback on 404', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('test/fixtures/nope.html', function(err){
++calls;
assert(!res.headersSent);
res.send(err.message);
});
});
request(app)
.get('/')
.end(function(err, res){
assert(1 == calls, 'called too many times');
res.text.should.startWith("ENOENT, stat");
res.statusCode.should.equal(200);
done();
});
})
it('should not override manual content-types', function(done){
var app = express();
app.use(function(req, res){
res.contentType('txt');
res.sendfile('test/fixtures/user.html');
});
request(app)
.get('/')
.expect('Content-Type', 'text/plain; charset=utf-8')
.end(done);
})
it('should invoke the callback on 403', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('test/fixtures/foo/../user.html', function(err){
assert(!res.headersSent);
++calls;
res.send(err.message);
});
});
request(app)
.get('/')
.expect('Forbidden')
.expect(200, done);
})
it('should invoke the callback on socket error', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', function(err){
assert(!res.headersSent);
req.socket.listeners('error').should.have.length(1); // node's original handler
done();
});
req.socket.emit('error', new Error('broken!'));
});
request(app)
.get('/')
.end(function(){});
})
})
describe('.sendfile(path)', function(){
it('should not serve hidden files', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/.name');
});
request(app)
.get('/')
.expect(404, done);
})
it('should accept hidden option', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/.name', { hidden: true });
});
request(app)
.get('/')
.expect(200, 'tobi', done);
})
describe('with an absolute path', function(){
it('should transfer the file', function(done){
var app = express();
app.use(function(req, res){
res.sendfile(__dirname + '/fixtures/user.html');
});
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
})
})
describe('with a relative path', function(){
it('should transfer the file', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/user.html');
});
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
})
it('should serve relative to "root"', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('user.html', { root: 'test/fixtures/' });
});
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
})
it('should consider ../ malicious when "root" is not set', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/foo/../user.html');
});
request(app)
.get('/')
.expect(403, done);
})
it('should allow ../ when "root" is set', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('foo/../user.html', { root: 'test/fixtures' });
});
request(app)
.get('/')
.expect(200, done);
})
it('should disallow requesting out of "root"', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('foo/../../user.html', { root: 'test/fixtures' });
});
request(app)
.get('/')
.expect(403, done);
})
it('should next(404) when not found', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('user.html');
});
app.use(function(req, res){
assert(0, 'this should not be called');
});
app.use(function(err, req, res, next){
++calls;
next(err);
});
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(404);
calls.should.equal(1);
done();
});
})
describe('with non-GET', function(){
it('should still serve', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile(__dirname + '/fixtures/name.txt');
});
request(app)
.get('/')
.expect('tobi', done);
})
})
})
})
})

View File

@@ -1,2 +1,3 @@
process.env.NODE_ENV = 'test';
process.env.NO_DEPRECATION = 'express';

View File

@@ -2,28 +2,6 @@
var utils = require('../lib/utils')
, assert = require('assert');
describe('utils.deprecate(fn, msg)', function(){
var env
before(function(){
env = process.env.NODE_ENV
})
after(function(){
process.env.NODE_ENV = env
})
it('should pass-through fn in test environment', function(){
var fn = function(){}
process.env.NODE_ENV = 'test'
utils.deprecate(fn).should.equal(fn)
})
it('should return new fn in other environment', function(){
var fn = function(){}
process.env.NODE_ENV = ''
utils.deprecate(fn).should.not.equal(fn)
})
})
describe('utils.etag(body, encoding)', function(){
it('should support strings', function(){
utils.etag('express!')
@@ -50,18 +28,18 @@ describe('utils.etag(body, encoding)', function(){
describe('utils.wetag(body, encoding)', function(){
it('should support strings', function(){
utils.wetag('express!')
.should.eql('W/"8-3098196679"')
.should.eql('W/"8-b8aabac7"')
})
it('should support utf8 strings', function(){
utils.wetag('express❤', 'utf8')
.should.eql('W/"a-1751845617"')
.should.eql('W/"a-686b0af1"')
})
it('should support buffer', function(){
var buf = new Buffer('express!')
utils.wetag(buf)
.should.eql('W/"8-3098196679"');
.should.eql('W/"8-b8aabac7"');
})
it('should support empty string', function(){