Compare commits

...

298 Commits
4.2.0 ... 4.8.2

Author SHA1 Message Date
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
Douglas Christopher Wilson
7daae1912b 4.4.2 2014-06-09 20:40:39 -04:00
Douglas Christopher Wilson
3205f68510 deps: update example dependencies 2014-06-09 20:39:34 -04:00
Douglas Christopher Wilson
898dcfac8b Merge tag '3.10.4' 2014-06-09 20:39:22 -04:00
Douglas Christopher Wilson
b1efa19f97 deps: update testing dependencies 2014-06-09 20:24:36 -04:00
Douglas Christopher Wilson
b45fd70f99 deps: send@0.4.2 2014-06-09 20:22:42 -04:00
Douglas Christopher Wilson
7f6c7a19c6 deps: serve-static@1.2.2 2014-06-09 20:21:31 -04:00
Douglas Christopher Wilson
142462d539 deps: debug@1.0.1 2014-06-09 20:20:50 -04:00
Douglas Christopher Wilson
f881784e9b 3.10.4 2014-06-09 18:53:52 -04:00
Douglas Christopher Wilson
5af625903f deps: send@0.4.2 2014-06-09 18:51:55 -04:00
Douglas Christopher Wilson
dc94f305cc deps: debug@1.0.1 2014-06-09 18:50:36 -04:00
Douglas Christopher Wilson
8060a49c6c deps: connect@2.19.5 2014-06-09 18:50:00 -04:00
Douglas Christopher Wilson
4d3e0d88a2 Fix catching errors from top-level handlers 2014-06-09 09:44:37 -04:00
Nick Heiner
253ce4837a Use path.resolve for views dir instead of concat
closes #2165
2014-06-08 17:41:36 -04:00
Joshua Goldberg
ad05eb8222 Fix typo in console.log in multipart example
closes #2164
2014-06-07 20:14:33 -04:00
Douglas Christopher Wilson
21393c244c tests: add more route tests 2014-06-06 11:12:52 -04:00
Douglas Christopher Wilson
4279e6ef45 improve before hook in mvc example 2014-06-06 10:42:29 -04:00
Douglas Christopher Wilson
3db6dd752f change confusing 404 handling in download example 2014-06-06 10:23:47 -04:00
Douglas Christopher Wilson
fcbe68eeb5 docs: move badges 2014-06-06 00:41:08 -04:00
Douglas Christopher Wilson
5019f38e29 tests: add more tests 2014-06-06 00:38:14 -04:00
Douglas Christopher Wilson
9bf1247716 Merge tag '3.10.3' 2014-06-05 23:45:31 -04:00
Douglas Christopher Wilson
2fd31f6ea6 3.10.3 2014-06-05 23:38:58 -04:00
Douglas Christopher Wilson
9cf7bba8f0 deps: connect@2.19.4 2014-06-05 23:37:22 -04:00
Douglas Christopher Wilson
2e257d1cf7 build: use compressed formats in package 2014-06-05 19:45:00 -04:00
Douglas Christopher Wilson
980a15d847 deps: type-is@1.2.1 2014-06-05 19:40:27 -04:00
Douglas Christopher Wilson
56831d7799 deps: debug@1.0.0 2014-06-05 19:34:05 -04:00
Jonathan Ong
6d65ae5ba6 use vary@0.1.0
with backwards compatibility

closes #2161
2014-06-05 19:32:32 -04:00
Douglas Christopher Wilson
c919b4a573 3.10.2 2014-06-03 21:35:34 -04:00
Douglas Christopher Wilson
fe6f392c2d deps: connect@2.19.3 2014-06-03 21:33:55 -04:00
Douglas Christopher Wilson
bffb71d4c8 deps: proxy-addr@1.0.1 2014-06-03 17:25:20 -04:00
Douglas Christopher Wilson
3b34a537ee 3.10.1 2014-06-03 16:45:09 -04:00
Douglas Christopher Wilson
ad79ce9c4b deps: connect@2.19.2 2014-06-03 16:44:29 -04:00
Douglas Christopher Wilson
721f6388c3 deps: proxy-addr@1.0.1 2014-06-03 16:42:49 -04:00
Douglas Christopher Wilson
402ec83157 Merge tag '3.10.0' 2014-06-03 00:47:39 -04:00
Douglas Christopher Wilson
298ac11018 3.10.0 2014-06-03 00:40:27 -04:00
Douglas Christopher Wilson
bb6e207336 deps: connect@2.19.1 2014-06-03 00:37:57 -04:00
Douglas Christopher Wilson
f433b7c7cf replace utils.escape with html-escape 2014-06-03 00:37:32 -04:00
Douglas Christopher Wilson
a94278abd1 deps: send@0.4.1 2014-06-02 21:31:23 -04:00
Douglas Christopher Wilson
f9ec70edd0 4.4.1 2014-06-02 21:13:50 -04:00
Douglas Christopher Wilson
9e5a758e7c deps: update example dependencies 2014-06-02 20:59:45 -04:00
Douglas Christopher Wilson
492e933796 deps: serve-static@1.2.1 2014-06-02 20:50:54 -04:00
Douglas Christopher Wilson
8ccd9d0eb5 deps: send@0.4.1 2014-06-02 20:49:11 -04:00
Douglas Christopher Wilson
16fdc11ccb deps: methods@1.0.1 2014-06-02 20:48:18 -04:00
Douglas Christopher Wilson
a7cd5a2553 deps: methods@1.0.1 2014-06-02 19:19:56 -04:00
Douglas Christopher Wilson
9e6b881f85 remove jsdoc params for polymorphic functions
until jsdoc has a way to actually document them

closes #2156
2014-06-02 10:26:15 -04:00
Douglas Christopher Wilson
95fa49147b 4.4.0 2014-05-31 00:00:39 -04:00
Douglas Christopher Wilson
f665c57c5c update serve-static to 1.2.0 2014-05-30 22:53:02 -04:00
Douglas Christopher Wilson
9024d24e81 deps: supertest@~0.13.0 2014-05-30 22:50:22 -04:00
Douglas Christopher Wilson
f92a7ad0a3 update accepts to 1.0.2 2014-05-30 22:48:32 -04:00
Douglas Christopher Wilson
db4448dda8 Merge tag '3.9.0' 2014-05-30 22:17:51 -04:00
Douglas Christopher Wilson
0dc5836d5e 3.9.0 2014-05-30 21:35:12 -04:00
Douglas Christopher Wilson
8751d7ecf8 tests: add more tests 2014-05-30 21:28:48 -04:00
Douglas Christopher Wilson
c21226aa7c improve etag control for res.send
closes #1435
closes #2129
2014-05-30 21:02:21 -04:00
Douglas Christopher Wilson
3e358458f4 tests: add more etag tests 2014-05-30 19:51:32 -04:00
Douglas Christopher Wilson
766b3aecf7 deps: update example dependencies 2014-05-29 23:39:52 -04:00
Douglas Christopher Wilson
8ab96ab80d mark res.send ETag as weak and reduce collisions 2014-05-29 23:14:21 -04:00
Douglas Christopher Wilson
1f2e00ef8d deps: should@~4.0.0 2014-05-29 22:53:59 -04:00
Douglas Christopher Wilson
b49453cf0d update send to 0.4.0
closes #2150
2014-05-29 22:32:03 -04:00
Douglas Christopher Wilson
faffcb889c update connect to 2.18.0 2014-05-29 22:21:53 -04:00
Douglas Christopher Wilson
311e83e591 4.3.2 2014-05-29 00:17:21 -04:00
Tiago Relvao
3c0ec59432 Include ETag in HEAD requests
backport of commit 3c7310ebcb
2014-05-28 22:31:00 -04:00
Douglas Christopher Wilson
d4a2843500 tests: add param test with encoded value
closes #2143
2014-05-28 22:30:24 -04:00
Douglas Christopher Wilson
fb2d918056 fix handling of errors from param callbacks
fixes #2149
2014-05-28 22:26:05 -04:00
Douglas Christopher Wilson
e7ad49bbbe tests: add accepts test with params 2014-05-28 00:30:29 -04:00
Douglas Christopher Wilson
ad9a414fae tests: add more acceptance tests 2014-05-28 00:24:24 -04:00
Douglas Christopher Wilson
c18c2a8e68 tests: exclude untestable lines in examples from coverage 2014-05-28 00:07:27 -04:00
Douglas Christopher Wilson
1d54868c12 update supertest to 0.13.0 2014-05-27 23:54:34 -04:00
Douglas Christopher Wilson
dfefea5e9d update example dependencies 2014-05-27 23:51:47 -04:00
Douglas Christopher Wilson
3fbab91231 Merge tag '3.8.1' 2014-05-27 23:49:47 -04:00
Douglas Christopher Wilson
f7e73e2da0 3.8.1 2014-05-27 23:43:13 -04:00
Douglas Christopher Wilson
867728b5ab update connect to 2.17.3 2014-05-27 23:02:27 -04:00
Douglas Christopher Wilson
87e02c30e7 4.3.1 2014-05-23 19:11:28 -04:00
Douglas Christopher Wilson
c3470c9c96 tests: add route ordering test 2014-05-23 18:46:00 -04:00
Douglas Christopher Wilson
7f049164b7 Revert "fix behavior of multiple app.VERB for the same path"
This reverts commit 31b2e2d7b4.

fixes #2133
2014-05-23 18:35:20 -04:00
Douglas Christopher Wilson
4e12a72873 4.3.0 2014-05-21 02:13:03 -04:00
Douglas Christopher Wilson
91e0c27252 update example dependencies 2014-05-21 02:11:31 -04:00
Douglas Christopher Wilson
db4a061ed6 Merge tag '3.8.0' 2014-05-21 02:08:04 -04:00
Douglas Christopher Wilson
f6bbeafd26 3.8.0 2014-05-21 01:52:14 -04:00
Douglas Christopher Wilson
f14e39d451 set proper charset in content-type for res.send
This will write strings to sockets with an explicit "utf8" encoding
(which is the default) and will override the charset in the
Content-Type so it properly relfects the encoding of the response.

closes #1631
closes #2092
2014-05-21 01:31:08 -04:00
Alberto Leal
084f5d891b Keep previous Content-Type for res.jsonp
backport of commit be997fd654
2014-05-21 01:04:29 -04:00
Douglas Christopher Wilson
b0f72e13d9 update connect to 2.17.1 2014-05-21 00:55:10 -04:00
Douglas Christopher Wilson
8d7d80ef9d tests: add more tests of web-service example 2014-05-21 00:08:17 -04:00
Douglas Christopher Wilson
cf5de082b5 tests: add more tests of cookies example 2014-05-21 00:08:06 -04:00
Douglas Christopher Wilson
1944451082 tests: add more tests of negotiation example 2014-05-20 23:50:58 -04:00
Douglas Christopher Wilson
602e5a8200 tests: add more tests of mvc example 2014-05-20 23:41:09 -04:00
Douglas Christopher Wilson
83b8b7acb7 tests: add more various tests 2014-05-20 23:25:51 -04:00
Douglas Christopher Wilson
e660f19507 tests: add req.acceptsLanguage tests 2014-05-20 23:13:29 -04:00
Douglas Christopher Wilson
ff412b927d tests: add req.acceptsEncoding tests 2014-05-20 23:08:01 -04:00
Douglas Christopher Wilson
392ef1eb06 tests: add more app.render tests 2014-05-20 22:57:00 -04:00
Douglas Christopher Wilson
dcecdc9be6 update mocha to 1.19.0 2014-05-20 21:22:02 -04:00
Douglas Christopher Wilson
ed69b68892 update connect to 2.17.0 2014-05-20 21:20:56 -04:00
Douglas Christopher Wilson
42b982e13c build: remove coveralls from devDependencies 2014-05-20 20:22:55 -04:00
Douglas Christopher Wilson
e7e2592357 tests: add more app.param tests 2014-05-20 10:58:39 -04:00
Douglas Christopher Wilson
739586f96a add req.baseUrl to access stripped path in routes
fixes #2078
2014-05-19 00:39:26 -04:00
Douglas Christopher Wilson
4c0f1f53d3 update example dependencies 2014-05-18 23:07:59 -04:00
Douglas Christopher Wilson
359f12791a build: prevent failure from coveralls 2014-05-18 23:05:34 -04:00
Douglas Christopher Wilson
9354ab62dd build: prevent failure from coveralls 2014-05-18 23:02:25 -04:00
Douglas Christopher Wilson
23ff74bb3f tests: flow control with after 2014-05-18 16:33:11 -04:00
Douglas Christopher Wilson
98d17e2293 invoke router.param() only when necessary
fixes #2121
2014-05-18 16:21:01 -04:00
Douglas Christopher Wilson
ababa6ae5b fix issue routing requests among sub routers
fixes #2121
2014-05-18 15:27:28 -04:00
Douglas Christopher Wilson
097cd0c242 Merge tag '3.7.0' 2014-05-18 11:21:30 -04:00
Douglas Christopher Wilson
b91cd66fc5 3.7.0 2014-05-18 10:40:13 -04:00
Douglas Christopher Wilson
787d630157 update should to 3.3.1 2014-05-18 10:38:42 -04:00
Douglas Christopher Wilson
1f938c560a tests: improve examples/auth tests 2014-05-18 01:54:05 -04:00
Douglas Christopher Wilson
a96924a555 build: remove lib-cov fork 2014-05-18 01:35:12 -04:00
Douglas Christopher Wilson
33dc6629ff update connect to 2.16.2 2014-05-18 01:30:44 -04:00
Douglas Christopher Wilson
1b3fb0af8c build: add coverage reporting 2014-05-18 01:25:15 -04:00
Douglas Christopher Wilson
12da523ff7 build: test coverage with istanbul 2014-05-18 01:23:15 -04:00
Douglas Christopher Wilson
0f49d80623 build: clean up package file 2014-05-18 01:16:38 -04:00
Douglas Christopher Wilson
1717516a78 build: improve platform portability 2014-05-18 01:14:45 -04:00
Jonathan Ong
328c6d3060 remove unnecessary test/support/http
backport of 643397ed21
2014-05-18 00:57:54 -04:00
Douglas Christopher Wilson
566720be15 improve proxy trust with ip address list
closes #2099
2014-05-17 20:02:20 -04:00
Douglas Christopher Wilson
65f13c3cc6 update connect to 2.16.1 2014-05-17 14:30:52 -04:00
Douglas Christopher Wilson
31b2e2d7b4 fix behavior of multiple app.VERB for the same path
fixes #2116
2014-05-16 15:09:42 -04:00
Douglas Christopher Wilson
8fe8d74056 update type-is to 1.2.0 2014-05-14 00:25:39 -04:00
Douglas Christopher Wilson
fcc4742056 build: ignore Contributing 2014-05-14 00:22:47 -04:00
Douglas Christopher Wilson
d98e2e7498 deprecation messages are bright red on TTYs 2014-05-13 17:06:45 -04:00
Roman Shtylman
d37ffa1149 add Contributing.md
Hopefully this will guide some users when posting new issues. Feel free
to close any issues which don't follow the guidelines.
2014-05-12 15:45:54 -04:00
111 changed files with 5083 additions and 1212 deletions

5
.gitignore vendored
View File

@@ -1,6 +1,5 @@
coverage.html
coverage/
.DS_Store
lib-cov
*.seed
*.log
*.csv
@@ -13,7 +12,5 @@ benchmarks/graphs
testing
node_modules/
testing
.coverage_data
cover_html
test.js
.idea

View File

@@ -1,5 +1,6 @@
.git*
benchmarks/
coverage/
docs/
examples/
support/
@@ -7,5 +8,4 @@ test/
testing.js
.DS_Store
.travis.yml
coverage.html
lib-cov
Contributing.md

View File

@@ -6,3 +6,5 @@ matrix:
allow_failures:
- node_js: "0.11"
fast_finish: true
script: "npm run-script test-travis"
after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls"

25
Contributing.md Normal file
View File

@@ -0,0 +1,25 @@
## Website Issues
Issues for the expressjs.com website go here https://github.com/visionmedia/expressjs.com
## PRs and Code contributions
* Tests must pass.
* Follow existing coding style.
* If you fix a bug, add a test.
## Issues which are questions
We will typically close any vague issues or questions that are specific to some app you are writing. Please double check the docs and other references before being trigger happy with posting a question issue.
Things that will help get your question issue looked at:
* Full and runnable JS code.
* Clear description of the problem or unexpected behavior.
* Clear description of the expected result.
* Steps you have taken to debug it yourself.
If you post a question and do not outline the above items or make it easy for us to understand and reproduce your issue, it will be closed.

View File

@@ -1,3 +1,288 @@
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
==================
* fix catching errors from top-level handlers
* use `vary` module for `res.vary`
* deps: debug@1.0.1
* deps: proxy-addr@1.0.1
* deps: send@0.4.2
- fix "event emitter leak" warnings
- deps: debug@1.0.1
- deps: finished@1.2.1
* deps: serve-static@1.2.2
- fix "event emitter leak" warnings
- deps: send@0.4.2
* deps: type-is@1.2.1
4.4.1 / 2014-06-02
==================
* deps: methods@1.0.1
* deps: send@0.4.1
- Send `max-age` in `Cache-Control` in correct format
* deps: serve-static@1.2.1
- use `escape-html` for escaping
- deps: send@0.4.1
4.4.0 / 2014-05-30
==================
* custom etag control with `app.set('etag', val)`
- `app.set('etag', function(body, encoding){ return '"etag"' })` custom etag generation
- `app.set('etag', 'weak')` weak tag
- `app.set('etag', 'strong')` strong etag
- `app.set('etag', false)` turn off
- `app.set('etag', true)` standard etag
* mark `res.send` ETag as weak and reduce collisions
* update accepts to 1.0.2
- Fix interpretation when header not in request
* update send to 0.4.0
- Calculate ETag with md5 for reduced collisions
- Ignore stream errors after request ends
- deps: debug@0.8.1
* update serve-static to 1.2.0
- Calculate ETag with md5 for reduced collisions
- Ignore stream errors after request ends
- deps: send@0.4.0
4.3.2 / 2014-05-28
==================
* fix handling of errors from `router.param()` callbacks
4.3.1 / 2014-05-23
==================
* revert "fix behavior of multiple `app.VERB` for the same path"
- this caused a regression in the order of route execution
4.3.0 / 2014-05-21
==================
* add `req.baseUrl` to access the path stripped from `req.url` in routes
* fix behavior of multiple `app.VERB` for the same path
* fix issue routing requests among sub routers
* invoke `router.param()` only when necessary instead of every match
* proper proxy trust with `app.set('trust proxy', trust)`
- `app.set('trust proxy', 1)` trust first hop
- `app.set('trust proxy', 'loopback')` trust loopback addresses
- `app.set('trust proxy', '10.0.0.1')` trust single IP
- `app.set('trust proxy', '10.0.0.1/16')` trust subnet
- `app.set('trust proxy', '10.0.0.1, 10.0.0.2')` trust list
- `app.set('trust proxy', false)` turn off
- `app.set('trust proxy', true)` trust everything
* set proper `charset` in `Content-Type` for `res.send`
* update type-is to 1.2.0
- support suffix matching
4.2.0 / 2014-05-11
==================
@@ -94,6 +379,323 @@
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route
- Router & Route - public API
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
===================
* deps: connect@2.19.5
- fix "event emitter leak" warnings
- deps: csurf@1.2.1
- deps: debug@1.0.1
- deps: serve-static@1.2.2
- deps: type-is@1.2.1
* deps: debug@1.0.1
* deps: send@0.4.2
- fix "event emitter leak" warnings
- deps: finished@1.2.1
- deps: debug@1.0.1
3.10.3 / 2014-06-05
===================
* use `vary` module for `res.vary`
* deps: connect@2.19.4
- deps: errorhandler@1.0.2
- deps: method-override@2.0.2
- deps: serve-favicon@2.0.1
* deps: debug@1.0.0
3.10.2 / 2014-06-03
===================
* deps: connect@2.19.3
- deps: compression@1.0.6
3.10.1 / 2014-06-03
===================
* deps: connect@2.19.2
- deps: compression@1.0.4
* deps: proxy-addr@1.0.1
3.10.0 / 2014-06-02
===================
* deps: connect@2.19.1
- deprecate `methodOverride()` -- use `method-override` npm module instead
- deps: body-parser@1.3.0
- deps: method-override@2.0.1
- deps: multiparty@3.2.8
- deps: response-time@2.0.0
- deps: serve-static@1.2.1
* deps: methods@1.0.1
* deps: send@0.4.1
- Send `max-age` in `Cache-Control` in correct format
3.9.0 / 2014-05-30
==================
* custom etag control with `app.set('etag', val)`
- `app.set('etag', function(body, encoding){ return '"etag"' })` custom etag generation
- `app.set('etag', 'weak')` weak tag
- `app.set('etag', 'strong')` strong etag
- `app.set('etag', false)` turn off
- `app.set('etag', true)` standard etag
* Include ETag in HEAD requests
* mark `res.send` ETag as weak and reduce collisions
* update connect to 2.18.0
- deps: compression@1.0.3
- deps: serve-index@1.1.0
- deps: serve-static@1.2.0
* update send to 0.4.0
- Calculate ETag with md5 for reduced collisions
- Ignore stream errors after request ends
- deps: debug@0.8.1
3.8.1 / 2014-05-27
==================
* update connect to 2.17.3
- deps: body-parser@1.2.2
- deps: express-session@1.2.1
- deps: method-override@1.0.2
3.8.0 / 2014-05-21
==================
* keep previous `Content-Type` for `res.jsonp`
* set proper `charset` in `Content-Type` for `res.send`
* update connect to 2.17.1
- fix `res.charset` appending charset when `content-type` has one
- deps: express-session@1.2.0
- deps: morgan@1.1.1
- deps: serve-index@1.0.3
3.7.0 / 2014-05-18
==================
* proper proxy trust with `app.set('trust proxy', trust)`
- `app.set('trust proxy', 1)` trust first hop
- `app.set('trust proxy', 'loopback')` trust loopback addresses
- `app.set('trust proxy', '10.0.0.1')` trust single IP
- `app.set('trust proxy', '10.0.0.1/16')` trust subnet
- `app.set('trust proxy', '10.0.0.1, 10.0.0.2')` trust list
- `app.set('trust proxy', false)` turn off
- `app.set('trust proxy', true)` trust everything
* update connect to 2.16.2
- deprecate `res.headerSent` -- use `res.headersSent`
- deprecate `res.on("header")` -- use on-headers module instead
- fix edge-case in `res.appendHeader` that would append in wrong order
- json: use body-parser
- urlencoded: use body-parser
- dep: bytes@1.0.0
- dep: cookie-parser@1.1.0
- dep: csurf@1.2.0
- dep: express-session@1.1.0
- dep: method-override@1.0.1
3.6.0 / 2014-05-09
==================

View File

@@ -1,34 +0,0 @@
MOCHA_OPTS= --check-leaks
REPORTER = dot
check: test
test: test-unit test-acceptance
test-unit:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--globals setImmediate,clearImmediate \
$(MOCHA_OPTS)
test-acceptance:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--bail \
test/acceptance/*.js
test-cov: lib-cov
@EXPRESS_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html
lib-cov:
@jscoverage lib lib-cov
bench:
@$(MAKE) -C benchmarks
clean:
rm -f coverage.html
rm -fr lib-cov
.PHONY: test test-unit test-acceptance bench clean

112
Readme.md
View File

@@ -1,45 +1,58 @@
[![express logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](http://expressjs.com/)
[![Express Logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](https://expressjs.com/)
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
[![Build Status](https://travis-ci.org/visionmedia/express.svg?branch=master)](https://travis-ci.org/visionmedia/express) [![Gittip](http://img.shields.io/gittip/visionmedia.svg)](https://www.gittip.com/visionmedia/) [![NPM version](https://badge.fury.io/js/express.svg)](http://badge.fury.io/js/express)
[![NPM Version](https://badge.fury.io/js/express.svg)](https://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/dougwilson.svg)](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/visionmedia/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/visionmedia/express/wiki/New-features-in-4.x).
## Installation
### Installation
$ npm install express
```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
@@ -58,51 +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/visionmedia/expressjs.com)]
* [Github Organization](https://github.com/expressjs) for Official Middleware & Modules
* [#express](https://webchat.freenode.net/?channels=express) on freenode IRC
* Visit the [Wiki](https://github.com/visionmedia/express/wiki)
* [Google Group](https://groups.google.com/group/express-js) for discussion
* [Русскоязычная документация](http://jsman.ru/express/)
* [한국어 문서](http://expressjs.kr) - [[website repo](https://github.com/Hanul/expressjs.kr)]
* Run express examples [online](https://runnable.com/express)
## Viewing Examples
Clone the Express repo, then install the dev dependencies to install all the example / test suite 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/visionmedia/express.git --depth 1
$ cd express
$ npm install
```
Then run whichever tests you want:
Then run whichever example you want:
$ node examples/content-negotiation
You can also view live examples here:
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:
$ make test
```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/visionmedia/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
@@ -78,7 +80,7 @@ function restrict(req, res, next) {
}
app.get('/', function(req, res){
res.redirect('login');
res.redirect('/login');
});
app.get('/restricted', restrict, function(req, res){
@@ -116,11 +118,12 @@ app.post('/login', function(req, res){
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.redirect('login');
res.redirect('/login');
}
});
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');

View File

@@ -24,5 +24,8 @@ app.get('/', function(req, res){
res.render('pets', { pets: pets });
});
app.listen(3000);
console.log('Express listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -37,7 +37,8 @@ function format(path) {
app.get('/users', format('./users'));
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('listening on port 3000');
console.log('Express started on port 3000');
}

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);
@@ -23,7 +19,8 @@ function count(req, res) {
res.send('viewed ' + n + ' times\n');
}
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express server listening on port 3000');
console.log('Express started on port 3000');
}

View File

@@ -9,8 +9,7 @@ var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
// custom log format
if ('test' != process.env.NODE_ENV)
app.use(logger(':method :url'));
if ('test' != process.env.NODE_ENV) app.use(logger(':method :url'));
// parses request cookies, populating
// req.cookies and req.signedCookies
@@ -18,8 +17,8 @@ if ('test' != process.env.NODE_ENV)
// for signing the cookies.
app.use(cookieParser('my secret here'));
// parses json, x-www-form-urlencoded, and multipart/form-data
app.use(bodyParser());
// parses x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
app.get('/', function(req, res){
if (req.cookies.remember) {
@@ -42,7 +41,8 @@ app.post('/', function(req, res){
res.redirect('back');
});
if (!module.parent){
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

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>');
});
@@ -19,25 +20,16 @@ app.get('/files/:file(*)', function(req, res, next){
var file = req.params.file;
var path = __dirname + '/files/' + file;
res.download(path);
});
// error handling middleware. Because it's
// below our routes, you will be able to
// "intercept" errors, otherwise Connect
// will respond with 500 "Internal Server Error".
app.use(function(err, req, res, next){
// special-case 404s,
// remember you could
// render a 404 template here
if (404 == err.status) {
res.download(path, function(err){
if (!err) return; // file sent
if (err && err.status !== 404) return next(err); // non-404 error
// file for download not found
res.statusCode = 404;
res.send('Cant find that file, sorry!');
} else {
next(err);
}
});
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');

View File

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

View File

@@ -43,7 +43,8 @@ app.get('/', function(req, res){
});
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express app started on port 3000');
console.log('Express started on port 3000');
}

View File

@@ -18,9 +18,7 @@ app.enable('verbose errors');
// disable them in production
// use $ NODE_ENV=production node examples/error-pages
if ('production' == app.settings.env) {
app.disable('verbose errors');
}
if ('production' == app.settings.env) app.disable('verbose errors');
silent || app.use(logger('dev'));
@@ -99,7 +97,8 @@ app.use(function(err, req, res, next){
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
silent || console.log('Express started on port 3000');
console.log('Express started on port 3000');
}

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){
@@ -40,6 +41,7 @@ app.get('/next', function(req, res, next){
// from app.get() etc
app.use(error);
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');

View File

@@ -57,5 +57,8 @@ app.get('/user', function(req, res){
res.render('page');
});
app.listen(3000);
console.log('app listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -6,5 +6,8 @@ app.get('/', function(req, res){
res.send('Hello World');
});
app.listen(3000);
console.log('Express started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -44,5 +44,8 @@ app.use(function(err, req, res, next) {
res.send(err.stack);
});
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -38,6 +38,7 @@ app.get('/fail', function(req, res){
res.render('missing', { title: 'Markdown Example' });
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');

View File

@@ -53,7 +53,8 @@ app.post('/', function(req, res, next){
form.parse(req);
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(4000);
console.log('Express started on port 3000');
console.log('Express started on port 4000');
}

View File

@@ -6,7 +6,7 @@ var db = require('../../db');
exports.before = function(req, res, next){
var pet = db.pets[req.params.pet_id];
if (!pet) return next(new Error('Pet not found'));
if (!pet) return next('route');
req.pet = pet;
next();
};

View File

@@ -1,7 +1,6 @@
link(rel='stylesheet', href='/style.css')
h1= pet.name
form(action='/pet/#{pet.id}', method='post')
input(type='hidden', name='_method', value='put')
form(action='/pet/#{pet.id}?_method=put', method='post')
label= 'Name: '
input(type='text', name='pet[name]', value=pet.name)
input(type='submit', value='Update')

View File

@@ -11,7 +11,7 @@ exports.create = function(req, res, next){
var id = req.params.user_id;
var user = db.users[id];
var body = req.body;
if (!user) return next(new Error('User not found'));
if (!user) return next('route');
var pet = { name: body.pet.name };
pet.id = db.pets.push(pet) - 1;
user.pets.push(pet);

View File

@@ -11,7 +11,7 @@ exports.before = function(req, res, next){
process.nextTick(function(){
req.user = db.users[id];
// cant find that user
if (!req.user) return next(new Error('User not found'));
if (!req.user) return next('route');
// found it, move on to the routes
next();
});

View File

@@ -1,7 +1,6 @@
link(rel='stylesheet', href='/style.css')
h1= user.name
form(action='/user/#{user.id}', method='post')
input(type='hidden', name='_method', value='put')
form(action='/user/#{user.id}?_method=put', method='post')
label= 'Name: '
input(type='text', name='user[name]', value='#{user.name}')
input(type='submit', value='Update')

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,14 +37,17 @@ if (!module.parent) app.use(logger('dev'));
app.use(express.static(__dirname + '/public'));
// session support
app.use(cookieParser('some secret here'));
app.use(session());
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'some secret here'
}));
// parse request bodies (req.body)
app.use(bodyParser());
app.use(bodyParser.urlencoded({ extended: true }));
// override methods (put, delete)
app.use(methodOverride());
// allow overriding methods in query (?_method=put)
app.use(methodOverride('_method'));
// expose the "messages" local variable when views are rendered
app.use(function(req, res, next){
@@ -73,16 +75,9 @@ app.use(function(req, res, next){
// load controllers
require('./lib/boot')(app, { verbose: !module.parent });
// assume "not found" in the error msgs
// is a 404. this is somewhat silly, but
// valid, you can do whatever you like, set
// properties, use instanceof etc.
app.use(function(err, req, res, next){
// treat as 404
if (~err.message.indexOf('not found')) return next();
// log it
console.error(err.stack);
if (!module.parent) console.error(err.stack);
// error page
res.status(500).render('5xx');
@@ -93,7 +88,8 @@ app.use(function(req, res, next){
res.status(404).render('404', { url: req.originalUrl });
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('\n listening on port 3000\n');
console.log('Express started on port 3000');
}

View File

@@ -13,6 +13,7 @@ module.exports = function(parent, options){
var name = obj.name || name;
var prefix = obj.prefix || '';
var app = express();
var handler;
var method;
var path;
@@ -20,16 +21,6 @@ module.exports = function(parent, options){
if (obj.engine) app.set('view engine', obj.engine);
app.set('views', __dirname + '/../controllers/' + name + '/views');
// before middleware support
if (obj.before) {
path = '/' + name + '/:' + name + '_id';
app.all(path, obj.before);
verbose && console.log(' ALL %s -> before', path);
path = '/' + name + '/:' + name + '_id/*';
app.all(path, obj.before);
verbose && console.log(' ALL %s -> before', path);
}
// generate routes based
// on the exported methods
for (var key in obj) {
@@ -62,15 +53,25 @@ module.exports = function(parent, options){
path = '/';
break;
default:
/* istanbul ignore next */
throw new Error('unrecognized route: ' + name + '.' + key);
}
// setup
handler = obj[key];
path = prefix + path;
app[method](path, obj[key]);
verbose && console.log(' %s %s -> %s', method.toUpperCase(), path, key);
// before middleware support
if (obj.before) {
app[method](path, obj.before, handler);
verbose && console.log(' %s %s -> before -> %s', method.toUpperCase(), path, key);
} else {
app[method](path, obj[key]);
verbose && console.log(' %s %s -> %s', method.toUpperCase(), path, key);
}
}
// mount the app
parent.use(app);
});
};
};

View File

@@ -49,5 +49,8 @@ app.get('/', function(req, res, next){
});
});
app.listen(3000);
console.log('listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -18,8 +18,8 @@ var users = [
// Convert :to and :from to integers
app.param(['to', 'from'], function(req, res, next, num, name){
req.params[name] = num = parseInt(num, 10);
if( isNaN(num) ){
req.params[name] = parseInt(num, 10);
if( isNaN(req.params[name]) ){
next(new Error('failed to parseInt '+num));
} else {
next();
@@ -63,7 +63,8 @@ app.get('/users/:from-:to', function(req, res, next){
res.send('users ' + names.slice(from, to).join(', '));
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}

View File

@@ -17,7 +17,10 @@ app.resource = function(path, obj) {
obj.range(req, res, a, b, format);
});
this.get(path + '/:id', obj.show);
this.delete(path + '/:id', obj.destroy);
this.delete(path + '/:id', function(req, res){
var id = parseInt(req.params.id, 10);
obj.destroy(req, res, id);
});
};
// Fake records
@@ -40,8 +43,7 @@ var User = {
show: function(req, res){
res.send(users[req.params.id] || { error: 'Cannot find user' });
},
destroy: function(req, res){
var id = req.params.id;
destroy: function(req, res, id){
var destroyed = id in users;
delete users[id];
res.send(destroyed ? 'destroyed' : 'Cannot find user');
@@ -84,7 +86,8 @@ app.get('/', function(req, res){
].join('\n'));
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}

View File

@@ -65,4 +65,8 @@ app.map({
}
});
app.listen(3000);
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -81,5 +81,8 @@ app.delete('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('Deleted user ' + req.user.name);
});
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -16,8 +16,9 @@ var user = require('./user');
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');
app.use(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
@@ -37,5 +38,8 @@ app.put('/user/:id/edit', user.update);
app.get('/posts', post.list);
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -3,8 +3,7 @@ extends ../layout
block content
h1 Editing #{user.name}
#user
form(method="post")
input(type="hidden", value="put", name="_method")
form(action="?_method=put", method="post")
p Name:
input(type="text", value= user.name, name="user[name]")
p Email:

View File

@@ -47,15 +47,18 @@ app.get('/search/:query?', function(req, res){
});
/**
* GET client javascript. Here we use sendfile()
* GET client javascript. Here we use sendFile()
* because serving __dirname with the static() middleware
* would also mean serving our server "index.js" and the "search.jade"
* template.
*/
app.get('/client.js', function(req, res){
res.sendfile(__dirname + '/client.js');
res.sendFile(__dirname + '/client.js');
});
app.listen(3000);
console.log('app listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

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 = '';
@@ -27,5 +25,8 @@ app.get('/', function(req, res){
res.send(body + '<p>viewed <strong>' + req.session.views + '</strong> times.</p>');
});
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

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

@@ -18,7 +18,7 @@ edit /etc/hosts:
var main = express();
main.use(logger('dev'));
if (!module.parent) main.use(logger('dev'));
main.get('/', function(req, res){
res.send('Hello from main app!');
@@ -32,17 +32,20 @@ main.get('/:sub', function(req, res){
var redirect = express();
redirect.all('*', function(req, res){
console.log(req.subdomains);
res.redirect('http://example.com:3000/' + req.subdomains[0]);
redirect.use(function(req, res){
if (!module.parent) console.log(req.vhost);
res.redirect('http://example.com:3000/' + req.vhost[0]);
});
// Vhost app
var app = express();
var app = module.exports = express();
app.use(vhost('*.example.com', redirect)); // Serves all subdomains via Redirect app
app.use(vhost('example.com', main)); // Serves top level domain via Main server app
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -40,6 +40,7 @@ app.get('/Readme.md', function(req, res){
res.render('Readme.md');
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');

View File

@@ -145,5 +145,8 @@ app.all('/api/*', function(req, res, next){
*/
app.listen(3000);
console.log('Application listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -93,17 +93,20 @@ app.get('/api/user/:name/repos', function(req, res, next){
app.use(function(err, req, res, next){
// whatever you want here, feel free to populate
// properties on `err` to treat it differently in here.
res.send(err.status || 500, { error: err.message });
res.status(err.status || 500);
res.send({ error: err.message });
});
// our custom JSON 404 middleware. Since it's placed last
// it will be the last middleware called, if all others
// invoke next() and do not respond.
app.use(function(req, res){
res.send(404, { error: "Lame, can't find that" });
res.status(404);
res.send({ error: "Lame, can't find that" });
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express server listening on port 3000');
console.log('Express started on port 3000');
}

View File

@@ -1,4 +1,2 @@
module.exports = process.env.EXPRESS_COV
? require('./lib-cov/express')
: require('./lib/express');
module.exports = require('./lib/express');

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');
@@ -11,7 +12,12 @@ var query = require('./middleware/query');
var debug = require('debug')('express:application');
var View = require('./view');
var http = require('http');
var deprecate = require('./utils').deprecate;
var compileETag = require('./utils').compileETag;
var compileQueryParser = require('./utils').compileQueryParser;
var compileTrust = require('./utils').compileTrust;
var deprecate = require('depd')('express');
var resolve = require('path').resolve;
var slice = Array.prototype.slice;
/**
* Application prototype.
@@ -45,10 +51,12 @@ app.init = function(){
app.defaultConfiguration = function(){
// default settings
this.enable('x-powered-by');
this.enable('etag');
this.set('etag', 'weak');
var env = process.env.NODE_ENV || 'development';
this.set('env', env);
this.set('query parser', 'extended');
this.set('subdomain offset', 2);
this.set('trust proxy', false);
debug('booting in %s mode', env);
@@ -71,7 +79,7 @@ app.defaultConfiguration = function(){
// default configuration
this.set('view', View);
this.set('views', process.cwd() + '/views');
this.set('views', resolve('views'));
this.set('jsonp callback name', 'callback');
if (env === 'production') {
@@ -100,7 +108,7 @@ app.lazyrouter = function() {
strict: this.enabled('strict routing')
});
this._router.use(query());
this._router.use(query(this.get('query parser fn')));
this._router.use(middleware.init(this));
}
};
@@ -115,45 +123,22 @@ app.lazyrouter = function() {
*/
app.handle = function(req, res, done) {
var env = this.get('env');
var router = this._router;
this._router.handle(req, res, function(err) {
if (done) {
return done(err);
}
// unhandled error
if (err) {
// default to 500
if (res.statusCode < 400) res.statusCode = 500;
debug('default %s', res.statusCode);
// respect err.status
if (err.status) res.statusCode = err.status;
// production gets a basic error message
var msg = 'production' == env
? http.STATUS_CODES[res.statusCode]
: err.stack || err.toString();
msg = escapeHtml(msg);
// log to stderr in a non-test env
if ('test' != env) console.error(err.stack || err.toString());
if (res.headersSent) return req.socket.destroy();
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', Buffer.byteLength(msg));
if ('HEAD' == req.method) return res.end();
res.end(msg);
return;
}
// 404
debug('default 404');
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
if ('HEAD' == req.method) return res.end();
res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
// final handler
done = done || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
// no routes
if (!router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);
};
/**
@@ -163,43 +148,53 @@ app.handle = function(req, res, done) {
* If the _fn_ parameter is an express app, then it will be
* mounted at the _route_ specified.
*
* @param {String|Function|Server} route
* @param {Function|Server} fn
* @return {app} for chaining
* @api public
*/
app.use = function(route, fn){
var mount_app;
app.use = function use(fn) {
var offset = 0;
var path = '/';
var self = this;
// default route to '/'
if ('string' != typeof route) fn = route, route = '/';
// default path to '/'
if (typeof fn !== 'function') {
offset = 1;
path = fn;
}
// express app
if (fn.handle && fn.set) mount_app = fn;
var fns = flatten(slice.call(arguments, offset));
// 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) {
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;
};
@@ -303,12 +298,31 @@ app.param = function(name, fn){
*/
app.set = function(setting, val){
if (1 == arguments.length) {
if (arguments.length === 1) {
// app.get(setting)
return this.settings[setting];
} else {
this.settings[setting] = val;
return this;
}
// set value
this.settings[setting] = val;
// trigger matched settings
switch (setting) {
case 'etag':
debug('compile etag %s', val);
this.set('etag fn', compileETag(val));
break;
case 'query parser':
debug('compile query parser %s', val);
this.set('query parser fn', compileQueryParser(val));
break;
case 'trust proxy':
debug('compile trust proxy %s', val);
this.set('trust proxy fn', compileTrust(val));
break;
}
return this;
};
/**
@@ -404,7 +418,7 @@ methods.forEach(function(method){
this.lazyrouter();
var route = this._router.route(path);
route[method].apply(route, [].slice.call(arguments, 1));
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
@@ -423,7 +437,7 @@ app.all = function(path){
this.lazyrouter();
var route = this._router.route(path);
var args = [].slice.call(arguments, 1);
var args = slice.call(arguments, 1);
methods.forEach(function(method){
route[method].apply(route, args);
});
@@ -433,7 +447,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`
@@ -531,3 +545,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,11 +3,13 @@
*/
var accepts = require('accepts');
var deprecate = require('depd')('express');
var typeis = require('type-is');
var http = require('http');
var fresh = require('fresh');
var parseRange = require('range-parser');
var parse = require('parseurl');
var proxyaddr = require('proxy-addr');
/**
* Request prototype.
@@ -105,53 +107,55 @@ req.accepts = function(){
};
/**
* Check if the given `encoding` is accepted.
* Check if the given `encoding`s are accepted.
*
* @param {String} encoding
* @param {String} ...encoding
* @return {Boolean}
* @api public
*/
req.acceptsEncoding = // backwards compatibility
req.acceptsEncodings = function(){
var accept = accepts(this);
return accept.encodings.apply(accept, arguments);
};
req.acceptsEncoding = deprecate.function(req.acceptsEncodings,
'req.acceptsEncoding: Use acceptsEncodings instead');
/**
* To do: update docs.
*
* Check if the given `charset` is acceptable,
* Check if the given `charset`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
* @param {String} charset
* @param {String} ...charset
* @return {Boolean}
* @api public
*/
req.acceptsCharset = // backwards compatibility
req.acceptsCharsets = function(){
var accept = accepts(this);
return accept.charsets.apply(accept, arguments);
};
req.acceptsCharset = deprecate.function(req.acceptsCharsets,
'req.acceptsCharset: Use acceptsCharsets instead');
/**
* To do: update docs.
*
* Check if the given `lang` is acceptable,
* Check if the given `lang`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
* @param {String} lang
* @param {String} ...lang
* @return {Boolean}
* @api public
*/
req.acceptsLanguage = // backwards compatibility
req.acceptsLanguages = function(){
var accept = accepts(this);
return accept.languages.apply(accept, arguments);
};
req.acceptsLanguage = deprecate.function(req.acceptsLanguages,
'req.acceptsLanguage: Use acceptsLanguages instead');
/**
* Parse Range header field,
* capping to the given `size`.
@@ -239,20 +243,30 @@ req.is = function(types){
/**
* Return the protocol string "http" or "https"
* when requested with TLS. When the "trust proxy"
* setting is enabled the "X-Forwarded-Proto" header
* field will be trusted. If you're running behind
* a reverse proxy that supplies https for you this
* may be enabled.
* setting trusts the socket address, the
* "X-Forwarded-Proto" header field will be trusted
* and used if present.
*
* If you're running behind a reverse proxy that
* supplies https for you this may be enabled.
*
* @return {String}
* @api public
*/
req.__defineGetter__('protocol', function(){
var trustProxy = this.app.get('trust proxy');
if (this.connection.encrypted) return 'https';
if (!trustProxy) return 'http';
var proto = this.get('X-Forwarded-Proto') || 'http';
defineGetter(req, 'protocol', function protocol(){
var proto = this.connection.encrypted
? 'https'
: 'http';
var trust = this.app.get('trust proxy fn');
if (!trust(this.connection.remoteAddress)) {
return proto;
}
// Note: X-Forwarded-Proto is normally only ever a
// single value, but this is to be safe.
proto = this.get('X-Forwarded-Proto') || proto;
return proto.split(/\s*,\s*/)[0];
});
@@ -265,41 +279,41 @@ req.__defineGetter__('protocol', function(){
* @api public
*/
req.__defineGetter__('secure', function(){
defineGetter(req, 'secure', function secure(){
return 'https' == this.protocol;
});
/**
* Return the remote address, or when
* "trust proxy" is `true` return
* the upstream addr.
* Return the remote address from the trusted proxy.
*
* The is the remote address on the socket unless
* "trust proxy" is set.
*
* @return {String}
* @api public
*/
req.__defineGetter__('ip', function(){
return this.ips[0] || this.connection.remoteAddress;
defineGetter(req, 'ip', function ip(){
var trust = this.app.get('trust proxy fn');
return proxyaddr(this, trust);
});
/**
* When "trust proxy" is `true`, parse
* the "X-Forwarded-For" ip address list.
* When "trust proxy" is set, trusted proxy addresses + client.
*
* For example if the value were "client, proxy1, proxy2"
* you would receive the array `["client", "proxy1", "proxy2"]`
* where "proxy2" is the furthest down-stream.
* where "proxy2" is the furthest down-stream and "proxy1" and
* "proxy2" were trusted.
*
* @return {Array}
* @api public
*/
req.__defineGetter__('ips', function(){
var trustProxy = this.app.get('trust proxy');
var val = this.get('X-Forwarded-For');
return trustProxy && val
? val.split(/ *, */)
: [];
defineGetter(req, 'ips', function ips() {
var trust = this.app.get('trust proxy fn');
var addrs = proxyaddr.all(this, trust);
return addrs.slice(1).reverse();
});
/**
@@ -317,9 +331,9 @@ req.__defineGetter__('ips', function(){
* @api public
*/
req.__defineGetter__('subdomains', function(){
defineGetter(req, 'subdomains', function subdomains() {
var offset = this.app.get('subdomain offset');
return (this.host || '')
return (this.hostname || '')
.split('.')
.reverse()
.slice(offset);
@@ -332,31 +346,48 @@ req.__defineGetter__('subdomains', function(){
* @api public
*/
req.__defineGetter__('path', function(){
defineGetter(req, 'path', function path() {
return parse(this).pathname;
});
/**
* Parse the "Host" header field hostname.
* Parse the "Host" header field to a hostname.
*
* When the "trust proxy" setting trusts the socket
* address, the "X-Forwarded-Host" header field will
* be trusted.
*
* @return {String}
* @api public
*/
req.__defineGetter__('host', function(){
var trustProxy = this.app.get('trust proxy');
var host = trustProxy && this.get('X-Forwarded-Host');
host = host || this.get('Host');
defineGetter(req, 'hostname', function hostname(){
var trust = this.app.get('trust proxy fn');
var host = this.get('X-Forwarded-Host');
if (!host || !trust(this.connection.remoteAddress)) {
host = this.get('Host');
}
if (!host) return;
// IPv6 literal support
var offset = host[0] === '['
? host.indexOf(']') + 1
: 0;
var index = host.indexOf(':', offset);
return ~index
? host.substring(0, index)
: host;
});
// TODO: change req.host to return host in next major
defineGetter(req, 'host', deprecate.function(function host(){
return this.hostname;
}, 'req.host: Use req.hostname instead'));
/**
* Check if the request is fresh, aka
* Last-Modified and/or the ETag
@@ -366,7 +397,7 @@ req.__defineGetter__('host', function(){
* @api public
*/
req.__defineGetter__('fresh', function(){
defineGetter(req, 'fresh', function(){
var method = this.method;
var s = this.res.statusCode;
@@ -390,7 +421,7 @@ req.__defineGetter__('fresh', function(){
* @api public
*/
req.__defineGetter__('stale', function(){
defineGetter(req, 'stale', function stale(){
return !this.fresh;
});
@@ -401,7 +432,23 @@ req.__defineGetter__('stale', function(){
* @api public
*/
req.__defineGetter__('xhr', function(){
defineGetter(req, 'xhr', function xhr(){
var val = this.get('X-Requested-With') || '';
return 'xmlhttprequest' == val.toLowerCase();
});
/**
* Helper function for creating a getter on an object.
*
* @param {Object} obj
* @param {String} name
* @param {Function} getter
* @api private
*/
function defineGetter(obj, name, getter) {
Object.defineProperty(obj, name, {
configurable: true,
enumerable: true,
get: getter
});
};

View File

@@ -2,22 +2,25 @@
* Module dependencies.
*/
var deprecate = require('depd')('express');
var escapeHtml = require('escape-html');
var http = require('http');
var isAbsolute = require('./utils').isAbsolute;
var path = require('path');
var mixin = require('utils-merge');
var escapeHtml = require('escape-html');
var sign = require('cookie-signature').sign;
var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
var setCharset = require('./utils').setCharset;
var contentDisposition = require('./utils').contentDisposition;
var deprecate = require('./utils').deprecate;
var etag = require('./utils').etag;
var statusCodes = http.STATUS_CODES;
var cookie = require('cookie');
var send = require('send');
var basename = path.basename;
var extname = path.extname;
var mime = send.mime;
var resolve = path.resolve;
var vary = require('vary');
/**
* Response prototype.
@@ -71,69 +74,100 @@ res.links = function(links){
* res.send(new Buffer('wahoo'));
* res.send({ some: 'json' });
* res.send('<p>some html</p>');
* res.send(404, 'Sorry, cant find that');
* res.send(404);
*
* @param {Mixed} body or status
* @param {Mixed} body
* @return {ServerResponse}
* @param {string|number|boolean|object|Buffer} body
* @api public
*/
res.send = function(body){
var req = this.req;
var head = 'HEAD' == req.method;
res.send = function send(body) {
var chunk = body;
var encoding;
var len;
var req = this.req;
var type;
// settings
var app = this.app;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.send(body, status) backwards compat
if ('number' != typeof body && 'number' == typeof arguments[1]) {
if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
deprecate('res.send(body, status): Use res.status(status).send(body) instead');
this.statusCode = arguments[1];
} else {
this.statusCode = body;
body = arguments[1];
deprecate('res.send(status, body): Use res.status(status).send(body) instead');
this.statusCode = arguments[0];
chunk = arguments[1];
}
}
switch (typeof body) {
// response status
case 'number':
this.get('Content-Type') || this.type('txt');
this.statusCode = body;
body = http.STATUS_CODES[body];
break;
// disambiguate res.send(status) and res.send(status, num)
if (typeof chunk === 'number' && arguments.length === 1) {
// res.send(status) will set status message as text string
if (!this.get('Content-Type')) {
this.type('txt');
}
deprecate('res.send(status): Use res.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;
}
// populate Content-Length
if (undefined !== body && !this.get('Content-Length')) {
this.set('Content-Length', len = Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body));
// write strings in utf-8
if (typeof chunk === 'string') {
encoding = 'utf8';
type = this.get('Content-Type');
// reflect this in content-type
if (typeof type === 'string') {
this.set('Content-Type', setCharset(type, 'utf-8'));
}
}
// populate Content-Length
if (chunk !== undefined) {
if (!Buffer.isBuffer(chunk)) {
// convert chunk to Buffer; saves later double conversions
chunk = new Buffer(chunk, encoding);
encoding = undefined;
}
len = chunk.length;
this.set('Content-Length', len);
}
// method check
var isHead = req.method === 'HEAD';
// ETag support
// TODO: W/ support
if (app.settings.etag && len && ('GET' == req.method || 'HEAD' == req.method)) {
if (!this.get('ETag')) {
this.set('ETag', etag(body));
if (len !== undefined && (isHead || req.method === 'GET')) {
var etag = app.get('etag fn');
if (etag && !this.get('ETag')) {
etag = etag(chunk, encoding);
etag && this.set('ETag', etag);
}
}
@@ -145,11 +179,17 @@ res.send = function(body){
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
this.removeHeader('Transfer-Encoding');
body = '';
chunk = '';
}
// skip body for HEAD
if (isHead) {
this.end();
}
// respond
this.end(head ? null : body);
this.end(chunk, encoding);
return this;
};
@@ -160,27 +200,24 @@ res.send = function(body){
*
* res.json(null);
* res.json({ user: 'tj' });
* res.json(500, 'oh noes!');
* res.json(404, 'I dont have that');
*
* @param {Mixed} obj or status
* @param {Mixed} obj
* @return {ServerResponse}
* @param {string|number|boolean|object} obj
* @api public
*/
res.json = function(obj){
res.json = function json(obj) {
var val = obj;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
if (typeof arguments[1] === 'number') {
deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
this.statusCode = arguments[1];
return 'number' === typeof obj
? jsonNumDeprecated.call(this, obj)
: jsonDeprecated.call(this, obj);
} else {
this.statusCode = obj;
obj = arguments[1];
deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
this.statusCode = arguments[0];
val = arguments[1];
}
}
@@ -188,20 +225,16 @@ res.json = function(obj){
var app = this.app;
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var body = JSON.stringify(obj, replacer, spaces);
var body = JSON.stringify(val, replacer, spaces);
// content-type
this.get('Content-Type') || this.set('Content-Type', 'application/json');
if (!this.get('Content-Type')) {
this.set('Content-Type', 'application/json');
}
return this.send(body);
};
var jsonDeprecated = deprecate(res.json,
'res.json(obj, status): Use res.json(status, obj) instead');
var jsonNumDeprecated = deprecate(res.json,
'res.json(num, status): Use res.status(status).json(num) instead');
/**
* Send JSON response with JSONP callback support.
*
@@ -209,27 +242,24 @@ var jsonNumDeprecated = deprecate(res.json,
*
* res.jsonp(null);
* res.jsonp({ user: 'tj' });
* res.jsonp(500, 'oh noes!');
* res.jsonp(404, 'I dont have that');
*
* @param {Mixed} obj or status
* @param {Mixed} obj
* @return {ServerResponse}
* @param {string|number|boolean|object} obj
* @api public
*/
res.jsonp = function(obj){
res.jsonp = function jsonp(obj) {
var val = obj;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
if (typeof arguments[1] === 'number') {
deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
this.statusCode = arguments[1];
return 'number' === typeof obj
? jsonpNumDeprecated.call(this, obj)
: jsonpDeprecated.call(this, obj);
} else {
this.statusCode = obj;
obj = arguments[1];
deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
this.statusCode = arguments[0];
val = arguments[1];
}
}
@@ -237,13 +267,14 @@ res.jsonp = function(obj){
var app = this.app;
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var body = JSON.stringify(obj, replacer, spaces)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
var body = JSON.stringify(val, replacer, spaces);
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
this.get('Content-Type') || this.set('Content-Type', 'application/json');
if (!this.get('Content-Type')) {
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'application/json');
}
// fixup callback
if (Array.isArray(callback)) {
@@ -251,21 +282,27 @@ 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');
var jsonpNumDeprecated = deprecate(res.json,
'res.jsonp(num, status): Use res.status(status).jsonp(num) instead');
/**
* Transfer the file at the given `path`.
*
@@ -277,9 +314,127 @@ 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 done;
var req = this.req;
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');
}
// socket errors
req.socket.on('error', onerror);
// errors
function onerror(err) {
if (done) return;
done = true;
// clean up
cleanup();
// callback available
if (fn) return fn(err);
// delegate
next(err);
}
// streaming
function onstream(stream) {
if (done) return;
cleanup();
if (fn) stream.on('end', fn);
}
// cleanup
function cleanup() {
req.socket.removeListener('error', onerror);
}
// transfer
var pathname = encodeURI(path);
var file = send(req, pathname, options);
file.on('error', onerror);
file.on('directory', next);
file.on('stream', onstream);
if (options.headers) {
// set headers on successful transfer
file.on('headers', function headers(res) {
var obj = options.headers;
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
res.setHeader(k, obj[k]);
}
});
}
// pipe
file.pipe(this);
this.on('finish', cleanup);
};
/**
* Transfer the file at the given `path`.
*
* Automatically sets the _Content-Type_ response header field.
* The callback `fn(err)` is invoked when the transfer is complete
* or when an error occurs. Be sure to check `res.sentHeader`
* if you wish to attempt responding, as the header and some data
* may have already been transferred.
*
* Options:
*
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
* - `root` root directory for relative filenames
* - `headers` object of headers to serve with file
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
*
* Other options are passed along to `send`.
*
@@ -303,9 +458,6 @@ var jsonpNumDeprecated = deprecate(res.json,
* });
* });
*
* @param {String} path
* @param {Object|Function} options or fn
* @param {Function} fn
* @api public
*/
@@ -333,14 +485,10 @@ res.sendfile = function(path, options, fn){
// 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);
}
@@ -357,18 +505,33 @@ res.sendfile = function(path, options, fn){
req.socket.removeListener('error', error);
}
// Back-compat
options.maxage = options.maxage || options.maxAge || 0;
// transfer
var file = send(req, path, options);
file.on('error', error);
file.on('directory', next);
file.on('stream', stream);
if (options.headers) {
// set headers on successful transfer
file.on('headers', function headers(res) {
var obj = options.headers;
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
res.setHeader(k, obj[k]);
}
});
}
// pipe
file.pipe(this);
this.on('finish', cleanup);
};
res.sendfile = deprecate.function(res.sendfile,
'res.sendfile: Use res.sendFile instead');
/**
* Transfer the file at the given `path` as an attachment.
*
@@ -379,22 +542,27 @@ res.sendfile = function(path, options, fn){
*
* This method uses `res.sendfile()`.
*
* @param {String} path
* @param {String|Function} filename or fn
* @param {Function} fn
* @api public
*/
res.download = function(path, filename, fn){
res.download = function download(path, filename, fn) {
// support function as second arg
if ('function' == typeof filename) {
if (typeof filename === 'function') {
fn = filename;
filename = null;
}
filename = filename || path;
this.set('Content-Disposition', contentDisposition(filename));
return this.sendfile(path, fn);
// set Content-Disposition when file is sent
var headers = {
'Content-Disposition': contentDisposition(filename)
};
// Resolve the full path for sendFile
var fullPath = resolve(path);
return this.sendFile(fullPath, { headers: headers }, fn);
};
/**
@@ -538,8 +706,8 @@ res.attachment = function(filename){
*/
res.set =
res.header = function(field, val){
if (2 == arguments.length) {
res.header = function header(field, val) {
if (arguments.length === 2) {
if (Array.isArray(val)) val = val.map(String);
else val = String(val);
if ('content-type' == field.toLowerCase() && !/;\s*charset\s*=/.test(val)) {
@@ -572,7 +740,7 @@ res.get = function(field){
*
* @param {String} name
* @param {Object} options
* @param {ServerResponse} for chaining
* @return {ServerResponse} for chaining
* @api public
*/
@@ -603,6 +771,7 @@ res.clearCookie = function(name, options){
* @param {String} name
* @param {String|Object} val
* @param {Options} options
* @return {ServerResponse} for chaining
* @api public
*/
@@ -648,6 +817,7 @@ res.cookie = function(name, val, options){
* res.location('../login');
*
* @param {String} url
* @return {ServerResponse} for chaining
* @api public
*/
@@ -675,32 +845,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({
@@ -721,7 +889,12 @@ res.redirect = function(url){
// Respond
this.statusCode = status;
this.set('Content-Length', Buffer.byteLength(body));
this.end(head ? null : body);
if (this.req.method === 'HEAD') {
this.end();
}
this.end(body);
};
/**
@@ -729,36 +902,19 @@ res.redirect = function(url){
* this call is simply ignored.
*
* @param {Array|String} field
* @param {ServerResponse} for chaining
* @return {ServerResponse} for chaining
* @api public
*/
res.vary = function(field){
var self = this;
// nothing
if (!field) return this;
// array
if (Array.isArray(field)) {
field.forEach(function(field){
self.vary(field);
});
return;
}
var vary = this.get('Vary');
// append
if (vary) {
vary = vary.split(/ *, */);
if (!~vary.indexOf(field)) vary.push(field);
this.set('Vary', vary.join(', '));
// checks for back-compat
if (!field || (Array.isArray(field) && !field.length)) {
deprecate('res.vary(): Provide a field name');
return this;
}
// set
this.set('Vary', field);
vary(this, field);
return this;
};
@@ -772,9 +928,6 @@ res.vary = function(field){
* - `cache` boolean hinting to the engine it should cache
* - `filename` filename of the view being rendered
*
* @param {String} view
* @param {Object|Function} options or callback function
* @param {Function} fn
* @api public
*/

View File

@@ -5,9 +5,12 @@
var Route = require('./route');
var Layer = require('./layer');
var methods = require('methods');
var mixin = require('utils-merge');
var debug = require('debug')('express:router');
var parseUrl = require('parseurl');
var slice = Array.prototype.slice;
var toString = Object.prototype.toString;
var utils = require('../utils');
/**
* Initialize a new `Router` with the given `options`.
@@ -30,6 +33,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 +117,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('://');
@@ -122,6 +124,7 @@ proto.handle = function(req, res, done) {
var idx = 0;
var removed = '';
var slashAdded = false;
var paramcalled = {};
// store options for OPTIONS request
// only used if OPTIONS request
@@ -130,16 +133,16 @@ proto.handle = function(req, res, done) {
// middleware and routes
var stack = self.stack;
// request-level next
var parent = req.next;
done = wrap(done, function(old, err) {
req.next = parent;
old(err);
});
// manage inter-router variables
var parentParams = req.params;
var parentUrl = req.baseUrl || '';
done = restore(done, req, 'baseUrl', 'next', 'params');
// setup next layer
req.next = next;
// for options requests, respond with a default if nothing else responds
if (method === 'options') {
if (req.method === 'OPTIONS') {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
@@ -148,32 +151,38 @@ proto.handle = function(req, res, done) {
});
}
// setup basic req values
req.baseUrl = parentUrl;
req.originalUrl = req.originalUrl || req.url;
next();
function next(err) {
if (err === 'route') {
err = undefined;
}
var layerError = err === 'route'
? null
: err;
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
req.url = protohost + removed + req.url.substr(protohost.length);
req.originalUrl = req.originalUrl || req.url;
removed = '';
if (removed.length !== 0) {
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
try {
var path = parseUrl(req).pathname;
if (undefined == path) path = '/';
if (!layer) {
return done(layerError);
}
if (!layer.match(path)) return next(err);
self.match_layer(layer, req, res, function (err, path) {
if (err || path === undefined) {
return next(layerError || err);
}
// route object and not middleware
var route = layer.route;
@@ -181,88 +190,115 @@ proto.handle = function(req, res, done) {
// if final route, then we support options
if (route) {
// we don't run any routes with error first
if (err) {
return next(err);
if (layerError) {
return next(layerError);
}
req.route = route;
var method = req.method;
var has_method = route._handles_method(method);
// we can now dispatch to the route
if (method === 'options' && !route.methods['options']) {
// build up automatic options response
if (!has_method && method === 'OPTIONS') {
options.push.apply(options, route._options());
}
// don't even bother
if (!has_method && method !== 'HEAD') {
return next();
}
// we can now dispatch to the route
req.route = route;
}
req.params = layer.params;
// Capture one-time layer values
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
// this should be done for the layer
return self.process_params(layer, req, res, function(err) {
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(err);
return next(layerError || err);
}
if (route) {
return layer.handle(req, res, next);
return layer.handle_request(req, res, next);
}
trim_prefix();
trim_prefix(layer, layerError, layerPath, path);
});
});
}
} catch (err) {
next(err);
}
function trim_prefix(layer, layerError, layerPath, path) {
var c = path[layerPath.length];
if (c && '/' !== c && '.' !== c) return next(layerError);
function trim_prefix() {
var c = path[layer.path.length];
if (c && '/' != c && '.' != c) return next(err);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', removed, req.url);
removed = layer.path;
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
if (layerPath.length !== 0) {
debug('trim prefix (%s) from url %s', layerPath, req.url);
removed = layerPath;
req.url = protohost + req.url.substr(protohost.length + removed.length);
// Ensure leading slash
if (!fqdn && '/' != req.url[0]) {
if (!fqdn && req.url[0] !== '/') {
req.url = '/' + req.url;
slashAdded = true;
}
debug('%s %s : %s', layer.handle.name || 'anonymous', layer.path, req.originalUrl);
var arity = layer.handle.length;
if (err) {
if (arity === 4) {
layer.handle(err, req, res, next);
} else {
next(err);
}
} else if (arity < 4) {
layer.handle(req, res, next);
} else {
next(err);
}
// Setup base URL (no trailing slash)
req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
? removed.substring(0, removed.length - 1)
: removed);
}
}
function wrap(old, fn) {
return function () {
var args = [old].concat(slice.call(arguments));
fn.apply(this, args);
};
debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
if (layerError) {
layer.handle_error(layerError, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
/**
* Process any parameters for the route.
* Match request to a layer.
*
* @api private
*/
proto.process_params = function(route, req, res, done) {
proto.match_layer = function match_layer(layer, req, res, done) {
var error = null;
var path;
try {
path = parseUrl(req).pathname;
if (!layer.match(path)) {
path = undefined;
}
} catch (err) {
error = err;
}
done(error, path);
};
/**
* Process any parameters for the layer.
*
* @api private
*/
proto.process_params = function(layer, called, req, res, done) {
var params = this.params;
// captured parameters from the route, keys and values
var keys = route.keys;
// captured parameters from the layer, keys and values
var keys = layer.keys;
// fast track
if (!keys || keys.length === 0) {
@@ -270,10 +306,12 @@ proto.process_params = function(route, req, res, done) {
}
var i = 0;
var name;
var paramIndex = 0;
var key;
var paramVal;
var paramCallbacks;
var paramCalled;
// process params in order
// param callbacks can be async
@@ -288,27 +326,59 @@ proto.process_params = function(route, req, res, done) {
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
try {
if (paramCallbacks && undefined !== paramVal) {
return paramCallback();
} else if (key) {
return param();
}
} catch (err) {
return done(err);
if (!key) {
return done();
}
done();
name = key.name;
paramVal = req.params[name];
paramCallbacks = params[name];
paramCalled = called[name];
if (paramVal === undefined || !paramCallbacks) {
return param();
}
// param previously called with same value or error occurred
if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) {
// restore value
req.params[name] = paramCalled.value;
// next param
return param(paramCalled.error);
}
called[name] = paramCalled = {
error: null,
match: paramVal,
value: paramVal
};
paramCallback();
}
// single param callbacks
function paramCallback(err) {
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
// store updated value
paramCalled.value = req.params[key.name];
if (err) {
// store error
paramCalled.error = err;
param(err);
return;
}
if (!fn) return param();
try {
fn(req, res, paramCallback, paramVal, key.name);
} catch (e) {
paramCallback(e);
}
}
param();
@@ -326,40 +396,47 @@ proto.process_params = function(route, req, res, done) {
* handlers can operate without any code changes regardless of the "prefix"
* pathname.
*
* @param {String|Function} route
* @param {Function} fn
* @return {app} for chaining
* @api public
*/
proto.use = function(route, fn){
// default route to '/'
if ('string' != typeof route) {
fn = route;
route = '/';
}
proto.use = function use(fn) {
var offset = 0;
var path = '/';
var self = this;
// default path to '/'
if (typeof fn !== 'function') {
var type = {}.toString.call(fn);
var msg = 'Router.use() requires callback functions but got a ' + type;
throw new Error(msg);
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 callback function');
}
var layer = new Layer(route, {
sensitive: this.caseSensitive,
strict: this.strict,
end: false
}, fn);
callbacks.forEach(function (fn) {
if (typeof fn !== 'function') {
var type = toString.call(fn);
var msg = 'Router.use() requires callback function but got a ' + type;
throw new TypeError(msg);
}
// add the middleware
debug('use %s %s', 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;
};
@@ -395,7 +472,77 @@ 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;
};
});
// 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;
}
}

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

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

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

@@ -4,34 +4,55 @@
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 proxyaddr = require('proxy-addr');
var qs = require('qs');
var querystring = require('querystring');
var typer = require('media-typer');
/**
* Deprecate function, like core `util.deprecate`
*
* @param {Function} fn
* @param {String} msg
* @return {Function}
* @api private
*/
exports.deprecate = function(fn, msg){
return 'test' !== process.env.NODE_ENV
? deprecate(fn, 'express: ' + msg)
: fn;
};
/**
* Return ETag for `body`.
* Return strong ETag for `body`.
*
* @param {String|Buffer} body
* @param {String} [encoding]
* @return {String}
* @api private
*/
exports.etag = function(body){
return '"' + crc32.signed(body) + '"';
exports.etag = function etag(body, encoding){
if (body.length === 0) {
// fast-path empty body
return '"1B2M2Y8AsgTpgAmY7PhCfg=="'
}
var hash = crypto
.createHash('md5')
.update(body, encoding)
.digest('base64')
return '"' + hash + '"'
};
/**
* Return weak ETag for `body`.
*
* @param {String|Buffer} body
* @param {String} [encoding]
* @return {String}
* @api private
*/
exports.wetag = function wetag(body, encoding){
if (body.length === 0) {
// fast-path empty body
return 'W/"0-0"'
}
var buf = Buffer.isBuffer(body)
? body
: new Buffer(body, encoding)
var len = buf.length
return 'W/"' + len.toString(16) + '-' + crc32.unsigned(buf) + '"'
};
/**
@@ -116,7 +137,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 + '"';
}
@@ -148,3 +169,134 @@ function acceptParams(str, index) {
return ret;
}
/**
* Compile "etag" value to function.
*
* @param {Boolean|String|Function} val
* @return {Function}
* @api private
*/
exports.compileETag = function(val) {
var fn;
if (typeof val === 'function') {
return val;
}
switch (val) {
case true:
fn = exports.wetag;
break;
case false:
break;
case 'strong':
fn = exports.etag;
break;
case 'weak':
fn = exports.wetag;
break;
default:
throw new TypeError('unknown value for etag function: ' + val);
}
return fn;
}
/**
* Compile "query parser" value to function.
*
* @param {String|Function} val
* @return {Function}
* @api private
*/
exports.compileQueryParser = function compileQueryParser(val) {
var fn;
if (typeof val === 'function') {
return val;
}
switch (val) {
case true:
fn = querystring.parse;
break;
case false:
fn = newObject;
break;
case 'extended':
fn = qs.parse;
break;
case 'simple':
fn = querystring.parse;
break;
default:
throw new TypeError('unknown value for query parser function: ' + val);
}
return fn;
}
/**
* Compile "proxy trust" value to function.
*
* @param {Boolean|String|Number|Array|Function} val
* @return {Function}
* @api private
*/
exports.compileTrust = function(val) {
if (typeof val === 'function') return val;
if (val === true) {
// Support plain true/false
return function(){ return true };
}
if (typeof val === 'number') {
// Support trusting hop count
return function(a, i){ return i < val };
}
if (typeof val === 'string') {
// Support comma-separated values
val = val.split(/ *, */);
}
return proxyaddr.compile(val || []);
}
/**
* Set the charset in a given Content-Type string.
*
* @param {String} type
* @param {String} charset
* @return {String}
* @api private
*/
exports.setCharset = function(type, charset){
if (!type || !charset) return type;
// parse type
var parsed = typer.parse(type);
// set charset
parsed.parameters.charset = charset;
// format type
return typer.format(parsed);
};
/**
* Return new empty objet.
*
* @return {Object}
* @api private
*/
function newObject() {
return {};
}

View File

@@ -1,74 +1,17 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "4.2.0",
"description": "Fast, unopinionated, minimalist web framework",
"version": "4.8.2",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{
"name": "TJ Holowaychuk",
"email": "tj@vision-media.ca"
},
{
"name": "Aaron Heckmann",
"email": "aaron.heckmann+github@gmail.com"
},
{
"name": "Ciaran Jessup",
"email": "ciaranj@gmail.com"
},
{
"name": "Douglas Christopher Wilson",
"email": "doug@somethingdoug.com"
},
{
"name": "Guillermo Rauch",
"email": "rauchg@gmail.com"
},
{
"name": "Jonathan Ong",
"email": "me@jongleberry.com"
},
{
"name": "Roman Shtylman",
"email": "shtylman+expressjs@gmail.com"
}
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
"Ciaran Jessup <ciaranj@gmail.com>",
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Guillermo Rauch <rauchg@gmail.com>",
"Jonathan Ong <me@jongleberry.com>",
"Roman Shtylman <shtylman+expressjs@gmail.com>",
"Young Jae Sim <hanul@hanul.me>"
],
"dependencies": {
"parseurl": "1.0.1",
"accepts": "1.0.1",
"type-is": "1.1.0",
"range-parser": "1.0.0",
"cookie": "0.1.2",
"buffer-crc32": "0.2.1",
"fresh": "0.2.2",
"methods": "1.0.0",
"send": "0.3.0",
"cookie-signature": "1.0.3",
"merge-descriptors": "0.0.2",
"utils-merge": "1.0.0",
"escape-html": "1.0.1",
"qs": "0.6.6",
"serve-static": "1.1.0",
"path-to-regexp": "0.1.2",
"debug": "0.8.1"
},
"devDependencies": {
"mocha": "~1.18.2",
"body-parser": "~1.1.2",
"connect-redis": "~2.0.0",
"ejs": "~1.0.0",
"jade": "~0.35.0",
"marked": "0.3.2",
"multiparty": "~3.2.4",
"hjs": "~0.0.6",
"should": "~3.3.1",
"supertest": "~0.12.0",
"method-override": "1.0.0",
"cookie-parser": "1.0.1",
"express-session": "1.0.4",
"morgan": "1.0.1",
"vhost": "1.0.0"
},
"keywords": [
"express",
"framework",
@@ -80,13 +23,58 @@
"app",
"api"
],
"repository": "git://github.com/visionmedia/express",
"scripts": {
"prepublish": "npm prune",
"test": "make test"
"repository": "visionmedia/express",
"license": "MIT",
"dependencies": {
"accepts": "~1.0.7",
"buffer-crc32": "0.2.3",
"debug": "1.0.4",
"depd": "0.4.4",
"escape-html": "1.0.1",
"finalhandler": "0.1.0",
"media-typer": "0.2.0",
"methods": "1.1.0",
"parseurl": "~1.2.0",
"path-to-regexp": "0.1.3",
"proxy-addr": "1.0.1",
"qs": "1.2.0",
"range-parser": "1.0.0",
"send": "0.8.1",
"serve-static": "~1.5.0",
"type-is": "~1.3.2",
"vary": "0.1.0",
"cookie": "0.1.2",
"fresh": "0.2.2",
"cookie-signature": "1.0.4",
"merge-descriptors": "0.0.2",
"utils-merge": "1.0.0"
},
"devDependencies": {
"after": "0.8.1",
"istanbul": "0.3.0",
"mocha": "~1.21.4",
"should": "~4.0.4",
"supertest": "~0.13.0",
"connect-redis": "~2.0.0",
"ejs": "~1.0.0",
"marked": "0.3.2",
"hjs": "~0.0.6",
"body-parser": "~1.6.1",
"cookie-parser": "~1.3.1",
"express-session": "~1.7.2",
"jade": "~1.5.0",
"method-override": "~2.1.1",
"morgan": "~1.2.2",
"multiparty": "~3.3.1",
"vhost": "2.0.0"
},
"engines": {
"node": ">= 0.10.0"
},
"license": "MIT"
"scripts": {
"prepublish": "npm prune",
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-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,164 +10,229 @@ describe('Route', function(){
describe('.all', function(){
it('should add handler', function(done){
var req = { method: 'GET', url: '/' };
var route = new Route('/foo');
route.all(function(req, res, next) {
assert.equal(req.a, 1);
assert.equal(res.b, 2);
req.called = true;
next();
});
route.dispatch({ a:1, method: 'GET' }, { b:2 }, done);
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.ok;
done();
});
})
it('should handle VERBS', function(done) {
var route = new Route('/foo');
var count = 0;
var route = new Route('/foo');
var cb = after(methods.length, function (err) {
if (err) return done(err);
count.should.equal(methods.length);
done();
});
route.all(function(req, res, next) {
count++;
next();
});
methods.forEach(function testMethod(method) {
route.dispatch({ method: method }, {});
var req = { method: method, url: '/' };
route.dispatch(req, {}, cb);
});
assert.equal(count, methods.length);
done();
})
it('should stack', function(done) {
var req = { count: 0, method: 'GET', url: '/' };
var route = new Route('/foo');
var count = 0;
route.all(function(req, res, next) {
count++;
req.count++;
next();
});
route.all(function(req, res, next) {
count++;
req.count++;
next();
});
route.dispatch({ method: 'GET' }, {}, function(err) {
assert.ifError(err);
count++;
route.dispatch(req, {}, function (err) {
if (err) return done(err);
req.count.should.equal(2);
done();
});
assert.equal(count, 3);
done();
})
})
describe('.VERB', function(){
it('should support .get', function(done){
var req = { method: 'GET', url: '/' };
var route = new Route('');
var count = 0;
route.get(function(req, res, next) {
count++;
req.called = true;
next();
})
route.dispatch({ method: 'GET' }, {});
assert(count);
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.ok;
done();
});
})
it('should limit to just .VERB', function(done){
var req = { method: 'POST', url: '/' };
var route = new Route('');
route.get(function(req, res, next) {
assert(false);
done();
throw new Error('not me!');
})
route.post(function(req, res, next) {
assert(true);
req.called = true;
next();
})
route.dispatch({ method: 'post' }, {});
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.true;
done();
});
})
it('should allow fallthrough', function(done){
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.get(function(req, res, next) {
order += 'a';
req.order += 'a';
next();
})
route.all(function(req, res, next) {
order += 'b';
req.order += 'b';
next();
});
route.get(function(req, res, next) {
order += 'c';
req.order += 'c';
next();
})
route.dispatch({ method: 'get' }, {});
assert.equal(order, 'abc');
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
req.order.should.equal('abc');
done();
});
})
})
describe('errors', function(){
it('should handle errors via arity 4 functions', function(done){
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.all(function(req, res, next){
next(new Error('foobar'));
});
route.all(function(req, res, next){
order += '0';
req.order += '0';
next();
});
route.all(function(err, req, res, next){
order += 'a';
req.order += 'a';
next(err);
});
route.all(function(err, req, res, next){
assert.equal(err.message, 'foobar');
assert.equal(order, 'a');
route.dispatch(req, {}, function (err) {
should(err).be.ok;
should(err.message).equal('foobar');
req.order.should.equal('a');
done();
});
route.dispatch({ method: 'get' }, {});
})
it('should handle throw', function(done) {
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.all(function(req, res, next){
throw new Error('foobar');
});
route.all(function(req, res, next){
order += '0';
req.order += '0';
next();
});
route.all(function(err, req, res, next){
order += 'a';
req.order += 'a';
next(err);
});
route.all(function(err, req, res, next){
assert.equal(err.message, 'foobar');
assert.equal(order, 'a');
route.dispatch(req, {}, function (err) {
should(err).be.ok;
should(err.message).equal('foobar');
req.order.should.equal('a');
done();
});
});
route.dispatch({ method: 'get' }, {});
it('should handle throwing inside error handlers', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.get(function(req, res, next){
throw new Error('boom!');
});
route.get(function(err, req, res, next){
throw new Error('oops');
});
route.get(function(err, req, res, next){
req.message = err.message;
next();
});
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.message).equal('oops');
done();
});
});
it('should handle throw in .all', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.all(function(req, res, next){
throw new Error('boom!');
});
route.dispatch(req, {}, function(err){
should(err).be.ok;
err.message.should.equal('boom!');
done();
});
});
it('should handle single error handler', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.all(function(err, req, res, next){
// this should not execute
true.should.be.false;
});
route.dispatch(req, {}, done);
});
})
})

View File

@@ -1,4 +1,5 @@
var after = require('after');
var express = require('../')
, Router = express.Router
, methods = require('methods')
@@ -128,7 +129,153 @@ describe('Router', function(){
done();
});
router.handle({ url: '/foo/2', method: 'GET' }, {}, done);
router.handle({ url: '/foo/2', method: 'GET' }, {}, function() {});
});
it('should handle throwing in handler after async param', function(done) {
var router = new Router();
router.param('user', function(req, res, next, val){
process.nextTick(function(){
req.user = val;
next();
});
});
router.use('/:user', function(req, res, next){
throw new Error('oh no!');
});
router.use(function(err, req, res, next){
assert.equal(err.message, 'oh no!');
done();
});
router.handle({ url: '/bob', method: 'GET' }, {}, function() {});
});
it('should handle throwing inside error handlers', function(done) {
var router = new Router();
router.use(function(req, res, next){
throw new Error('boom!');
});
router.use(function(err, req, res, next){
throw new Error('oops');
});
router.use(function(err, req, res, next){
assert.equal(err.message, 'oops');
done();
});
router.handle({ url: '/', method: 'GET' }, {}, done);
});
})
describe('FQDN', function () {
it('should not obscure FQDNs', function (done) {
var request = { hit: 0, url: 'http://example.com/foo', method: 'GET' };
var router = new Router();
router.use(function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/foo');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should ignore FQDN in search', function (done) {
var request = { hit: 0, url: '/proxy?url=http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use('/proxy', function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, '/?url=http://example.com/blog/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should 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();
});
});
})
@@ -183,5 +330,97 @@ describe('Router', function(){
router.handle({ url: '/foo/123/bar/baz', method: 'get' }, {}, done);
});
it('should only call once per request', function(done) {
var count = 0;
var req = { url: '/foo/bob/bar', method: 'get' };
var router = new Router();
var sub = new Router();
sub.get('/bar', function(req, res, next) {
next();
});
router.param('user', function(req, res, next, user) {
count++;
req.user = user;
next();
});
router.use('/foo/:user/', new Router());
router.use('/foo/:user/', sub);
router.handle(req, {}, function(err) {
if (err) return done(err);
assert.equal(count, 1);
assert.equal(req.user, 'bob');
done();
});
});
it('should call when values differ', function(done) {
var count = 0;
var req = { url: '/foo/bob/bar', method: 'get' };
var router = new Router();
var sub = new Router();
sub.get('/bar', function(req, res, next) {
next();
});
router.param('user', function(req, res, next, user) {
count++;
req.user = user;
next();
});
router.use('/foo/:user/', new Router());
router.use('/:user/bob/', sub);
router.handle(req, {}, function(err) {
if (err) return done(err);
assert.equal(count, 2);
assert.equal(req.user, 'foo');
done();
});
});
});
describe('parallel requests', function() {
it('should not mix requests', function(done) {
var req1 = { url: '/foo/50/bar', method: 'get' };
var req2 = { url: '/foo/10/bar', method: 'get' };
var router = new Router();
var sub = new Router();
done = after(2, done);
sub.get('/bar', function(req, res, next) {
next();
});
router.param('ms', function(req, res, next, ms) {
ms = parseInt(ms, 10);
req.ms = ms;
setTimeout(next, ms);
});
router.use('/foo/:ms/', new Router());
router.use('/foo/:ms/', sub);
router.handle(req1, {}, function(err) {
assert.ifError(err);
assert.equal(req1.ms, 50);
assert.equal(req1.originalUrl, '/foo/50/bar');
done();
});
router.handle(req2, {}, function(err) {
assert.ifError(err);
assert.equal(req2.ms, 10);
assert.equal(req2.originalUrl, '/foo/10/bar');
done();
});
});
});
})

View File

@@ -1,13 +1,5 @@
var app = require('../../examples/auth/app')
, request = require('supertest');
function redirects(to, fn){
return function(err, res){
res.statusCode.should.equal(302)
res.headers.should.have.property('location').match(to);
fn()
}
}
var request = require('supertest')
function getCookie(res) {
return res.headers['set-cookie'][0].split(';')[0];
@@ -18,25 +10,93 @@ describe('auth', function(){
it('should redirect to /login', function(done){
request(app)
.get('/')
.end(redirects(/login$/, done))
.expect('Location', '/login')
.expect(302, done)
})
})
describe('GET /restricted (w/o cookie)',function(){
it('should redirect to /login', function(done){
describe('GET /login',function(){
it('should render login form', function(done){
request(app)
.get('/restricted')
.end(redirects(/login$/,done))
.get('/login')
.expect(200, /<form/, done)
})
})
describe('POST /login', function(){
it('should fail without proper credentials', function(done){
it('should display login error', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=not-tj&password=foobar')
.end(redirects(/login$/, done))
.expect('Location', '/login')
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/login')
.set('Cookie', getCookie(res))
.expect(200, /Authentication failed/, done)
})
})
})
})
describe('GET /logout',function(){
it('should redirect to /', function(done){
request(app)
.get('/logout')
.expect('Location', '/')
.expect(302, done)
})
})
describe('GET /restricted',function(){
it('should redirect to /login without cookie', function(done){
request(app)
.get('/restricted')
.expect('Location', '/login')
.expect(302, done)
})
it('should succeed with proper cookie', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=tj&password=foobar')
.expect('Location', '/')
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/restricted')
.set('Cookie', getCookie(res))
.expect(200, done)
})
})
})
describe('POST /login', function(){
it('should fail without proper username', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=not-tj&password=foobar')
.expect('Location', '/login')
.expect(302, done)
})
it('should fail without proper password', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=tj&password=baz')
.expect('Location', '/login')
.expect(302, done)
})
it('should succeed with proper credentials', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=tj&password=foobar')
.expect('Location', '/')
.expect(302, done)
})
})
})

View File

@@ -7,16 +7,43 @@ describe('content-negotiation', function(){
it('should default to text/html', function(done){
request(app)
.get('/')
.expect('<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>')
.end(done);
.expect(200, '<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>', done)
})
it('should accept to text/plain', function(done){
request(app)
.get('/')
.set('Accept', 'text/plain')
.expect(' - Tobi\n - Loki\n - Jane\n')
.end(done);
.expect(200, ' - Tobi\n - Loki\n - Jane\n', done)
})
it('should accept to application/json', function(done){
request(app)
.get('/')
.set('Accept', 'application/json')
.expect(200, '[{"name":"Tobi"},{"name":"Loki"},{"name":"Jane"}]', done)
})
})
})
describe('GET /users', function(){
it('should default to text/html', function(done){
request(app)
.get('/users')
.expect(200, '<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>', done)
})
it('should accept to text/plain', function(done){
request(app)
.get('/users')
.set('Accept', 'text/plain')
.expect(200, ' - Tobi\n - Loki\n - Jane\n', done)
})
it('should accept to application/json', function(done){
request(app)
.get('/users')
.set('Accept', 'application/json')
.expect(200, '[{"name":"Tobi"},{"name":"Loki"},{"name":"Jane"}]', done)
})
})
})

View File

@@ -18,17 +18,59 @@ describe('cookies', function(){
done()
})
})
it('should respond to cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/')
.set('Cookie', res.headers['set-cookie'][0])
.expect(200, /Remembered/, done)
})
})
})
describe('GET /forget', function(){
it('should clear cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/forget')
.set('Cookie', res.headers['set-cookie'][0])
.expect('Set-Cookie', /remember=;/)
.expect(302, done)
})
})
})
describe('POST /', function(){
it('should set a cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.end(function(err, res){
.expect(302, function(err, res){
res.headers.should.have.property('set-cookie')
done()
})
})
it('should no set cookie w/o reminder', function(done){
request(app)
.post('/')
.send({})
.expect(302, function(err, res){
res.headers.should.not.have.property('set-cookie')
done()
})
})
})
})
})

View File

@@ -30,4 +30,4 @@ describe('downloads', function(){
.expect(404, done)
})
})
})
})

View File

@@ -7,14 +7,11 @@ describe('ejs', function(){
it('should respond with html', function(done){
request(app)
.get('/')
.end(function(err, res){
res.should.have.status(200);
res.should.have.header('Content-Type', 'text/html; charset=utf-8');
res.text.should.include('<li>tobi &lt;tobi@learnboost.com&gt;</li>');
res.text.should.include('<li>loki &lt;loki@learnboost.com&gt;</li>');
res.text.should.include('<li>jane &lt;jane@learnboost.com&gt;</li>');
done();
});
.expect('Content-Type', 'text/html; charset=utf-8')
.expect(/<li>tobi &lt;tobi@learnboost\.com&gt;<\/li>/)
.expect(/<li>loki &lt;loki@learnboost\.com&gt;<\/li>/)
.expect(/<li>jane &lt;jane@learnboost\.com&gt;<\/li>/)
.expect(200, done)
})
})
})
})

View File

@@ -1,6 +1,6 @@
var app = require('../../examples/markdown')
, request = require('supertest');
var request = require('supertest')
describe('markdown', function(){
describe('GET /', function(){
@@ -18,4 +18,4 @@ describe('markdown', function(){
.expect(500,done)
})
})
})
})

View File

@@ -7,10 +7,39 @@ describe('mvc', function(){
it('should redirect to /users', function(done){
request(app)
.get('/')
.expect('Location', '/users')
.expect(302, done)
})
})
describe('GET /pet/0', function(){
it('should get pet', function(done){
request(app)
.get('/pet/0')
.expect(200, /Tobi/, done)
})
})
describe('GET /pet/0/edit', function(){
it('should get pet edit page', function(done){
request(app)
.get('/pet/0/edit')
.expect(/<form/)
.expect(200, /Tobi/, done)
})
})
describe('PUT /pet/2', function(){
it('should update the pet', function(done){
request(app)
.put('/pet/3')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ pet: { name: 'Boots' } })
.end(function(err, res){
res.should.have.status(302);
res.headers.location.should.include('/users');
done();
if (err) return done(err);
request(app)
.get('/pet/3/edit')
.expect(200, /Boots/, done)
})
})
})
@@ -19,13 +48,11 @@ describe('mvc', function(){
it('should display a list of users', function(done){
request(app)
.get('/users')
.end(function(err, res){
res.text.should.include('<h1>Users</h1>');
res.text.should.include('>TJ<');
res.text.should.include('>Guillermo<');
res.text.should.include('>Nathan<');
done();
})
.expect(/<h1>Users<\/h1>/)
.expect(/>TJ</)
.expect(/>Guillermo</)
.expect(/>Nathan</)
.expect(200, done)
})
})
@@ -34,21 +61,16 @@ describe('mvc', function(){
it('should display the user', function(done){
request(app)
.get('/user/0')
.end(function(err, res){
res.text.should.include('<h1>TJ <a href="/user/0/edit">edit');
done();
})
.expect(200, /<h1>TJ <a href="\/user\/0\/edit">edit/, done)
})
it('should display the users pets', function(done){
request(app)
.get('/user/0')
.end(function(err, res){
res.text.should.include('/pet/0">Tobi');
res.text.should.include('/pet/1">Loki');
res.text.should.include('/pet/2">Jane');
done();
})
.expect(/\/pet\/0">Tobi/)
.expect(/\/pet\/1">Loki/)
.expect(/\/pet\/2">Jane/)
.expect(200, done)
})
})
@@ -65,27 +87,46 @@ describe('mvc', function(){
it('should display the edit form', function(done){
request(app)
.get('/user/1/edit')
.end(function(err, res){
res.text.should.include('<h1>Guillermo</h1>');
res.text.should.include('value="put"');
done();
})
.expect(/Guillermo/)
.expect(200, /<form/, done)
})
})
describe('PUT /user/:id', function(){
it('should 500 on error', function(done){
request(app)
.put('/user/1')
.send({})
.expect(500, done)
})
it('should update the user', function(done){
request(app)
.put('/user/1')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ user: { name: 'Tobo' }})
.end(function(err, res){
if (err) return done(err);
request(app)
.get('/user/1/edit')
.end(function(err, res){
res.text.should.include('<h1>Tobo</h1>');
done();
})
.expect(200, /Tobo/, done)
})
})
})
})
describe('POST /user/:id/pet', function(){
it('should create a pet for user', function(done){
request(app)
.post('/user/2/pet')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ pet: { name: 'Snickers' }})
.expect('Location', '/user/2')
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/user/2')
.expect(200, /Snickers/, done)
})
})
})
})

View File

@@ -1,5 +1,5 @@
var app = require('../../examples/params/app')
, request = require('supertest');
var request = require('supertest')
describe('params', function(){
describe('GET /', function(){
@@ -18,6 +18,14 @@ describe('params', function(){
})
})
describe('GET /user/9', function(){
it('should fail to find user', function(done){
request(app)
.get('/user/9')
.expect(/failed to find user/,done)
})
})
describe('GET /users/0-2', function(){
it('should respond with three users', function(done){
request(app)
@@ -25,4 +33,12 @@ describe('params', function(){
.expect(/users tj, tobi/,done)
})
})
})
describe('GET /users/foo-bar', function(){
it('should fail integer parsing', function(done){
request(app)
.get('/users/foo-bar')
.expect(/failed to parseInt foo/,done)
})
})
})

View File

@@ -1,5 +1,5 @@
var app = require('../../examples/resource/app')
, request = require('supertest');
var request = require('supertest')
describe('resource', function(){
describe('GET /', function(){
@@ -26,6 +26,14 @@ describe('resource', function(){
})
})
describe('GET /users/9', function(){
it('should respond with error', function(done){
request(app)
.get('/users/9')
.expect('{"error":"Cannot find user"}', done)
})
})
describe('GET /users/1..3', function(){
it('should respond with users 1 through 3', function(done){
request(app)
@@ -35,13 +43,21 @@ describe('resource', function(){
})
describe('DELETE /users/1', function(){
it('should respond with users 1 through 3', function(done){
it('should delete user 1', function(done){
request(app)
.del('/users/1')
.expect(/^destroyed/,done)
})
})
describe('DELETE /users/9', function(){
it('should fail', function(done){
request(app)
.del('/users/9')
.expect('Cannot find user', done)
})
})
describe('GET /users/1..3.json', function(){
it('should respond with users 2 and 3 as json', function(done){
request(app)
@@ -49,4 +65,4 @@ describe('resource', function(){
.expect(/^\[null,{"name":"aaron"},{"name":"guillermo"}\]/,done)
})
})
})
})

46
test/acceptance/vhost.js Normal file
View File

@@ -0,0 +1,46 @@
var app = require('../../examples/vhost')
var request = require('supertest')
describe('vhost', function(){
describe('example.com', function(){
describe('GET /', function(){
it('should say hello', function(done){
request(app)
.get('/')
.set('Host', 'example.com')
.expect(200, /hello/i, done)
})
})
describe('GET /foo', function(){
it('should say foo', function(done){
request(app)
.get('/foo')
.set('Host', 'example.com')
.expect(200, 'requested foo', done)
})
})
})
describe('foo.example.com', function(){
describe('GET /', function(){
it('should redirect to /foo', function(done){
request(app)
.get('/')
.set('Host', 'foo.example.com')
.expect(302, /Redirecting to http:\/\/example.com:3000\/foo/, done)
})
})
})
describe('bar.example.com', function(){
describe('GET /', function(){
it('should redirect to /bar', function(done){
request(app)
.get('/')
.set('Host', 'bar.example.com')
.expect(302, /Redirecting to http:\/\/example.com:3000\/bar/, done)
})
})
})
})

View File

@@ -24,11 +24,72 @@ describe('web-service', function(){
it('should respond users json', function(done){
request(app)
.get('/api/users?api-key=foo')
.end(function(err, res){
res.should.be.json;
res.text.should.equal('[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]');
done();
});
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]', done)
})
})
})
describe('GET /api/repos', function(){
describe('without an api key', function(){
it('should respond with 400 bad request', function(done){
request(app)
.get('/api/repos')
.expect(400, done);
})
})
describe('with an invalid api key', function(){
it('should respond with 401 unauthorized', function(done){
request(app)
.get('/api/repos?api-key=rawr')
.expect(401, done);
})
})
describe('with a valid api key', function(){
it('should respond repos json', function(done){
request(app)
.get('/api/repos?api-key=foo')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(/"name":"express"/)
.expect(/"url":"http:\/\/github.com\/visionmedia\/express"/)
.expect(200, done)
})
})
})
describe('GET /api/user/:name/repos', function(){
describe('without an api key', function(){
it('should respond with 400 bad request', function(done){
request(app)
.get('/api/user/loki/repos')
.expect(400, done);
})
})
describe('with an invalid api key', function(){
it('should respond with 401 unauthorized', function(done){
request(app)
.get('/api/user/loki/repos?api-key=rawr')
.expect(401, done);
})
})
describe('with a valid api key', function(){
it('should respond user repos json', function(done){
request(app)
.get('/api/user/loki/repos?api-key=foo')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(/"name":"stylus"/)
.expect(/"url":"http:\/\/github.com\/learnboost\/stylus"/)
.expect(200, done)
})
it('should 404 with unknown user', function(done){
request(app)
.get('/api/user/bob/repos?api-key=foo')
.expect(404, done)
})
})
})
@@ -37,12 +98,8 @@ describe('web-service', function(){
it('should respond with 404 json', function(done){
request(app)
.get('/api/something?api-key=bar')
.end(function(err, res){
res.should.have.status(404);
res.should.be.json;
res.text.should.equal('{"error":"Lame, can\'t find that"}');
done();
});
.expect('Content-Type', /json/)
.expect(404, '{"error":"Lame, can\'t find that"}', done)
})
})
})
})

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('/');
})
})
@@ -84,3 +99,12 @@ describe('in production', function(){
process.env.NODE_ENV = 'test';
})
})
describe('without NODE_ENV', function(){
it('should default to development', function(){
process.env.NODE_ENV = '';
var app = express();
app.get('env').should.equal('development');
process.env.NODE_ENV = 'test';
})
})

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

@@ -37,6 +37,11 @@ describe('app', function(){
});
})
it('should fail if not given fn', function(){
var app = express();
app.param.bind(app, ':name', 'bob').should.throw();
})
})
describe('.param(names, fn)', function(){
@@ -95,5 +100,208 @@ describe('app', function(){
.get('/user/123')
.expect('123', done);
})
it('should only call once per request', function(done) {
var app = express();
var called = 0;
var count = 0;
app.param('user', function(req, res, next, user) {
called++;
req.user = user;
next();
});
app.get('/foo/:user', function(req, res, next) {
count++;
next();
});
app.get('/foo/:user', function(req, res, next) {
count++;
next();
});
app.use(function(req, res) {
res.end([count, called, req.user].join(' '));
});
request(app)
.get('/foo/bob')
.expect('2 1 bob', done);
})
it('should call when values differ', function(done) {
var app = express();
var called = 0;
var count = 0;
app.param('user', function(req, res, next, user) {
called++;
req.users = (req.users || []).concat(user);
next();
});
app.get('/:user/bob', function(req, res, next) {
count++;
next();
});
app.get('/foo/:user', function(req, res, next) {
count++;
next();
});
app.use(function(req, res) {
res.end([count, called, req.users.join(',')].join(' '));
});
request(app)
.get('/foo/bob')
.expect('2 2 foo,bob', done);
})
it('should support altering req.params across routes', function(done) {
var app = express();
app.param('user', function(req, res, next, user) {
req.params.user = 'loki';
next();
});
app.get('/:user', function(req, res, next) {
next('route');
});
app.get('/:user', function(req, res, next) {
res.send(req.params.user);
});
request(app)
.get('/bob')
.expect('loki', done);
})
it('should not invoke without route handler', function(done) {
var app = express();
app.param('thing', function(req, res, next, thing) {
req.thing = thing;
next();
});
app.param('user', function(req, res, next, user) {
next(new Error('invalid invokation'));
});
app.post('/:user', function(req, res, next) {
res.send(req.params.user);
});
app.get('/:thing', function(req, res, next) {
res.send(req.thing);
});
request(app)
.get('/bob')
.expect(200, 'bob', done);
})
it('should work with encoded values', function(done){
var app = express();
app.param('name', function(req, res, next, name){
req.params.name = name;
next();
});
app.get('/user/:name', function(req, res){
var name = req.params.name;
res.send('' + name);
});
request(app)
.get('/user/foo%25bar')
.expect('foo%bar', done);
})
it('should catch thrown error', function(done){
var app = express();
app.param('id', function(req, res, next, id){
throw new Error('err!');
});
app.get('/user/:id', function(req, res){
var id = req.params.id;
res.send('' + id);
});
request(app)
.get('/user/123')
.expect(500, done);
})
it('should catch thrown secondary error', function(done){
var app = express();
app.param('id', function(req, res, next, val){
process.nextTick(next);
});
app.param('id', function(req, res, next, id){
throw new Error('err!');
});
app.get('/user/:id', function(req, res){
var id = req.params.id;
res.send('' + id);
});
request(app)
.get('/user/123')
.expect(500, done);
})
it('should defer to next route', function(done){
var app = express();
app.param('id', function(req, res, next, id){
next('route');
});
app.get('/user/:id', function(req, res){
var id = req.params.id;
res.send('' + id);
});
app.get('/:name/123', function(req, res){
res.send('name');
});
request(app)
.get('/user/123')
.expect('name', done);
})
it('should defer all the param routes', function(done){
var app = express();
app.param('id', function(req, res, next, val){
if (val === 'new') return next('route');
return next();
});
app.all('/user/:id', function(req, res){
res.send('all.id');
});
app.get('/user/:id', function(req, res){
res.send('get.id');
});
app.get('/user/new', function(req, res){
res.send('get.new');
});
request(app)
.get('/user/new')
.expect('get.new', done);
})
})
})

View File

@@ -54,6 +54,27 @@ describe('app', function(){
})
})
it('should handle render error throws', function(done){
var app = express();
function View(name, options){
this.name = name;
this.path = 'fale';
}
View.prototype.render = function(options, fn){
throw new Error('err!');
};
app.set('view', View);
app.render('something', function(err, str){
err.should.be.ok;
err.message.should.equal('err!');
done();
})
})
describe('when the file does not exist', function(){
it('should provide a helpful error', function(done){
var app = express();
@@ -132,6 +153,68 @@ describe('app', function(){
})
})
})
describe('caching', function(){
it('should always lookup view without cache', function(done){
var app = express();
var count = 0;
function View(name, options){
this.name = name;
this.path = 'fake';
count++;
}
View.prototype.render = function(options, fn){
fn(null, 'abstract engine');
};
app.set('view cache', false);
app.set('view', View);
app.render('something', function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
app.render('something', function(err, str){
if (err) return done(err);
count.should.equal(2);
str.should.equal('abstract engine');
done();
})
})
})
it('should cache with "view cache" setting', function(done){
var app = express();
var count = 0;
function View(name, options){
this.name = name;
this.path = 'fake';
count++;
}
View.prototype.render = function(options, fn){
fn(null, 'abstract engine');
};
app.set('view cache', true);
app.set('view', View);
app.render('something', function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
app.render('something', function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
done();
})
})
})
})
})
describe('.render(name, options, fn)', function(){
@@ -175,5 +258,37 @@ describe('app', function(){
done();
})
})
describe('caching', function(){
it('should cache with cache option', function(done){
var app = express();
var count = 0;
function View(name, options){
this.name = name;
this.path = 'fake';
count++;
}
View.prototype.render = function(options, fn){
fn(null, 'abstract engine');
};
app.set('view cache', false);
app.set('view', View);
app.render('something', {cache: true}, function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
app.render('something', {cache: true}, function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
done();
})
})
})
})
})
})

View File

@@ -49,4 +49,14 @@ describe('app.route', function(){
.get('/test')
.expect('test', done);
});
it('should not error on empty routes', function(done){
var app = express();
app.route('/:foo');
request(app)
.get('/test')
.expect(404, done);
});
});

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;
@@ -25,6 +53,40 @@ describe('app.router', function(){
[method]('/foo')
.expect('head' == method ? '' : method, done);
})
it('should reject numbers for app.' + method, function(){
var app = express();
app[method].bind(app, '/', 3).should.throw(/Number/);
})
});
it('should re-route when method is altered', function (done) {
var app = express();
var cb = after(3, done);
app.use(function (req, res, next) {
if (req.method !== 'POST') return next();
req.method = 'DELETE';
res.setHeader('X-Method-Altered', '1');
next();
});
app.delete('/', function (req, res) {
res.end('deleted everything');
});
request(app)
.get('/')
.expect(404, 'Cannot GET /\n', cb);
request(app)
.delete('/')
.expect(200, 'deleted everything', cb);
request(app)
.post('/')
.expect('X-Method-Altered', '1')
.expect(200, 'deleted everything', cb);
});
})
@@ -178,6 +240,106 @@ describe('app.router', function(){
})
})
describe('params', function(){
it('should overwrite existing req.params by default', function(done){
var app = express();
var router = new express.Router();
router.get('/:action', function(req, res){
res.send(req.params);
});
app.use('/user/:user', router);
request(app)
.get('/user/1/get')
.expect(200, '{"action":"get"}', done);
})
it('should allow merging existing req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:action', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/:user', router);
request(app)
.get('/user/tj/get')
.expect(200, '[["action","get"],["user","tj"]]', done);
})
it('should use params from router', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:thing', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/:thing', router);
request(app)
.get('/user/tj/get')
.expect(200, '[["thing","get"]]', done);
})
it('should merge numeric indices req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/*.*', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/id:(\\d+)', router);
request(app)
.get('/user/id:10/profile.json')
.expect(200, '[["0","10"],["1","profile"],["2","json"]]', done);
})
it('should merge numeric indices req.params when more in parent', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/*', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/id:(\\d+)/name:(\\w+)', router);
request(app)
.get('/user/id:10/name:tj/profile')
.expect(200, '[["0","10"],["1","tj"],["2","profile"]]', done);
})
it('should ignore invalid incoming req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:name', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/', function (req, res, next) {
req.params = 3; // wat?
router(req, res, next);
});
request(app)
.get('/user/tj')
.expect(200, '[["name","tj"]]', done);
})
})
describe('trailing slashes', function(){
it('should be optional by default', function(done){
var app = express();
@@ -206,6 +368,46 @@ describe('app.router', function(){
.expect('tj', done);
})
it('should pass-though middleware', function(done){
var app = express();
app.enable('strict routing');
app.use(function (req, res, next) {
res.setHeader('x-middleware', 'true');
next();
});
app.get('/user/', function(req, res){
res.end('tj');
});
request(app)
.get('/user/')
.expect('x-middleware', 'true')
.expect(200, 'tj', done);
})
it('should pass-though mounted middleware', function(done){
var app = express();
app.enable('strict routing');
app.use('/user/', function (req, res, next) {
res.setHeader('x-middleware', 'true');
next();
});
app.get('/user/test/', function(req, res){
res.end('tj');
});
request(app)
.get('/user/test/')
.expect('x-middleware', 'true')
.expect(200, 'tj', done);
})
it('should match no slashes', function(done){
var app = express();
@@ -220,6 +422,48 @@ describe('app.router', function(){
.expect('tj', done);
})
it('should match middleware when omitting the trailing slash', function(done){
var app = express();
app.enable('strict routing');
app.use('/user/', function(req, res){
res.end('tj');
});
request(app)
.get('/user')
.expect(200, 'tj', done);
})
it('should match middleware', function(done){
var app = express();
app.enable('strict routing');
app.use('/user', function(req, res){
res.end('tj');
});
request(app)
.get('/user')
.expect(200, 'tj', done);
})
it('should match middleware when adding the trailing slash', function(done){
var app = express();
app.enable('strict routing');
app.use('/user', function(req, res){
res.end('tj');
});
request(app)
.get('/user/')
.expect(200, 'tj', done);
})
it('should fail when omitting the trailing slash', function(done){
var app = express();
@@ -534,6 +778,30 @@ describe('app.router', function(){
})
})
describe('when next("route") is called', function(){
it('should jump to next route', function(done){
var app = express()
function fn(req, res, next){
res.set('X-Hit', '1')
next('route')
}
app.get('/foo', fn, function(req, res, next){
res.end('failure')
});
app.get('/foo', function(req, res){
res.end('success')
})
request(app)
.get('/foo')
.expect('X-Hit', '1')
.expect(200, 'success', done)
})
})
describe('when next(err) is called', function(){
it('should break out of app.router', function(done){
var app = express()
@@ -568,6 +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){
@@ -588,6 +882,45 @@ describe('app.router', function(){
.expect('editing user 12', done);
})
it('should run in order added', function(done){
var app = express();
var path = [];
app.get('*', function(req, res, next){
path.push(0);
next();
});
app.get('/user/:id', function(req, res, next){
path.push(1);
next();
});
app.use(function(req, res, next){
path.push(2);
next();
});
app.all('/user/:id', function(req, res, next){
path.push(3);
next();
});
app.get('*', function(req, res, next){
path.push(4);
next();
});
app.use(function(req, res, next){
path.push(5);
res.end(path.join(','))
});
request(app)
.get('/user/1')
.expect(200, '0,1,2,3,4,5', done);
})
it('should be chainable', function(){
var app = express();
app.get('/', function(){}).should.equal(app);

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,6 +16,11 @@ describe('app', function(){
app.use(blog);
})
it('should reject missing functions', function(){
var app = express();
app.use.bind(app, 3).should.throw(/requires middleware function/);
})
describe('.use(app)', function(){
it('should mount the app', function(done){
var blog = express()
@@ -78,5 +84,220 @@ 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);
})
})
describe('.use(path, middleware)', function(){
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 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 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);
})
})
})

View File

@@ -13,6 +13,29 @@ describe('config', function(){
var app = express();
app.set('foo', undefined).should.equal(app);
})
describe('"etag"', function(){
it('should throw on bad value', function(){
var app = express()
app.set.bind(app, 'etag', 42).should.throw(/unknown value/)
})
it('should set "etag fn"', function(){
var app = express()
var fn = function(){}
app.set('etag', fn)
app.get('etag fn').should.equal(fn)
})
})
describe('"trust proxy"', function(){
it('should set "trust proxy fn"', function(){
var app = express()
var fn = function(){}
app.set('trust proxy', fn)
app.get('trust proxy fn').should.equal(fn)
})
})
})
describe('.get()', function(){
@@ -91,4 +114,4 @@ describe('config', function(){
app.disabled('foo').should.be.false;
})
})
})
})

View File

@@ -1,7 +1,7 @@
var express = require('../');
var request = require('supertest');
var assert = require('assert');
var should = require('should');
describe('exports', function(){
it('should expose Router', function(){
@@ -50,4 +50,12 @@ describe('exports', function(){
.get('/')
.expect('bar', done);
})
it('should throw on old middlewares', function(){
var error;
try { express.bodyParser; } catch (e) { error = e; }
should(error).have.property('message');
error.message.should.containEql('middleware');
error.message.should.containEql('bodyParser');
})
})

1
test/fixtures/% of dogs.txt vendored Normal file
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

@@ -41,4 +41,4 @@ describe('middleware', function(){
})
})
})
})
})

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

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

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('.acceptsLanguage', function(){
it('should be true if language accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguage('en-us').should.be.ok;
req.acceptsLanguage('en').should.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
it('should be false if language not accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguage('es').should.not.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
describe('when Accept-Language is not present', function(){
it('should always return true', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguage('en').should.be.ok;
req.acceptsLanguage('es').should.be.ok;
req.acceptsLanguage('jp').should.be.ok;
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})
})
})

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);
})
})
})
})

87
test/req.baseUrl.js Normal file
View File

@@ -0,0 +1,87 @@
var express = require('..')
var request = require('supertest')
describe('req', function(){
describe('.baseUrl', function(){
it('should be empty for top-level route', function(done){
var app = express()
app.get('/:a', function(req, res){
res.end(req.baseUrl)
})
request(app)
.get('/foo')
.expect(200, '', done)
})
it('should contain lower path', function(done){
var app = express()
var sub = express.Router()
sub.get('/:b', function(req, res){
res.end(req.baseUrl)
})
app.use('/:a', sub)
request(app)
.get('/foo/bar')
.expect(200, '/foo', done);
})
it('should contain full lower path', function(done){
var app = express()
var sub1 = express.Router()
var sub2 = express.Router()
var sub3 = express.Router()
sub3.get('/:d', function(req, res){
res.end(req.baseUrl)
})
sub2.use('/:c', sub3)
sub1.use('/:b', sub2)
app.use('/:a', sub1)
request(app)
.get('/foo/bar/baz/zed')
.expect(200, '/foo/bar/baz', done);
})
it('should travel through routers correctly', function(done){
var urls = []
var app = express()
var sub1 = express.Router()
var sub2 = express.Router()
var sub3 = express.Router()
sub3.get('/:d', function(req, res, next){
urls.push('0@' + req.baseUrl)
next()
})
sub2.use('/:c', sub3)
sub1.use('/', function(req, res, next){
urls.push('1@' + req.baseUrl)
next()
})
sub1.use('/bar', sub2)
sub1.use('/bar', function(req, res, next){
urls.push('2@' + req.baseUrl)
next()
})
app.use(function(req, res, next){
urls.push('3@' + req.baseUrl)
next()
})
app.use('/:a', sub1)
app.use(function(req, res, next){
urls.push('4@' + req.baseUrl)
res.end(urls.join(','))
})
request(app)
.get('/foo/bar/baz/zed')
.expect(200, '3@,1@/foo,0@/foo/bar/baz,2@/foo/bar,4@', done);
})
})
})

View File

@@ -6,15 +6,16 @@ describe('req', function(){
describe('.fresh', function(){
it('should return true when the resource is not modified', function(done){
var app = express();
var etag = '"12345"';
app.use(function(req, res){
res.set('ETag', '12345');
res.set('ETag', etag);
res.send(req.fresh);
});
request(app)
.get('/')
.set('If-None-Match', '12345')
.set('If-None-Match', etag)
.expect(304, done);
})
@@ -22,14 +23,14 @@ describe('req', function(){
var app = express();
app.use(function(req, res){
res.set('ETag', '123');
res.set('ETag', '"123"');
res.send(req.fresh);
});
request(app)
.get('/')
.set('If-None-Match', '12345')
.expect('false', done);
.set('If-None-Match', '"12345"')
.expect(200, 'false', done);
})
})
})

View File

@@ -69,5 +69,70 @@ describe('req', function(){
.set('Host', '[::1]:3000')
.expect('[::1]', done);
})
describe('when "trust proxy" is enabled', function(){
it('should respect X-Forwarded-Host', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.end(req.host);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'example.com')
.expect('example.com', done);
})
it('should ignore X-Forwarded-Host if socket addr not trusted', function(done){
var app = express();
app.set('trust proxy', '10.0.0.1');
app.use(function(req, res){
res.end(req.host);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'example.com')
.expect('localhost', done);
})
it('should default to Host', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.end(req.host);
});
request(app)
.get('/')
.set('Host', 'example.com')
.expect('example.com', done);
})
})
describe('when "trust proxy" is disabled', function(){
it('should ignore X-Forwarded-Host', function(done){
var app = express();
app.use(function(req, res){
res.end(req.host);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'evil')
.expect('localhost', done);
})
})
})
})

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

@@ -20,6 +20,21 @@ describe('req', function(){
.set('X-Forwarded-For', 'client, p1, p2')
.expect('client', done);
})
it('should return the addr after trusted proxy', function(done){
var app = express();
app.set('trust proxy', 2);
app.use(function(req, res, next){
res.send(req.ip);
});
request(app)
.get('/')
.set('X-Forwarded-For', 'client, p1, p2')
.expect('p1', done);
})
})
describe('when "trust proxy" is disabled', function(){

View File

@@ -20,6 +20,21 @@ describe('req', function(){
.set('X-Forwarded-For', 'client, p1, p2')
.expect('["client","p1","p2"]', done);
})
it('should stop at first untrusted', function(done){
var app = express();
app.set('trust proxy', 2);
app.use(function(req, res, next){
res.send(req.ips);
});
request(app)
.get('/')
.set('X-Forwarded-For', 'client, p1, p2')
.expect('["p1","p2"]', done);
})
})
describe('when "trust proxy" is disabled', function(){

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,36 @@ 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();
app.set('trust proxy', '10.0.0.1');
app.use(function(req, res){
res.end(req.protocol);
});
request(app)
.get('/')
.set('X-Forwarded-Proto', 'https')
.expect('http', done);
})
it('should default to http', 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

@@ -1,5 +1,6 @@
var express = require('../');
var assert = require('assert');
var express = require('..');
function req(ret) {
return {
@@ -27,5 +28,11 @@ describe('req', function(){
ret.type = 'users';
req('users=0-').range(Infinity).should.eql(ret);
})
it('should return undefined if no range', function(){
var ret = [{ start: 0, end: 50 }, { start: 60, end: 100 }];
ret.type = 'bytes';
assert(req('').range(120) === undefined);
})
})
})

View File

@@ -6,15 +6,16 @@ describe('req', function(){
describe('.stale', function(){
it('should return false when the resource is not modified', function(done){
var app = express();
var etag = '"12345"';
app.use(function(req, res){
res.set('ETag', '12345');
res.set('ETag', etag);
res.send(req.stale);
});
request(app)
.get('/')
.set('If-None-Match', '12345')
.set('If-None-Match', etag)
.expect(304, done);
})
@@ -22,14 +23,14 @@ describe('req', function(){
var app = express();
app.use(function(req, res){
res.set('ETag', '123');
res.set('ETag', '"123"');
res.send(req.stale);
});
request(app)
.get('/')
.set('If-None-Match', '12345')
.expect('true', done);
.set('If-None-Match', '"12345"')
.expect(200, 'true', done);
})
})
})

View File

@@ -36,7 +36,7 @@ describe('res', function(){
app.use(function(req, res){
res.attachment('/path/to/image.png');
res.send('foo');
res.send(new Buffer(4));
});
request(app)
@@ -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);
})

Some files were not shown because too many files have changed in this diff Show More