Compare commits

...

263 Commits
2.3.3 ... 2.5.9

Author SHA1 Message Date
TJ Holowaychuk
e3e7d0def6 Release 2.5.9 2012-04-02 19:21:18 -07:00
TJ Holowaychuk
585039f487 Merge pull request #1037 from pbuyle/2.x
Support PURGE request method.
2012-03-05 13:48:21 -08:00
Pierre Buyle
de4bc808a4 Support PURGE request method. 2012-03-05 16:35:14 -05:00
TJ Holowaychuk
1515d53f55 Merge pull request #1020 from papandreou/2.x
req.accepts: */* wildcard fix
2012-02-22 08:57:49 -08:00
Andreas Lind Petersen
c92c10d8ff req.accepts: */* wildcard fix.
Look for '*/*' anywhere in the Accept header instead of only supporting it when it's the exact value of the header.

IE8 sends:

    Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, */*

for which req.accepts('html') returned false.
2012-02-22 11:04:12 +01:00
TJ Holowaychuk
bc81e7441c Merge pull request #1007 from mmalecki/fix-boilerplate
[2.x] Do not to call `app.address()` before `listening` event
2012-02-13 10:29:14 -08:00
Maciej Małecki
78b874e881 Do not to call app.address() before listening event
According to joyent/node@d3f6b094c7,
call to `net.Server.address()` should happen only after `net.Server`
emits `listening` event.
2012-02-12 16:53:17 +01:00
Tj Holowaychuk
5c50e3a58c Release 2.5.8 2012-02-08 12:05:54 -08:00
Tj Holowaychuk
508e8cc64e upgrade deps. Closes #991 2012-02-08 12:04:51 -08:00
Tj Holowaychuk
5c962ba27b Release 2.5.7 2012-02-06 10:04:33 -08:00
TJ Holowaychuk
9bcb46848b Merge pull request #992 from mscdex/patch-1
Fixed app.all to not execute twice for the same incoming DELETE request
2012-02-06 10:05:07 -08:00
Brian White
4363b20186 Add test to check for multiple DELETEs in app.all 2012-02-04 23:13:36 -05:00
Brian White
77645bcc93 Fix app.all to not execute twice for the same incoming DELETE method request 2012-02-04 02:08:41 -05:00
TJ Holowaychuk
b0a66fe6c9 Merge pull request #985 from Turbo87/patch-2
bin/express: Removed whitespace / Added missing semicolon
2012-01-30 15:09:56 -08:00
Tobias Bieniek
e8dd0a1267 bin/express: Removed whitespace / Added missing semicolon 2012-01-30 23:29:57 +01:00
Tj Holowaychuk
9dd7b7b63a Release 2.5.6 2012-01-13 15:37:56 -08:00
Tj Holowaychuk
ba80b7ba97 updated hamljs dev dep. Closes #953 2012-01-13 14:06:30 -08:00
TJ Holowaychuk
45bb4aff09 Release 2.5.5 2012-01-08 12:31:48 -08:00
TJ Holowaychuk
bb14a86e17 Merge pull request #954 from matthewleon/filename_bug
In express 2.x, filename should be set on cached templates
2012-01-08 12:30:35 -08:00
Matthew Leon Grinshpun
1773681ef8 set filename on cached templates 2012-01-08 12:44:27 +01:00
Tj Holowaychuk
2ac612f14d Release 2.5.4 2012-01-02 08:33:41 -08:00
Tj Holowaychuk
20570fc80d Fixed express(1) eol on 0.4.x. Closes #947 2012-01-02 08:33:05 -08:00
TJ Holowaychuk
92c1c953dc Release 2.5.3 2011-12-30 15:31:10 -08:00
TJ Holowaychuk
8b372b3faa fixed a require 2011-12-30 15:30:00 -08:00
TJ Holowaychuk
da3a743c1b backported req.is() charset fix 2011-12-30 15:29:21 -08:00
TJ Holowaychuk
09c9537381 tweak eol 2011-12-28 10:15:18 -07:00
TJ Holowaychuk
9cf1800731 Merge pull request #942 from atsuya/fix_eol
Use os.platform to determine End-of-line
2011-12-28 09:14:56 -08:00
Atsuya Takagi
0d0fd347ab determine enf of line based on os.platform. 2011-12-28 01:26:53 -08:00
Tj Holowaychuk
5cf8023dc3 connect 1.x dep 2011-12-16 08:51:55 -08:00
Tj Holowaychuk
380a6c5363 Release 2.5.2 2011-12-10 11:07:21 -08:00
Tj Holowaychuk
c047b64ab5 removed less support since compiler() has gone away 2011-12-10 11:05:49 -08:00
Tj Holowaychuk
ae2bcb2615 express(1) LF -> CRLF for windows 2011-12-10 11:05:20 -08:00
Tj Holowaychuk
02cdf0c72b fixed express(1) --version 2011-11-18 08:03:16 -08:00
Tj Holowaychuk
a7520ad00c Release 2.5.1 2011-11-17 11:36:13 -08:00
Tj Holowaychuk
bf7807619d updated connect to 1.8.x 2011-11-17 11:32:56 -08:00
Tj Holowaychuk
b5346005af typo 2011-11-04 08:55:57 -07:00
Tj Holowaychuk
334a8d3fa2 Removed sass.js support from express(1)
sass.js is lame, use stylus
2011-11-04 08:35:10 -07:00
Tj Holowaychuk
7c6c07497a "node": ">= 0.4.1 < 0.7.0" 2011-10-24 16:01:46 -07:00
Tj Holowaychuk
95f6cda9c4 Release 2.5.0 2011-10-24 16:00:46 -07:00
Tj Holowaychuk
e2de941d09 Release 0.5.0 2011-10-24 16:00:35 -07:00
Tj Holowaychuk
f7d67ce766 Added ./routes dir for generated app by default 2011-10-24 15:58:22 -07:00
Tj Holowaychuk
b63f2ca903 Added npm install reminder to express(1) app gen 2011-10-24 15:46:42 -07:00
Tj Holowaychuk
77c2d89be6 fixing tests 2011-10-24 14:59:21 -07:00
Tj Holowaychuk
458097fe7b fixing tests 2011-10-24 14:51:09 -07:00
Tj Holowaychuk
1b25240d36 update expresso/should 2011-10-24 14:47:06 -07:00
Tj Holowaychuk
1cce4d98ff Removed make test-cov since it wont work with node 0.5.x 2011-10-24 14:37:09 -07:00
Tj Holowaychuk
09b5c79073 OCD 2011-10-24 14:36:45 -07:00
Tj Holowaychuk
9348500366 readme 2011-10-24 14:36:30 -07:00
Tj Holowaychuk
0f6a044d98 Fixed express(1) public dir for windows. Closes #866 2011-10-14 08:38:21 -07:00
Tj Holowaychuk
049a557341 remove test stuff 2011-10-07 08:30:31 -07:00
Tj Holowaychuk
94a6f42efd clean up jade example 2011-10-07 08:28:57 -07:00
Tj Holowaychuk
a4aed5f51a jade example using template inheritance 2011-10-07 08:25:43 -07:00
Tj Holowaychuk
3e6f45cb72 update jade dev dep 2011-10-07 08:19:11 -07:00
Tj Holowaychuk
3bd4de7f73 bump express(1) version 2011-10-06 12:05:44 -07:00
Tj Holowaychuk
5c04f85f93 Release 2.4.7 2011-10-05 15:41:50 -07:00
Tj Holowaychuk
77f885d4a0 connect 1.7.x to fix npm issue... 2011-10-05 15:34:22 -07:00
Tj Holowaychuk
c2fc9a83e8 Added mkdirp to express(1). Closes #795 2011-09-21 08:24:37 -07:00
Tj Holowaychuk
d8b20bd2d5 Added simple json-config example
for some who might prefer this. there are benefits to both
2011-09-13 09:00:09 -07:00
Tj Holowaychuk
d3c4fd91c9 typo 2011-09-12 10:39:22 -07:00
Tj Holowaychuk
00e44f6fc9 Fixed res.redirect() HEAD support. [reported by xerox] 2011-09-07 10:01:28 -07:00
Tj Holowaychuk
6692f911ab Fixed req.flash(), only escape args 2011-09-06 15:14:09 -07:00
Tj Holowaychuk
4f0b3f4684 Added shorthand for the parsed request's pathname 2011-09-02 16:41:58 -07:00
Tj Holowaychuk
cbf330c3db Fixed absolute path checking on windows. Closes #829 [reported by andrewpmckenzie] 2011-08-29 08:40:30 -07:00
Tj Holowaychuk
13010987b0 logger for jade example 2011-08-24 03:14:21 -07:00
Tj Holowaychuk
b078481cdb Release 2.5.6 2011-08-22 10:20:04 -07:00
Tj Holowaychuk
3888468a96 Fixed multiple param callback regression. Closes #824 [reported by TroyGoode] 2011-08-22 10:04:46 -07:00
Tj Holowaychuk
a6b70ceca4 fixed tests 2011-08-22 09:47:43 -07:00
Tj Holowaychuk
45757a0f1a bump express(1) version 2011-08-20 04:33:51 -07:00
Tj Holowaychuk
24a4a80ccd Release 2.4.5 2011-08-19 10:12:38 -07:00
Tj Holowaychuk
2fdd906a41 docs 2011-08-19 08:53:02 -07:00
Tj Holowaychuk
71cc6bac22 google analytics 2011-08-19 08:51:54 -07:00
Tj Holowaychuk
3a46660932 typo. Closes #815 2011-08-18 09:03:46 -07:00
Tj Holowaychuk
adca07a858 Refactored Route to use a single array of callbacks 2011-08-17 15:48:27 -07:00
Tj Holowaychuk
6b924bf1df fixed route error handlers when errors are thrown 2011-08-17 14:39:27 -07:00
Tj Holowaychuk
eb88f160b6 Added support for routes to handle errors. Closes #809
currently only the route end-point callbacks
support this, however this will change in the near future
to support route middleware etc
2011-08-17 14:33:11 -07:00
Tj Holowaychuk
01e6df759e Added "basepath" setting to work in conjunction with reverse proxies etc. Closes #805
this will allow you to essentially "trick" the express
app into thinking it is mounted when it is not.
2011-08-17 09:27:28 -07:00
Tj Holowaychuk
1dc4e6fcb4 qs >= 0.3.1 2011-08-17 07:53:16 -07:00
Tj Holowaychuk
fff6951d94 use nextRoute() internally 2011-08-16 18:29:57 -07:00
Tj Holowaychuk
1ebd49af75 Changed: removed .call(self) for route callbacks
not sure why we had this, ive never even used it
and the tests dont cover it, and its slower
2011-08-16 18:28:41 -07:00
Tj Holowaychuk
8c2107e337 Added app.routes.all(). Closes #803
not a huge fan of this API-wise, but at least it is something for now
2011-08-16 17:51:20 -07:00
Tj Holowaychuk
cddc5442f1 typo 2011-08-15 16:31:11 -07:00
Tj Holowaychuk
ac8cb270fe Fixed res.redirect() on windows due to join() usage. Closes #808 2011-08-15 13:46:58 -07:00
Tj Holowaychuk
d54ee58f93 Added support for multiple callbacks for app.param(). Closes #801
you can also make several calls to `app.param()` for the same
param name, which is equivalent to passing multiple in
a single call
2011-08-11 11:07:04 -07:00
Tj Holowaychuk
ce0fa0a3b2 Added test for multiple app.param() calls for the same param 2011-08-11 10:17:23 -07:00
Tj Holowaychuk
e2f41147ed added another test 2011-08-11 10:12:16 -07:00
Tj Holowaychuk
af3f7f0a65 Added test for app.param(fn) 2011-08-11 10:11:00 -07:00
Tj Holowaychuk
a37003945b docs 2011-08-05 19:35:11 -07:00
Tj Holowaychuk
75361fb177 Release 2.4.4 2011-08-05 04:29:52 -07:00
Tj Holowaychuk
21f9b386db empty string 2011-08-05 04:28:31 -07:00
Tj Holowaychuk
271cb16ecd Fixed res.send(204) (again?) 2011-08-05 04:28:08 -07:00
Tj Holowaychuk
803ec213d7 Fixed res.header() intention of a set, even when undefined 2011-08-03 19:59:40 -07:00
Tj Holowaychuk
9e9042f1eb added header.jade to jade example 2011-08-02 08:59:14 -07:00
Tj Holowaychuk
ec4c86f46d fixed * consumption 2011-07-29 09:51:09 -07:00
Arpad Borsos
45f22ec602 specialcase .:format routing to not include a dot in the capture group 2011-07-29 09:43:34 -07:00
Tj Holowaychuk
0e1eb72058 fixed a jade test 2011-07-29 09:32:36 -07:00
Tj Holowaychuk
4690b6cdf2 tweak generated stylus 2011-07-25 11:15:10 -07:00
Tj Holowaychuk
22204a5ce1 Fixed res.send(204) support. Closes #771 2011-07-22 08:34:13 -07:00
Tj Holowaychuk
1ec16c0450 qs >= 0.3.0 2011-07-19 12:08:50 -07:00
Tj Holowaychuk
c72abc5293 Release 2.4.3 2011-07-14 12:58:24 -07:00
Tj Holowaychuk
93189ad0b6 Fixed options.filename, exposing to template engines
this is useful for performing relative
lookups within the template engine itself,
without manually specifyin the path
2011-07-14 12:53:49 -07:00
Tj Holowaychuk
c0aab36187 Added docs for status option special-case. Closes #739 2011-07-07 09:09:34 -07:00
Tj Holowaychuk
5ae994ee8f Release 2.4.2 2011-07-06 20:15:44 -07:00
Tj Holowaychuk
60d16eab77 Revert "removed jsonp stripping"
This reverts commit 0ae18bca60.
2011-07-06 20:14:42 -07:00
Tj Holowaychuk
fc60dfc1a6 docs 2011-07-06 09:59:09 -07:00
Tj Holowaychuk
45d149c146 docs for multiple envs in app.configure() calls 2011-07-06 09:58:32 -07:00
Tj Holowaychuk
4dfc1a69c3 Release 2.4.1 2011-07-06 09:57:06 -07:00
Tj Holowaychuk
0ae18bca60 removed jsonp stripping
I cannot recall why I added this, doesnt make
sense to me now haha.
2011-07-06 09:45:21 -07:00
Tj Holowaychuk
2aaf0defe7 res.send() using res.json() 2011-07-06 09:32:16 -07:00
Tj Holowaychuk
73ea5cd7ee Added res.json() JSONP support. Closes #737 2011-07-06 09:31:37 -07:00
Tj Holowaychuk
e7f2d229ec added failing jsonp tests for res.json() 2011-07-06 09:25:10 -07:00
Tj Holowaychuk
87bc265817 moved jsonp tests 2011-07-06 09:24:08 -07:00
Tj Holowaychuk
796aaff295 connect 1.5.2 2011-07-06 09:07:09 -07:00
Tj Holowaychuk
8f87c50320 fixed connect-redis example 2011-07-05 09:55:04 -07:00
Tj Holowaychuk
ee4471b345 when cookie path === null dont default it 2011-07-04 13:51:08 -07:00
Tj Holowaychuk
6815feb8cf docs 2011-07-04 13:45:08 -07:00
Tj Holowaychuk
dbbe7be891 Added extending-templates example. Closes #730 2011-07-04 13:34:41 -07:00
Tj Holowaychuk
09c9452e5c Changed; default cookie path to "home" setting. Closes #731 2011-07-04 13:18:50 -07:00
Tj Holowaychuk
b0e669ba00 remove pids/logs creation from express(1)
just confused people
2011-07-04 13:05:13 -07:00
Tj Holowaychuk
f46ae9f3b2 docs 2011-07-04 09:20:35 -07:00
Tj Holowaychuk
9f2b344be8 Added support for multiple env app.configure() calls. Closes #735 2011-07-04 09:16:12 -07:00
Tj Holowaychuk
6b47271679 updated error-pages example 2011-07-01 14:14:46 -07:00
Tj Holowaychuk
6f7075be74 Added "strict routing" setting for trailing slashes
for people who are:

  a) over-optimizing seo
  b) working on fs-like APIs
2011-06-29 16:35:49 -07:00
Tj Holowaychuk
4b9cc3d698 docs 2011-06-28 11:27:51 -07:00
Tj Holowaychuk
3faa790b53 Release 2.4.0 2011-06-28 09:41:21 -07:00
Tj Holowaychuk
9477c9b516 docs for res.status() 2011-06-28 09:32:28 -07:00
Tj Holowaychuk
b04be51848 Added chainable res.status(code)
ex:

  res.status(500).send("lame")
2011-06-28 09:32:03 -07:00
Tj Holowaychuk
9e4020efd3 Merge branch 'feature/res-json' 2011-06-28 09:14:09 -07:00
Tj Holowaychuk
6db19db665 docs for res.json 2011-06-28 09:14:04 -07:00
Tj Holowaychuk
1386f80ae5 Added res.json() and tests 2011-06-28 09:11:54 -07:00
Tj Holowaychuk
e4342a7097 Added error handling to web-service example 2011-06-28 08:49:43 -07:00
Tj Holowaychuk
fda31b75f9 Added simple web-service example 2011-06-23 09:45:19 -07:00
Tj Holowaychuk
8ca0a45b33 hmac for auth example 2011-06-23 08:50:20 -07:00
Tj Holowaychuk
ce2bcaef68 Release 2.3.12 2011-06-22 13:56:13 -07:00
Tj Holowaychuk
0db7f26ad3 Fixed view layout bug. Closes #720
preventing custom relative layouts such as:

  views/
   users/
     user-layout.jade
2011-06-22 13:53:58 -07:00
Tj Holowaychuk
35370da458 screencasts have their own page 2011-06-21 15:16:46 -07:00
Tj Holowaychuk
fe6c5832c2 fixed docs charset 2011-06-21 15:15:38 -07:00
Tj Holowaychuk
e8c32df79c fixed some docs 2011-06-21 15:13:51 -07:00
Tj Holowaychuk
652e166462 updated docs 2011-06-21 15:08:17 -07:00
Tj Holowaychuk
af6385f8e4 docs 2011-06-21 15:06:49 -07:00
Tj Holowaychuk
f0277d3777 #express on freenode 2011-06-21 14:59:06 -07:00
Tj Holowaychuk
6bb100d7fa docs for req.get() 2011-06-21 10:48:34 -07:00
Tj Holowaychuk
f13ea34de3 typo 2011-06-21 10:46:07 -07:00
Tj Holowaychuk
48a14a443a Merge branch 'master' of github.com:visionmedia/express 2011-06-21 10:30:07 -07:00
Tj Holowaychuk
1820ea6f59 Merge branch 'param' 2011-06-21 10:29:08 -07:00
Tj Holowaychuk
4d9647923e Added req.get(field, param) 2011-06-21 10:29:04 -07:00
Tj Holowaychuk
943e9b3a28 connect >= 1.5.1 < 2.0.0 2011-06-20 23:20:33 -07:00
TJ Holowaychuk
6b2ec50a0b Merge pull request #709 from jzacsh/master
documentation: typo fix for view-lookup
2011-06-12 18:43:12 -07:00
Jonathan Zacsh
7b813b95b6 documentation spelling typo and URL fix. 2011-06-12 20:05:52 -04:00
TJ Holowaychuk
cdaa2e78d7 Merge pull request #707 from jakeg/patch-2
Updated connect-redis markdown docs
2011-06-12 11:07:09 -07:00
Jake Gordon
add53d3222 trying the .md file this time 2011-06-12 10:59:52 -07:00
TJ Holowaychuk
f4f79d2217 Merge pull request #706 from jakeg/patch-1
docs to show require('connect-redis')(express) re npm 1.x changes
2011-06-12 09:32:22 -07:00
Jake Gordon
aa36bc4516 Due to npm 1.x changes need to pass connect/express to the function connect-redis exports (see https://github.com/visionmedia/connect-redis) 2011-06-12 05:05:25 -07:00
Tj Holowaychuk
9028cacfd1 Fixed; ignore body on 304. Closes #701
should do the trick
2011-06-08 12:40:07 -07:00
Tj Holowaychuk
40ccb595cd "Japanese Documentation" in Japanese 日本語ドキュメンテーション :) 2011-06-07 09:46:28 -07:00
Tj Holowaychuk
5606d08ecb Links to Japanese documentation, thanks @hideyukisaito! 2011-06-07 09:41:16 -07:00
Tj Holowaychuk
1888d6fca1 Added; the express(1) generated app outputs the env
thanks nathan! totally thoguht I had this :D
2011-06-06 15:38:20 -07:00
Tj Holowaychuk
5d16e6b302 added content-negotiation example 2011-06-06 11:58:33 -07:00
Tj Holowaychuk
96f7574bc1 connect >= 1.4.3 < 2.0.0 2011-06-06 10:21:53 -07:00
Tj Holowaychuk
490584c8bc misc refactor 2011-06-06 09:20:29 -07:00
Tj Holowaychuk
0cbb1f661c typo 2011-06-06 08:55:30 -07:00
Tj Holowaychuk
3dc53e105a misc refactoring 2011-06-06 08:22:35 -07:00
Tj Holowaychuk
e2cdd760d8 Release 2.3.11 2011-06-04 10:50:10 -07:00
Tj Holowaychuk
4169202a41 removed generation of dummy test file from express(1) 2011-06-04 10:47:48 -07:00
Tj Holowaychuk
835982c561 added devDependencies to generated package.json 2011-06-04 10:45:21 -07:00
Tj Holowaychuk
b67bacea18 more refactoring of cookie example 2011-06-02 13:58:46 -07:00
Tj Holowaychuk
3205ee7d75 refactored cookie example 2011-06-02 13:57:54 -07:00
Tj Holowaychuk
ff7d5ff4e5 generate docs 2011-06-01 17:35:27 -07:00
Tj Holowaychuk
723774af27 added quick start to guide 2011-06-01 17:35:14 -07:00
Tj Holowaychuk
c3fbd3fe10 express(1) usage docs 2011-06-01 17:34:07 -07:00
Tj Holowaychuk
d1d3871550 Fixed; express(1) adds express as a dep
duh...
2011-06-01 17:29:48 -07:00
Tj Holowaychuk
5462c8c7ec prune on prepublish 2011-06-01 16:59:29 -07:00
Tj Holowaychuk
9536341e30 added npm test 2011-05-30 14:19:26 -07:00
Tj Holowaychuk
1bb798d963 Release 2.3.10 2011-05-27 09:20:03 -07:00
Tj Holowaychuk
91997e9c53 Added req.route, exposing the current route. Closes #11
this can be used with a dynamicHelper to expose the _last_
route that was matched, aka the end-point when rendering a template.

this is a `Route` instance, so it has .path, .regexp, etc.
2011-05-27 09:12:49 -07:00
Tj Holowaychuk
1393187040 Merge branch 'refactor/executable' 2011-05-26 10:31:29 -07:00
Tj Holowaychuk
6e69c880d9 Added package.json generation support to express(1) 2011-05-26 10:31:21 -07:00
Tj Holowaychuk
59dcd03972 removed suggestions 2011-05-26 10:18:56 -07:00
Tj Holowaychuk
11482546a2 Fixed call to app.param() function for optional params. Closes #682 2011-05-26 09:56:04 -07:00
Tj Holowaychuk
1ce43dd347 added failing test for #682 2011-05-26 09:48:07 -07:00
Tj Holowaychuk
d1bfe137d4 test to ensure catch of invalid uri 2011-05-25 15:50:32 -07:00
Tj Holowaychuk
9d7452cdc2 more tests 2011-05-25 10:56:56 -07:00
Tj Holowaychuk
d9cee90efc Release 2.3.9 2011-05-25 10:18:26 -07:00
Tj Holowaychuk
175aa08500 more tests 2011-05-25 10:16:11 -07:00
Tj Holowaychuk
c9ff6198d3 more tests 2011-05-25 10:15:21 -07:00
Tj Holowaychuk
f026218c82 misc view refactoring 2011-05-25 10:10:23 -07:00
Tj Holowaychuk
5bc86b9e29 more tests 2011-05-25 09:54:55 -07:00
Tj Holowaychuk
5830ac9936 more tests 2011-05-25 09:54:06 -07:00
Tj Holowaychuk
d7c6c9a9f9 Release 2.3.8 2011-05-24 21:53:08 -07:00
Tj Holowaychuk
9c87eed60e Fixed OPTIONS regression preventing custom routing for this method 2011-05-24 21:52:52 -07:00
Tj Holowaychuk
f15eb6d5ef added options test 2011-05-24 21:41:37 -07:00
Tj Holowaychuk
5b33788359 express-mongoose link 2011-05-24 08:45:44 -07:00
Tj Holowaychuk
11ec3ccd48 removed some old invalid docs
new stuff to come
2011-05-23 17:16:02 -07:00
Tj Holowaychuk
9d498ba3f1 misc 2011-05-23 16:59:16 -07:00
Tj Holowaychuk
15d4047180 link 2011-05-23 16:57:36 -07:00
Tj Holowaychuk
44eae73843 Release 2.3.7 2011-05-23 15:54:17 -07:00
Tj Holowaychuk
d5b1c70731 removed invalidParamReturnValue() 2011-05-23 15:48:58 -07:00
Tj Holowaychuk
e78dc18cfd Removed app.param() callback with retval support
this is now abstract and will be going into express-params
along with some other cases
2011-05-23 15:40:41 -07:00
Tj Holowaychuk
4d122923e9 docs 2011-05-23 15:33:40 -07:00
Tj Holowaychuk
b1a7310263 Added support for app.param(fn) to define param logic 2011-05-23 15:30:34 -07:00
Tj Holowaychuk
d6ef90d98d Moved param() retval logic to Router#param() 2011-05-23 15:06:59 -07:00
Tj Holowaychuk
85df59ac31 comma first 2011-05-23 14:56:35 -07:00
Tj Holowaychuk
b789a28581 refactored with nextRoute() 2011-05-23 14:52:43 -07:00
Tj Holowaychuk
4068e7f444 added route Collection tests 2011-05-23 14:13:31 -07:00
Tj Holowaychuk
80e9ffbf5d Refactored router. Closes #639 2011-05-23 13:48:31 -07:00
Tj Holowaychuk
610fc92ca3 arity < 3 considered return-style param 2011-05-23 09:52:11 -07:00
Tj Holowaychuk
0f5dc9bdb2 Added checking of app.param() fn retval
functions returning NaN, == null, or === false are considered
a failure and the next route is executed
2011-05-23 09:38:01 -07:00
TJ Holowaychuk
c24b2faec5 Merge pull request #671 from jpalardy/patch-1
Documentation typo :uri -> :url for logger()
2011-05-22 10:18:01 -07:00
Jonathan Palardy
c407f58dc2 :uri -> :url
Example won't work otherwise. I checked the code :-)
2011-05-21 23:17:57 -07:00
Tj Holowaychuk
380da0e202 Removed module.parent check from express(1) generated app. Closes #670 2011-05-21 14:17:24 -07:00
Tj Holowaychuk
9e5e7a1526 added more route tests 2011-05-20 15:46:36 -07:00
Tj Holowaychuk
9016b5aaae added more route tests 2011-05-20 15:46:05 -07:00
Tj Holowaychuk
45f168e873 Release 2.3.6 2011-05-20 09:42:01 -07:00
Tj Holowaychuk
799938683d Merge branch 'refactor/dev-deps' 2011-05-20 09:39:34 -07:00
Tj Holowaychuk
7128f2d11f fixed last examples 2011-05-20 09:39:29 -07:00
Tj Holowaychuk
0634bf0b0d fixed redis example 2011-05-20 09:38:52 -07:00
Tj Holowaychuk
f9e48c2972 fixed markdown example 2011-05-20 09:29:22 -07:00
Tj Holowaychuk
909960c0b3 refactoring examples more 2011-05-20 09:26:47 -07:00
Tj Holowaychuk
8a7876f4d1 cleaning up examples 2011-05-20 09:16:35 -07:00
Tj Holowaychuk
286c92b13b docs 2011-05-20 09:15:39 -07:00
Tj Holowaychuk
b9872a278f docs 2011-05-20 09:15:35 -07:00
Tj Holowaychuk
1b34fd7efa misc 2011-05-20 09:11:15 -07:00
Tj Holowaychuk
f05c351762 Fixed view caching, should not be enabled in development 2011-05-20 09:01:05 -07:00
Tj Holowaychuk
8323f19e96 fixing examples 2011-05-20 08:56:25 -07:00
Tj Holowaychuk
565eda9ee5 docs 2011-05-20 08:40:26 -07:00
Tj Holowaychuk
7aea7194d1 example docs 2011-05-20 08:39:43 -07:00
Tj Holowaychuk
2f68957c8c fixed tests 2011-05-20 08:37:23 -07:00
Tj Holowaychuk
4ca848e526 ignore node_modules 2011-05-20 08:35:20 -07:00
Tj Holowaychuk
31a8c7c19c test docs 2011-05-20 08:35:11 -07:00
Tj Holowaychuk
f1c435e050 removed support submods 2011-05-20 08:31:13 -07:00
Tj Holowaychuk
fac75a9bff connect 1.4.1 2011-05-20 08:29:02 -07:00
Tj Holowaychuk
4fe03ab223 dev deps 2011-05-20 08:28:02 -07:00
TJ Holowaychuk
c6122da59b Merge pull request #668 from joemccann/master
Simple Update to Markdown example
2011-05-20 08:07:21 -07:00
Tj Holowaychuk
131f658779 Release 2.3.5 2011-05-20 07:32:16 -07:00
Joe McCann
127f77964e Updated markdown example to latest version of node-markdown and modified the compile method. 2011-05-20 09:16:53 -05:00
Tj Holowaychuk
9f2bd30dc7 router.routes 2011-05-19 18:36:41 -07:00
Tj Holowaychuk
6e633b31b4 return bool from req.is() 2011-05-19 09:48:19 -07:00
Tj Holowaychuk
1c65643488 more tests 2011-05-18 17:41:15 -07:00
Tj Holowaychuk
388ad9067a tweak tests 2011-05-17 13:59:24 -07:00
Tj Holowaychuk
f470f0bdc5 export .view as alias for .View
reads better:

   express.view.lookup(...)
2011-05-16 16:27:17 -07:00
Tj Holowaychuk
72384b0523 misc refactor of res.partial() 2011-05-16 15:29:20 -07:00
Tj Holowaychuk
1b199b7d98 lookup docs 2011-05-16 15:26:12 -07:00
Tj Holowaychuk
09b384ea44 Merge branch 'refactor/views' 2011-05-16 15:20:26 -07:00
Tj Holowaychuk
56ae55f987 keep duplicates out of view resolution hint 2011-05-16 15:20:08 -07:00
Tj Holowaychuk
1c360a89ba added views.compile(view, cache, cid, options)
private for now
2011-05-16 15:17:06 -07:00
Tj Holowaychuk
8636dee13e docs 2011-05-16 14:28:11 -07:00
Tj Holowaychuk
70e6baf6fc misc refactoring 2011-05-16 09:32:53 -07:00
Tj Holowaychuk
3588c1eedc docs 2011-05-16 09:27:16 -07:00
Tj Holowaychuk
8d6f167a81 added better middleware docs and use-cases 2011-05-16 09:24:53 -07:00
Tj Holowaychuk
6106188347 docs 2011-05-15 18:01:47 -07:00
Tj Holowaychuk
eeb77541cd Updated jade submodule 2011-05-14 11:53:26 -07:00
Tj Holowaychuk
99b244b47c export View 2011-05-13 19:38:23 -07:00
Tj Holowaychuk
3043672448 added exports.lookup(view, options) to view.js
private for now
2011-05-13 19:37:11 -07:00
Tj Holowaychuk
0477a53c9f misc refactor 2011-05-13 15:47:48 -07:00
Tj Holowaychuk
d9aa7c3bc9 Release 2.3.4 2011-05-08 10:53:57 -07:00
Tj Holowaychuk
986fac583b Merge branch 'master' of github.com:visionmedia/express 2011-05-08 10:52:59 -07:00
Tj Holowaychuk
c6d76086e2 Fixed res.sendfile() bug preventing the transfer of files with spaces
params are decoded so we need to encode before passing to send() which then
in turn decodes it again, however nodes url module chokes on the spaces.
2011-05-08 10:52:16 -07:00
Tj Holowaychuk
e2771364eb Updated connect submodule 2011-05-08 10:45:42 -07:00
Tj Holowaychuk
0d5a63798b added failing test with spaces in filename 2011-05-08 10:40:37 -07:00
TJ Holowaychuk
7d15e2bf52 Merge pull request #653 from darrentorpey/patch-1.
Fixed a typo: "A route is simple a string" => "A route is simply a string
2011-05-04 09:35:49 -07:00
Darren Torpey
31fef407b6 Fixed a typo: "A route is simple a string" => "A route is simply a string" 2011-05-04 04:14:40 -07:00
Tj Holowaychuk
6bef3ef891 misc 2011-05-03 16:48:17 -07:00
Tj Holowaychuk
b806846049 misc 2011-05-03 16:44:17 -07:00
Tj Holowaychuk
bc16020976 added stupid say example 2011-05-03 16:40:20 -07:00
89 changed files with 3074 additions and 1089 deletions

2
.gitignore vendored
View File

@@ -10,3 +10,5 @@ lib-cov
*.swo
benchmarks/graphs
testing.js
node_modules/
testing

30
.gitmodules vendored
View File

@@ -1,30 +0,0 @@
[submodule "support/expresso"]
path = support/expresso
url = git://github.com/visionmedia/expresso.git
[submodule "support/haml"]
path = support/haml
url = git://github.com/visionmedia/haml.js.git
[submodule "support/ejs"]
path = support/ejs
url = git://github.com/visionmedia/ejs.git
[submodule "support/connect-form"]
path = support/connect-form
url = git://github.com/visionmedia/connect-form.git
[submodule "support/connect"]
path = support/connect
url = git://github.com/senchalabs/connect.git
[submodule "support/should"]
path = support/should
url = git://github.com/visionmedia/should.js.git
[submodule "support/formidable"]
path = support/formidable
url = git://github.com/felixge/node-formidable.git
[submodule "support/jade"]
path = support/jade
url = git://github.com/visionmedia/jade.git
[submodule "support/qs"]
path = support/qs
url = git://github.com/visionmedia/node-querystring.git
[submodule "support/mime"]
path = support/mime
url = https://github.com/bentomas/node-mime.git

View File

@@ -1,4 +1,191 @@
2.5.9/ 2012-04-02
==================
* Added support for PURGE request method [pbuyle]
* Fixed `express(1)` generated app `app.address()` before `listening` [mmalecki]
2.5.8 / 2012-02-08
==================
* Update mkdirp dep. Closes #991
2.5.7 / 2012-02-06
==================
* Fixed `app.all` duplicate DELETE requests [mscdex]
2.5.6 / 2012-01-13
==================
* Updated hamljs dev dep. Closes #953
2.5.5 / 2012-01-08
==================
* Fixed: set `filename` on cached templates [matthewleon]
2.5.4 / 2012-01-02
==================
* Fixed `express(1)` eol on 0.4.x. Closes #947
2.5.3 / 2011-12-30
==================
* Fixed `req.is()` when a charset is present
2.5.2 / 2011-12-10
==================
* Fixed: express(1) LF -> CRLF for windows
2.5.1 / 2011-11-17
==================
* Changed: updated connect to 1.8.x
* Removed sass.js support from express(1)
2.5.0 / 2011-10-24
==================
* Added ./routes dir for generated app by default
* Added npm install reminder to express(1) app gen
* Added 0.5.x support
* Removed `make test-cov` since it wont work with node 0.5.x
* Fixed express(1) public dir for windows. Closes #866
2.4.7 / 2011-10-05
==================
* Added mkdirp to express(1). Closes #795
* Added simple _json-config_ example
* Added shorthand for the parsed request's pathname via `req.path`
* Changed connect dep to 1.7.x to fix npm issue...
* Fixed `res.redirect()` __HEAD__ support. [reported by xerox]
* Fixed `req.flash()`, only escape args
* Fixed absolute path checking on windows. Closes #829 [reported by andrewpmckenzie]
2.4.6 / 2011-08-22
==================
* Fixed multiple param callback regression. Closes #824 [reported by TroyGoode]
2.4.5 / 2011-08-19
==================
* Added support for routes to handle errors. Closes #809
* Added `app.routes.all()`. Closes #803
* Added "basepath" setting to work in conjunction with reverse proxies etc.
* Refactored `Route` to use a single array of callbacks
* Added support for multiple callbacks for `app.param()`. Closes #801
Closes #805
* Changed: removed .call(self) for route callbacks
* Dependency: `qs >= 0.3.1`
* Fixed `res.redirect()` on windows due to `join()` usage. Closes #808
2.4.4 / 2011-08-05
==================
* Fixed `res.header()` intention of a set, even when `undefined`
* Fixed `*`, value no longer required
* Fixed `res.send(204)` support. Closes #771
2.4.3 / 2011-07-14
==================
* Added docs for `status` option special-case. Closes #739
* Fixed `options.filename`, exposing the view path to template engines
2.4.2. / 2011-07-06
==================
* Revert "removed jsonp stripping" for XSS
2.4.1 / 2011-07-06
==================
* Added `res.json()` JSONP support. Closes #737
* Added _extending-templates_ example. Closes #730
* Added "strict routing" setting for trailing slashes
* Added support for multiple envs in `app.configure()` calls. Closes #735
* Changed: `res.send()` using `res.json()`
* Changed: when cookie `path === null` don't default it
* Changed; default cookie path to "home" setting. Closes #731
* Removed _pids/logs_ creation from express(1)
2.4.0 / 2011-06-28
==================
* Added chainable `res.status(code)`
* Added `res.json()`, an explicit version of `res.send(obj)`
* Added simple web-service example
2.3.12 / 2011-06-22
==================
* \#express is now on freenode! come join!
* Added `req.get(field, param)`
* Added links to Japanese documentation, thanks @hideyukisaito!
* Added; the `express(1)` generated app outputs the env
* Added `content-negotiation` example
* Dependency: connect >= 1.5.1 < 2.0.0
* Fixed view layout bug. Closes #720
* Fixed; ignore body on 304. Closes #701
2.3.11 / 2011-06-04
==================
* Added `npm test`
* Removed generation of dummy test file from `express(1)`
* Fixed; `express(1)` adds express as a dep
* Fixed; prune on `prepublish`
2.3.10 / 2011-05-27
==================
* Added `req.route`, exposing the current route
* Added _package.json_ generation support to `express(1)`
* Fixed call to `app.param()` function for optional params. Closes #682
2.3.9 / 2011-05-25
==================
* Fixed bug-ish with `../' in `res.partial()` calls
2.3.8 / 2011-05-24
==================
* Fixed `app.options()`
2.3.7 / 2011-05-23
==================
* Added route `Collection`, ex: `app.get('/user/:id').remove();`
* Added support for `app.param(fn)` to define param logic
* Removed `app.param()` support for callback with return value
* Removed module.parent check from express(1) generated app. Closes #670
* Refactored router. Closes #639
2.3.6 / 2011-05-20
==================
* Changed; using devDependencies instead of git submodules
* Fixed redis session example
* Fixed markdown example
* Fixed view caching, should not be enabled in development
2.3.5 / 2011-05-20
==================
* Added export `.view` as alias for `.View`
2.3.4 / 2011-05-08
==================
* Added `./examples/say`
* Fixed `res.sendfile()` bug preventing the transfer of files with spaces
2.3.3 / 2011-05-03
==================

View File

@@ -1,20 +1,10 @@
DOCS = $(shell find docs/*.md)
HTMLDOCS =$(DOCS:.md=.html)
HTMLDOCS = $(DOCS:.md=.html)
TESTS = $(shell find test/*.test.js)
test:
@NODE_ENV=test ./support/expresso/bin/expresso \
-I lib \
-I support \
-I support/connect/lib \
-I support/haml/lib \
-I support/jade/lib \
-I support/ejs/lib \
$(TESTFLAGS) \
test/*.test.js
test-cov:
@TESTFLAGS=--cov $(MAKE) test
@NODE_ENV=test ./node_modules/.bin/expresso $(TESTS)
docs: $(HTMLDOCS)
@ echo "... generating TOC"
@@ -36,4 +26,4 @@ site:
docclean:
rm -f docs/*.{1,html}
.PHONY: site test test-cov docs docclean
.PHONY: site test docs docclean

View File

@@ -20,6 +20,23 @@ or to access the `express(1)` executable install globally:
$ npm install -g express
## Quick Start
The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
Create the app:
$ npm install -g express
$ express /tmp/foo && cd /tmp/foo
Install dependencies:
$ npm install -d
Start the server:
$ node app.js
## Features
* Robust routing
@@ -58,13 +75,17 @@ The following are the major contributors of Express (in no specific order).
## More Information
* #express on freenode
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js with ease
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
* [express-params](https://github.com/visionmedia/express-params) param pre-condition functions
* [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
* [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito)
* Screencast - [Introduction](http://bit.ly/eRYu0O)
* Screencast - [View Partials](http://bit.ly/dU13Fx)
* Screencast - [Route Specific Middleware](http://bit.ly/hX4IaH)
@@ -74,7 +95,29 @@ The following are the major contributors of Express (in no specific order).
Express 1.x is compatible with node 0.2.x and connect < 1.0.
Express 2.x is compatible with node 0.4.x and connect 1.x
Express 2.x is compatible with node 0.4.x or 0.6.x, and connect 1.x
Express 3.x (master) will be compatible with node 0.6.x and connect 2.x
## Viewing Examples
First install the dev dependencies to install all the example / test suite deps:
$ npm install
then run whichever tests you want:
$ node examples/jade/app.js
## Running Tests
To run the test suite first invoke the following command within the repo, installing the development dependencies:
$ npm install
then run the tests:
$ make test
## License

View File

@@ -5,13 +5,15 @@
*/
var fs = require('fs')
, exec = require('child_process').exec;
, os = require('os')
, exec = require('child_process').exec
, mkdirp = require('mkdirp');
/**
* Framework version.
*/
var version = '2.3.3';
var version = '2.5.8';
/**
* Add session support.
@@ -25,6 +27,14 @@ var sessions = false;
var cssEngine;
/**
* End-of-line code.
*/
var eol = os.platform
? ('win32' == os.platform() ? '\r\n' : '\n')
: '\n';
/**
* Template engine to utilize.
*/
@@ -42,11 +52,26 @@ var usage = ''
+ ' Options:\n'
+ ' -s, --sessions add session support\n'
+ ' -t, --template <engine> add template <engine> support (jade|ejs). default=jade\n'
+ ' -c, --css <engine> add stylesheet <engine> support (less|sass|stylus). default=plain css\n'
+ ' -c, --css <engine> add stylesheet <engine> support (stylus). default=plain css\n'
+ ' -v, --version output framework version\n'
+ ' -h, --help output help information\n'
;
/**
* Routes index template.
*/
var index = [
''
, '/*'
, ' * GET home page.'
, ' */'
, ''
, 'exports.index = function(req, res){'
, ' res.render(\'index\', { title: \'Express\' })'
, '};'
].join(eol);
/**
* Jade layout template.
*/
@@ -58,7 +83,7 @@ var jadeLayout = [
, ' title= title'
, ' link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')'
, ' body!= body'
].join('\n');
].join(eol);
/**
* Jade index template.
@@ -67,7 +92,7 @@ var jadeLayout = [
var jadeIndex = [
'h1= title'
, 'p Welcome to #{title}'
].join('\n');
].join(eol);
/**
* EJS layout template.
@@ -84,7 +109,7 @@ var ejsLayout = [
, ' <%- body %>'
, ' </body>'
, '</html>'
].join('\n');
].join(eol);
/**
* EJS index template.
@@ -93,7 +118,7 @@ var ejsLayout = [
var ejsIndex = [
'<h1><%= title %></h1>'
, '<p>Welcome to <%= title %></p>'
].join('\n');
].join(eol);
/**
* Default css template.
@@ -108,34 +133,7 @@ var css = [
, 'a {'
, ' color: #00B7FF;'
, '}'
].join('\n');
/**
* Default less template.
*/
var less = [
'body {'
, ' padding: 50px;'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
, '}'
, ''
, 'a {'
, ' color: #00B7FF;'
, '}'
].join('\n');
/**
* Default sass template.
*/
var sass = [
'body'
, ' :padding 50px'
, ' :font 14px "Lucida Grande", Helvetica, Arial, sans-serif'
, 'a'
, ' :color #00B7FF'
].join('\n');
].join(eol);
/**
* Default stylus template.
@@ -143,38 +141,11 @@ var sass = [
var stylus = [
'body'
, ' padding 50px'
, ' font 14px "Lucida Grande", Helvetica, Arial, sans-serif'
, ' padding: 50px'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif'
, 'a'
, ' color #00B7FF'
].join('\n');
/**
* App test template.
*/
var appTest = [
""
, "// Run $ expresso"
, ""
, "/**"
, " * Module dependencies."
, " */"
, ""
, "var app = require('../app')"
, " , assert = require('assert');"
, "",
, "module.exports = {"
, " 'GET /': function(){"
, " assert.response(app,"
, " { url: '/' },"
, " { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }},"
, " function(res){"
, " assert.includes(res.body, '<title>Express</title>');"
, " });"
, " }"
, "};"
].join('\n');
, ' color: #00B7FF'
].join(eol);
/**
* App template.
@@ -186,7 +157,8 @@ var app = [
, ' * Module dependencies.'
, ' */'
, ''
, 'var express = require(\'express\');'
, 'var express = require(\'express\')'
, ' , routes = require(\'./routes\');'
, ''
, 'var app = module.exports = express.createServer();'
, ''
@@ -202,29 +174,22 @@ var app = [
, '});'
, ''
, 'app.configure(\'development\', function(){'
, ' app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); '
, ' app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));'
, '});'
, ''
, 'app.configure(\'production\', function(){'
, ' app.use(express.errorHandler()); '
, ' app.use(express.errorHandler());'
, '});'
, ''
, '// Routes'
, ''
, 'app.get(\'/\', function(req, res){'
, ' res.render(\'index\', {'
, ' title: \'Express\''
, ' });'
, 'app.get(\'/\', routes.index);'
, ''
, 'app.listen(3000, function(){'
, ' console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);'
, '});'
, ''
, '// Only listen on $ node app.js'
, ''
, 'if (!module.parent) {'
, ' app.listen(3000);'
, ' console.log("Express server listening on port %d", app.address().port);'
, '}'
, ''
].join('\n');
].join(eol);
// Parse arguments
@@ -290,9 +255,16 @@ while (args.length) {
*/
function createApplicationAt(path) {
console.log();
process.on('exit', function(){
console.log();
console.log(' dont forget to install dependencies:');
console.log(' $ cd %s && npm install', path);
console.log();
});
mkdir(path, function(){
mkdir(path + '/pids');
mkdir(path + '/logs');
mkdir(path + '/public');
mkdir(path + '/public/javascripts');
mkdir(path + '/public/images');
mkdir(path + '/public/stylesheets', function(){
@@ -300,16 +272,15 @@ function createApplicationAt(path) {
case 'stylus':
write(path + '/public/stylesheets/style.styl', stylus);
break;
case 'less':
write(path + '/public/stylesheets/style.less', less);
break;
case 'sass':
write(path + '/public/stylesheets/style.sass', sass);
break;
default:
write(path + '/public/stylesheets/style.css', css);
}
});
mkdir(path + '/routes', function(){
write(path + '/routes/index.js', index);
});
mkdir(path + '/views', function(){
switch (templateEngine) {
case 'ejs':
@@ -322,18 +293,11 @@ function createApplicationAt(path) {
break;
}
});
mkdir(path + '/test', function(){
write(path + '/test/app.test.js', appTest);
});
// CSS Engine support
switch (cssEngine) {
case 'sass':
case 'less':
app = app.replace('{css}', '\n app.use(express.compiler({ src: __dirname + \'/public\', enable: [\'' + cssEngine + '\'] }));');
break;
case 'stylus':
app = app.replace('{css}', '\n app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));');
app = app.replace('{css}', eol + ' app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));');
break;
default:
app = app.replace('{css}', '');
@@ -341,25 +305,27 @@ function createApplicationAt(path) {
// Session support
app = app.replace('{sess}', sessions
? '\n app.use(express.cookieParser());\n app.use(express.session({ secret: \'your secret here\' }));'
? eol + ' app.use(express.cookieParser());' + eol + ' app.use(express.session({ secret: \'your secret here\' }));'
: '');
// Template support
app = app.replace(':TEMPLATE', templateEngine);
write(path + '/app.js', app);
// package.json
var json = '{' + eol;
json += ' "name": "application-name"' + eol;
json += ' , "version": "0.0.1"' + eol;
json += ' , "private": true' + eol;
json += ' , "dependencies": {' + eol;
json += ' "express": "' + version + '"' + eol;
if (cssEngine) json += ' , "' + cssEngine + '": ">= 0.0.1"' + eol;
if (templateEngine) json += ' , "' + templateEngine + '": ">= 0.0.1"' + eol;
json += ' }' + eol;
json += '}';
// Suggestions
process.on('exit', function(){
if (cssEngine) {
console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
, cssEngine
, cssEngine);
}
console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
, templateEngine
, templateEngine);
});
write(path + '/package.json', json);
write(path + '/app.js', app);
});
}
@@ -432,9 +398,9 @@ function prompt(msg, fn) {
*/
function mkdir(path, fn) {
exec('mkdir -p ' + path, function(err){
mkdirp(path, 0755, function(err){
if (err) throw err;
console.log(' \x1b[36mcreate\x1b[0m : ' + path);
console.log(' \033[36mcreate\033[0m : ' + path);
fn && fn();
});
}

View File

@@ -1,7 +1,21 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25235225-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#tagline {
margin-left: 75px;

View File

@@ -1,7 +1,21 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25235225-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#tagline {
margin-left: 75px;
@@ -192,10 +206,9 @@
</ul>
<h3>Development Dependencies</h3>
<p>Express development dependencies are stored within the <em>./support</em> directory. To
update them execute:</p>
<p>First install the dev dependencies by executing the following command in the repo&rsquo;s directory:</p>
<pre><code>$ git submodule update --init
<pre><code>$ npm install
</code></pre>
<h3>Running Tests</h3>

View File

@@ -1,10 +1,9 @@
### Development Dependencies
Express development dependencies are stored within the _./support_ directory. To
update them execute:
First install the dev dependencies by executing the following command in the repo's directory:
$ git submodule update --init
$ npm install
### Running Tests

View File

@@ -1,7 +1,21 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25235225-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#tagline {
margin-left: 75px;

View File

@@ -1,7 +1,21 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25235225-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#tagline {
margin-left: 75px;
@@ -202,6 +216,7 @@
<li><a href="#req.accepts()">accepts()</a></li>
<li><a href="#req.is()">is()</a></li>
<li><a href="#req.param()">param()</a></li>
<li><a href="#req.get()">get()</a></li>
<li><a href="#req.flash()">flash()</a></li>
<li><a href="#req.isxmlhttprequest">isXMLHttpRequest</a></li>
</ul></li>
@@ -213,6 +228,7 @@
<li><a href="#res.sendfile()">sendfile()</a></li>
<li><a href="#res.download()">download()</a></li>
<li><a href="#res.send()">send()</a></li>
<li><a href="#res.json()">json()</a></li>
<li><a href="#res.redirect()">redirect()</a></li>
<li><a href="#res.cookie()">cookie()</a></li>
<li><a href="#res.clearcookie()">clearCookie()</a></li>
@@ -255,6 +271,31 @@
<pre><code>$ npm install express
</code></pre>
<p>or to access the <code>express(1)</code> executable install globally:</p>
<pre><code>$ npm install -g express
</code></pre>
<h2>Quick Start</h2>
<p> The quickest way to get started with express is to utilize the executable <code>express(1)</code> to generate an application as shown below:</p>
<p> Create the app:</p>
<pre><code>$ npm install -g express
$ express /tmp/foo &amp;&amp; cd /tmp/foo
</code></pre>
<p> Install dependencies:</p>
<pre><code>$ npm install -d
</code></pre>
<p> Start the server:</p>
<pre><code>$ node app.js
</code></pre>
<h3 id="creating-a server">Creating A Server</h3>
<p> To create an instance of the <em>express.HTTPServer</em>, simply invoke the <em>createServer()</em> method. With our instance <em>app</em> we can then define routes based on the HTTP verbs, in this example <em>app.get()</em>.</p>
@@ -306,6 +347,13 @@ app.configure('production', function(){
});
</code></pre>
<p>For similar environments you may also pass several env strings:</p>
<pre><code>app.configure('stage', 'prod', function(){
// config
});
</code></pre>
<p>For internal and arbitrary settings Express provides the <em>set(key[, val])</em>, <em>enable(key)</em>, <em>disable(key)</em> methods:</p>
<pre><code> app.configure(function(){
@@ -336,10 +384,14 @@ app.configure('production', function(){
<p>Express supports the following settings out of the box:</p>
<ul>
<li><em>home</em> Application base path used for <em>res.redirect()</em> and transparently handling mounted apps.</li>
<li><em>basepath</em> Application base path used for <em>res.redirect()</em> and transparently handling mounted apps.</li>
<li><em>views</em> Root views directory defaulting to <strong>CWD/views</strong></li>
<li><em>view engine</em> Default view engine name for views rendered without extensions</li>
<li><em>view options</em> An object specifying global view options</li>
<li><em>view cache</em> Enable view caching (enabled in production)</li>
<li><em>case sensitive routes</em> Enable case-sensitive routing</li>
<li><em>strict routing</em> When enabled trailing slashes are no longer ignored</li>
<li><em>jsonp callback</em> Enable <em>res.send()</em> / <em>res.json()</em> transparent jsonp support</li>
</ul>
@@ -431,7 +483,7 @@ app.post('/', function(req, res){
app.listen(3000);
</code></pre>
<p>Typically we may use a &ldquo;dumb&rdquo; placeholder such as &ldquo;/user/:id&rdquo; which has no restrictions, however say for example we are limiting a user id to digits, we may use <em>&lsquo;/user/:id(\d+)&rsquo;</em> which will <em>not</em> match unless the placeholder value contains only digits.</p>
<p>Typically we may use a &ldquo;dumb&rdquo; placeholder such as &ldquo;/user/:id&rdquo; which has no restrictions, however say for example we are limiting a user id to digits, we may use <em>&lsquo;/user/:id([0-9]+)&rsquo;</em> which will <em>not</em> match unless the placeholder value contains only digits.</p>
<h3 id="passing-route control">Passing Route Control</h3>
@@ -503,7 +555,7 @@ var app = express.createServer(
<p>Alternatively we can <em>use()</em> them which is useful when adding middleware within <em>configure()</em> blocks, in a progressive manor.</p>
<pre><code>app.use(express.logger({ format: ':method :uri' }));
<pre><code>app.use(express.logger({ format: ':method :url' }));
</code></pre>
<p>Typically with connect middleware you would <em>require(&lsquo;connect&rsquo;)</em> like so:</p>
@@ -519,6 +571,34 @@ app.use(connect.bodyParser());
app.use(express.bodyParser());
</code></pre>
<p>Middleware ordering is important, when Connect receives a request the <em>first</em> middleware we pass to <em>createServer()</em> or <em>use()</em> is executed with three parameters, <em>request</em>, <em>response</em>, and a callback function usually named <em>next</em>. When <em>next()</em> is invoked the second middleware will then have it&rsquo;s turn and so on. This is important to note because many middleware depend on each other, for example <em>methodOverride()</em> checks <em>req.body.</em>method<em> for the HTTP method override, however </em>bodyParser()<em> parses the request body and populates </em>req.body<em>. Another example of this is cookie parsing and session support, we must first </em>use()<em> </em>cookieParser()<em> followed by </em>session()_.</p>
<p>Many Express applications may contain the line <em>app.use(app.router)</em>, while this may appear strange, it&rsquo;s simply the middleware function that contains all defined routes, and performs route lookup based on the current request url and HTTP method. Express allows you to position this middleware, though by default it will be added to the bottom. By positioning the router, we can alter middleware precedence, for example we may want to add error reporting as the <em>last</em> middleware so that any exception passed to <em>next()</em> will be handled by it, or perhaps we want static file serving to have low precedence, allowing our routes to intercept requests to a static file to count downloads etc. This may look a little like below</p>
<pre><code>app.use(express.logger(...));
app.use(express.bodyParser(...));
app.use(express.cookieParser(...));
app.use(express.session(...));
app.use(app.router);
app.use(express.static(...));
app.use(express.errorHandler(...));
</code></pre>
<p>First we add <em>logger()</em> so that it may wrap node&rsquo;s <em>req.end()</em> method, providing us with response-time data. Next the request&rsquo;s body will be parsed (if any), followed by cookie parsing and session support, meaning <em>req.session</em> will be defined by the time we hit our routes in <em>app.router</em>. If a request such as <em>GET /javascripts/jquery.js</em> is handled by our routes, and we do not call <em>next()</em> then the <em>static()</em> middleware will never see this request, however if were to define a route as shown below, we can record stats, refuse downloads, consume download credits etc.</p>
<pre><code>var downloads = {};
app.use(app.router);
app.use(express.static(__dirname + '/public'));
app.get('/*', function(req, res, next){
var file = req.params[0];
downloads[file] = downloads[file] || 0;
downloads[file]++;
next();
});
</code></pre>
<h3 id="route-middleware">Route Middleware</h3>
<p>Routes may utilize route-specific middleware by passing one or more additional callbacks (or arrays) to the method. This feature is extremely useful for restricting access, loading data used by the route etc.</p>
@@ -595,6 +675,8 @@ app.get('/', all, function(){});
<p>For this example in full, view the <a href="http://github.com/visionmedia/express/blob/master/examples/route-middleware/app.js">route middleware example</a> in the repository.</p>
<p>There are times when we may want to &ldquo;skip&rdquo; passed remaining route middleware, but continue matching subsequent routes. To do this we invoke <code>next()</code> with the string &ldquo;route&rdquo; <code>next('route')</code>. If no remaining routes match the request url then Express will respond with 404 Not Found.</p>
<h3 id="http-methods">HTTP Methods</h3>
<p>We have seen <em>app.get()</em> a few times, however Express also exposes other familiar HTTP verbs in the same manor, such as <em>app.post()</em>, <em>app.del()</em>, etc.</p>
@@ -740,16 +822,6 @@ is present, which is useful for developing apps that rely heavily on client-side
});
</code></pre>
<p>For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to <em>next(err)</em>.</p>
<pre><code>app.param('number', function(n){ return parseInt(n, 10); });
</code></pre>
<p>We may also apply the same callback to several placeholders, for example a route GET <em>/commits/:from-:to</em> are both numbers, so we may define them as an array:</p>
<pre><code>app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
</code></pre>
<h3 id="view-rendering">View Rendering</h3>
<p>View filenames take the form &ldquo;&lt;name&gt;.&lt;engine&gt;&rdquo;, where &lt;engine&gt; is the name
@@ -884,14 +956,14 @@ app.use(express.session({ secret: "keyboard cat" }));
<p>By default the <em>session</em> middleware uses the memory store bundled with Connect, however many implementations exist. For example <a href="http://github.com/visionmedia/connect-redis">connect-redis</a> supplies a <a href="http://code.google.com/p/redis/">Redis</a> session store and can be used as shown below:</p>
<pre><code>var RedisStore = require('connect-redis');
<pre><code>var RedisStore = require('connect-redis')(express);
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
</code></pre>
<p>Now the <em>req.session</em> and <em>req.sessionStore</em> properties will be accessible to all routes and subsequent middleware. Properties on <em>req.session</em> are automatically saved on a response, so for example if we wish to shopping cart data:</p>
<pre><code>var RedisStore = require('connect-redis');
<pre><code>var RedisStore = require('connect-redis')(express);
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
@@ -1035,6 +1107,18 @@ can perform any request assertion you wish.</p>
should be an object. This can be done by using
the _express.bodyParser middleware.</p>
<h3 id="req.get()">req.get(field, param)</h3>
<p> Get <em>field</em>&rsquo;s <em>param</em> value, defaulting to &lsquo;&rsquo; when the <em>param</em>
or <em>field</em> is not present.</p>
<pre><code> req.get('content-disposition', 'filename');
// =&gt; "something.png"
req.get('Content-Type', 'boundary');
// =&gt; "--foo-bar-baz"
</code></pre>
<h3 id="req.flash()">req.flash(type[, msg])</h3>
<p>Queue flash <em>msg</em> of the given <em>type</em>.</p>
@@ -1155,7 +1239,7 @@ an error occurs, or when the transfer is complete. By default failures call <cod
});
</code></pre>
<h3 id="res.download()">res.download(file[, filename[, callback]])</h3>
<h3 id="res.download()">res.download(file[, filename[, callback[, callback2]]])</h3>
<p>Transfer the given <em>file</em> as an attachment with optional alternative <em>filename</em>.</p>
@@ -1169,13 +1253,22 @@ res.download('path/to/image.png', 'foo.png');
res.sendfile(file);
</code></pre>
<p>An optional callback may be supplied as either the second or third argument, which is passed to <em>res.sendfile()</em>:</p>
<p>An optional callback may be supplied as either the second or third argument, which is passed to <em>res.sendfile()</em>. Within this callback you may still respond, as the header has not been sent.</p>
<pre><code>res.download(path, 'expenses.doc', function(err){
// handle
});
</code></pre>
<p>An optional second callback, <em>callback2</em> may be given to allow you to act on connection related errors, however you should not attempt to respond.</p>
<pre><code>res.download(path, function(err){
// error or finished
}, function(err){
// connection related error
});
</code></pre>
<h3 id="res.send()">res.send(body|status[, headers|status[, status]])</h3>
<p>The <em>res.send()</em> method is a high level response utility allowing you to pass
@@ -1196,6 +1289,19 @@ it will not be set again.</p>
<p>Note that this method <em>end()</em>s the response, so you will want to use node&rsquo;s <em>res.write()</em> for multiple writes or streaming.</p>
<h3 id="res.json()">res.json(obj[, headers|status[, status]])</h3>
<p> Send a JSON response with optional <em>headers</em> and <em>status</em>. This method
is ideal for JSON-only APIs, however <em>res.send(obj)</em> will send JSON as
well, though not ideal for cases when you want to send for example a string
as JSON, since the default for <em>res.send(string)</em> is text/html.</p>
<pre><code>res.json(null);
res.json({ user: 'tj' });
res.json('oh noes!', 500);
res.json('I dont have that', 404);
</code></pre>
<h3 id="res.redirect()">res.redirect(url[, status])</h3>
<p>Redirect to the given <em>url</em> with a default response <em>status</em> of 302.</p>
@@ -1209,11 +1315,12 @@ res.redirect('back');
<p>Express supports &ldquo;redirect mapping&rdquo;, which by default provides <em>home</em>, and <em>back</em>.
The <em>back</em> map checks the <em>Referrer</em> and <em>Referer</em> headers, while <em>home</em> utilizes
the &ldquo;home&rdquo; setting and defaults to &ldquo;/&rdquo;.</p>
the &ldquo;basepath&rdquo; setting and defaults to &ldquo;/&rdquo;.</p>
<h3 id="res.cookie()">res.cookie(name, val[, options])</h3>
<p>Sets the given cookie <em>name</em> to <em>val</em>, with options <em>httpOnly</em>, <em>secure</em>, <em>expires</em> etc.</p>
<p>Sets the given cookie <em>name</em> to <em>val</em>, with options <em>httpOnly</em>, <em>secure</em>, <em>expires</em> etc. The <em>path</em> option defaults to the app&rsquo;s &ldquo;basepath&rdquo; setting, which
is typically &ldquo;/&rdquo;.</p>
<pre><code>// "Remember me" for 15 minutes
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
@@ -1233,9 +1340,10 @@ app.get('/', function(req, res){
});
</code></pre>
<h3 id="res.clearcookie()">res.clearCookie(name)</h3>
<h3 id="res.clearcookie()">res.clearCookie(name[, options])</h3>
<p>Clear cookie <em>name</em> by setting &ldquo;expires&rdquo; far in the past.</p>
<p>Clear cookie <em>name</em> by setting &ldquo;expires&rdquo; far in the past. Much like
<em>res.cookie()</em> the <em>path</em> option also defaults to the &ldquo;basepath&rdquo; setting.</p>
<pre><code>res.clearCookie('rememberme');
</code></pre>
@@ -1252,6 +1360,16 @@ automatically, however otherwise a response of <em>200</em> and <em>text/html</e
res.render('index', { layout: false, user: user });
</code></pre>
<p>This <em>options</em> object is also considered an &ldquo;options&rdquo; object. For example
when you pass the <em>status</em> local, it&rsquo;s not only available to the view, it
sets the response status to this number. This is also useful if a template
engine accepts specific options, such as <em>debug</em>, or <em>compress</em>. Below
is an example of how one might render an error page, passing the <em>status</em> for
display, as well as it setting <em>res.statusCode</em>.</p>
<pre><code> res.render('error', { status: 500, message: 'Internal Server Error' });
</code></pre>
<h3 id="res.partial()">res.partial(view[, options])</h3>
<p>Render <em>view</em> partial with the given <em>options</em>. This method is always available
@@ -1524,10 +1642,7 @@ as well as the <em>name()</em> function exposed.</p>
<p>Express also provides a few locals by default:</p>
<pre><code>- `settings` the app's settings object
- `filename` the view's filename
- `request` the request object
- `response` the response object
- `app` the application itself
- `layout(path)` specify the layout from within a view
</code></pre>
<p>This method is aliased as <em>app.locals()</em>.</p>

View File

@@ -1,8 +1,28 @@
### Installation
$ npm install express
or to access the `express(1)` executable install globally:
$ npm install -g express
## Quick Start
The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
Create the app:
$ npm install -g express
$ express /tmp/foo && cd /tmp/foo
Install dependencies:
$ npm install -d
Start the server:
$ node app.js
### Creating A Server
To create an instance of the _express.HTTPServer_, simply invoke the _createServer()_ method. With our instance _app_ we can then define routes based on the HTTP verbs, in this example _app.get()_.
@@ -51,6 +71,12 @@ otherwise the first call to _app.get()_, _app.post()_, etc will mount the routes
app.use(express.errorHandler());
});
For similar environments you may also pass several env strings:
app.configure('stage', 'prod', function(){
// config
});
For internal and arbitrary settings Express provides the _set(key[, val])_, _enable(key)_, _disable(key)_ methods:
app.configure(function(){
@@ -78,12 +104,14 @@ This is _very_ important, as many caching mechanisms are _only enabled_ when in
Express supports the following settings out of the box:
* _home_ Application base path used for _res.redirect()_ and transparently handling mounted apps.
* _basepath_ Application base path used for _res.redirect()_ and transparently handling mounted apps.
* _views_ Root views directory defaulting to **CWD/views**
* _view engine_ Default view engine name for views rendered without extensions
* _view options_ An object specifying global view options
* _view cache_ Enable view caching (enabled in production)
* _case sensitive routes_ Enable case-sensitive routing
* _strict routing_ When enabled trailing slashes are no longer ignored
* _jsonp callback_ Enable _res.send()_ / _res.json()_ transparent jsonp support
### Routing
@@ -167,7 +195,7 @@ For example we can __POST__ some json, and echo the json back using the _bodyPar
app.listen(3000);
Typically we may use a "dumb" placeholder such as "/user/:id" which has no restrictions, however say for example we are limiting a user id to digits, we may use _'/user/:id(\\\\d+)'_ which will _not_ match unless the placeholder value contains only digits.
Typically we may use a "dumb" placeholder such as "/user/:id" which has no restrictions, however say for example we are limiting a user id to digits, we may use _'/user/:id([0-9]+)'_ which will _not_ match unless the placeholder value contains only digits.
### Passing Route Control
@@ -236,7 +264,7 @@ passed to _express.createServer()_ as you would with a regular Connect server. F
Alternatively we can _use()_ them which is useful when adding middleware within _configure()_ blocks, in a progressive manor.
app.use(express.logger({ format: ':method :uri' }));
app.use(express.logger({ format: ':method :url' }));
Typically with connect middleware you would _require('connect')_ like so:
@@ -249,6 +277,33 @@ This is somewhat annoying, so express re-exports these middleware properties, ho
app.use(express.logger());
app.use(express.bodyParser());
Middleware ordering is important, when Connect receives a request the _first_ middleware we pass to _createServer()_ or _use()_ is executed with three parameters, _request_, _response_, and a callback function usually named _next_. When _next()_ is invoked the second middleware will then have it's turn and so on. This is important to note because many middleware depend on each other, for example _methodOverride()_ checks _req.body._method_ for the HTTP method override, however _bodyParser()_ parses the request body and populates _req.body_. Another example of this is cookie parsing and session support, we must first _use()_ _cookieParser()_ followed by _session()_.
Many Express applications may contain the line _app.use(app.router)_, while this may appear strange, it's simply the middleware function that contains all defined routes, and performs route lookup based on the current request url and HTTP method. Express allows you to position this middleware, though by default it will be added to the bottom. By positioning the router, we can alter middleware precedence, for example we may want to add error reporting as the _last_ middleware so that any exception passed to _next()_ will be handled by it, or perhaps we want static file serving to have low precedence, allowing our routes to intercept requests to a static file to count downloads etc. This may look a little like below
app.use(express.logger(...));
app.use(express.bodyParser(...));
app.use(express.cookieParser(...));
app.use(express.session(...));
app.use(app.router);
app.use(express.static(...));
app.use(express.errorHandler(...));
First we add _logger()_ so that it may wrap node's _req.end()_ method, providing us with response-time data. Next the request's body will be parsed (if any), followed by cookie parsing and session support, meaning _req.session_ will be defined by the time we hit our routes in _app.router_. If a request such as _GET /javascripts/jquery.js_ is handled by our routes, and we do not call _next()_ then the _static()_ middleware will never see this request, however if were to define a route as shown below, we can record stats, refuse downloads, consume download credits etc.
var downloads = {};
app.use(app.router);
app.use(express.static(__dirname + '/public'));
app.get('/*', function(req, res, next){
var file = req.params[0];
downloads[file] = downloads[file] || 0;
downloads[file]++;
next();
});
### Route Middleware
Routes may utilize route-specific middleware by passing one or more additional callbacks (or arrays) to the method. This feature is extremely useful for restricting access, loading data used by the route etc.
@@ -454,14 +509,6 @@ Doing so, as mentioned drastically improves our route readability, and allows us
res.send('user ' + req.user.name);
});
For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to _next(err)_.
app.param('number', function(n){ return parseInt(n, 10); });
We may also apply the same callback to several placeholders, for example a route GET _/commits/:from-:to_ are both numbers, so we may define them as an array:
app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
### View Rendering
View filenames take the form "&lt;name&gt;.&lt;engine&gt;", where &lt;engine&gt; is the name
@@ -576,13 +623,13 @@ Sessions support can be added by using Connect's _session_ middleware. To do so
By default the _session_ middleware uses the memory store bundled with Connect, however many implementations exist. For example [connect-redis](http://github.com/visionmedia/connect-redis) supplies a [Redis](http://code.google.com/p/redis/) session store and can be used as shown below:
var RedisStore = require('connect-redis');
var RedisStore = require('connect-redis')(express);
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
Now the _req.session_ and _req.sessionStore_ properties will be accessible to all routes and subsequent middleware. Properties on _req.session_ are automatically saved on a response, so for example if we wish to shopping cart data:
var RedisStore = require('connect-redis');
var RedisStore = require('connect-redis')(express);
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
@@ -714,6 +761,17 @@ To utilize urlencoded request bodies, _req.body_
should be an object. This can be done by using
the _express.bodyParser middleware.
### req.get(field, param)
Get _field_'s _param_ value, defaulting to '' when the _param_
or _field_ is not present.
req.get('content-disposition', 'filename');
// => "something.png"
req.get('Content-Type', 'boundary');
// => "--foo-bar-baz"
### req.flash(type[, msg])
Queue flash _msg_ of the given _type_.
@@ -865,6 +923,18 @@ it will not be set again.
Note that this method _end()_s the response, so you will want to use node's _res.write()_ for multiple writes or streaming.
### res.json(obj[, headers|status[, status]])
Send a JSON response with optional _headers_ and _status_. This method
is ideal for JSON-only APIs, however _res.send(obj)_ will send JSON as
well, though not ideal for cases when you want to send for example a string
as JSON, since the default for _res.send(string)_ is text/html.
res.json(null);
res.json({ user: 'tj' });
res.json('oh noes!', 500);
res.json('I dont have that', 404);
### res.redirect(url[, status])
Redirect to the given _url_ with a default response _status_ of 302.
@@ -877,11 +947,12 @@ Redirect to the given _url_ with a default response _status_ of 302.
Express supports "redirect mapping", which by default provides _home_, and _back_.
The _back_ map checks the _Referrer_ and _Referer_ headers, while _home_ utilizes
the "home" setting and defaults to "/".
the "basepath" setting and defaults to "/".
### res.cookie(name, val[, options])
Sets the given cookie _name_ to _val_, with options _httpOnly_, _secure_, _expires_ etc.
Sets the given cookie _name_ to _val_, with options _httpOnly_, _secure_, _expires_ etc. The _path_ option defaults to the app's "basepath" setting, which
is typically "/".
// "Remember me" for 15 minutes
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
@@ -900,7 +971,8 @@ To parse incoming _Cookie_ headers, use the _cookieParser_ middleware, which pro
### res.clearCookie(name[, options])
Clear cookie _name_ by setting "expires" far in the past.
Clear cookie _name_ by setting "expires" far in the past. Much like
_res.cookie()_ the _path_ option also defaults to the "basepath" setting.
res.clearCookie('rememberme');
@@ -915,6 +987,15 @@ The _options_ passed are the local variables as well, for example if we want to
var user = { name: 'tj' };
res.render('index', { layout: false, user: user });
This _options_ object is also considered an "options" object. For example
when you pass the _status_ local, it's not only available to the view, it
sets the response status to this number. This is also useful if a template
engine accepts specific options, such as _debug_, or _compress_. Below
is an example of how one might render an error page, passing the _status_ for
display, as well as it setting _res.statusCode_.
res.render('error', { status: 500, message: 'Internal Server Error' });
### res.partial(view[, options])
Render _view_ partial with the given _options_. This method is always available
@@ -1156,7 +1237,6 @@ as well as the _name()_ function exposed.
Express also provides a few locals by default:
- `settings` the app's settings object
- `filename` the view's filename
- `layout(path)` specify the layout from within a view
This method is aliased as _app.locals()_.

View File

@@ -1,7 +1,21 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25235225-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#tagline {
margin-left: 75px;
@@ -232,26 +246,27 @@ app.listen(3000);
<h2>Third-Party Modules</h2>
<p>The following modules compliment or extend Express directly:</p>
<p>The following modules complement or extend Express directly:</p>
<ul>
<li><a href="http://github.com/visionmedia/express-resource">express-resource</a> provides resourceful routing</li>
<li><a href="http://github.com/visionmedia/express-messages">express-messages</a> flash message notification rendering</li>
<li><a href="http://github.com/visionmedia/express-configuration">express-configure</a> async configuration support (load settings from redis etc)</li>
<li><a href="http://github.com/visionmedia/express-namespace">express-namespace</a> namespaced routing support</li>
<li><a href="http://github.com/visionmedia/express-expose">express-expose</a> expose objects, functions, modules and more to client-side js</li>
<li><a href="https://github.com/visionmedia/express-params">express-params</a> app.param() extensions</li>
<li><a href="https://github.com/LearnBoost/express-mongoose">express-mongoose</a> plugin for easy rendering of Mongoose async Query results</li>
</ul>
<h2>More Information</h2>
<ul>
<li><a href="http://groups.google.com/group/express-js">Google Group</a> for discussion</li>
<li>#express on freenode</li>
<li>Follow <a href="http://twitter.com/tjholowaychuk">tjholowaychuk</a> on twitter for updates</li>
<li>View the <a href="http://senchalabs.github.com/connect">Connect</a> documentation</li>
<li>View the <a href="http://wiki.github.com/senchalabs/connect/">Connect Wiki</a> for contrib middleware</li>
<li>View the <a href="http://github.com/visionmedia/express/tree/master/examples/">examples</a></li>
<li>View the <a href="http://github.com/visionmedia/express">source</a></li>
<li>View the <a href="contrib.html">contrib guide</a></li>
<li><a href="http://groups.google.com/group/express-js">Google Group</a> for discussion</li>
<li>Visit the <a href="http://github.com/visionmedia/express/wiki">Wiki</a></li>
<li><a href="http://hideyukisaito.com/doc/expressjs/">日本語ドキュメンテーション</a> by <a href="https://github.com/hideyukisaito">hideyukisaito</a></li>
</ul>
</div>

View File

@@ -34,19 +34,20 @@ The following are the major contributors of Express (in no specific order).
## Third-Party Modules
The following modules compliment or extend Express directly:
The following modules complement or extend Express directly:
* [express-resource](http://github.com/visionmedia/express-resource) provides resourceful routing
* [express-messages](http://github.com/visionmedia/express-messages) flash message notification rendering
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support (load settings from redis etc)
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced routing support
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js
* [express-params](https://github.com/visionmedia/express-params) app.param() extensions
* [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results
## More Information
* [Google Group](http://groups.google.com/group/express-js) for discussion
* \#express on freenode
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
* View the [Connect](http://senchalabs.github.com/connect) documentation
* View the [Connect Wiki](http://wiki.github.com/senchalabs/connect/) for contrib middleware
* View the [examples](http://github.com/visionmedia/express/tree/master/examples/)
* View the [source](http://github.com/visionmedia/express)
* View the [contrib guide](contrib.html)
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
* [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito)

View File

@@ -1,7 +1,21 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25235225-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#tagline {
margin-left: 75px;

View File

@@ -1,7 +1,21 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25235225-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#tagline {
margin-left: 75px;
@@ -326,7 +340,7 @@
<h3>partial() locals</h3>
<p> Both <em>res.partial()</em> and the <em>partial()</em> functions accept an single object consisting of both the options and the locals. Previously with Express 1.x you may pass <em>user</em> to a partial, along with <em>date</em> like so:</p>
<p> Both <em>res.partial()</em> and the <em>partial()</em> functions accept a single object consisting of both the options and the locals. Previously with Express 1.x you may pass <em>user</em> to a partial, along with <em>date</em> like so:</p>
<pre><code> partial('user', { object: user, locals: { date: new Date }})
</code></pre>
@@ -367,7 +381,7 @@
<h3>View Partial Lookup</h3>
<p> Previously partials were loaded relative to the now removed <em>view partials</em> directory setting, or by default <em>views/partials</em>, now they are relative to the view calling them, read more on <a href="guide.html#View-Lookup">view lookup</a>.</p>
<p> Previously partials were loaded relative to the now removed <em>view partials</em> directory setting, or by default <em>views/partials</em>, now they are relative to the view calling them, read more on <a href="guide.html#view-lookup">view lookup</a>.</p>
<h3>Mime Types</h3>

View File

@@ -123,7 +123,7 @@ However now we have the alternative _maxAge_ property which may be used to set _
### partial() locals
Both _res.partial()_ and the _partial()_ functions accept an single object consisting of both the options and the locals. Previously with Express 1.x you may pass _user_ to a partial, along with _date_ like so:
Both _res.partial()_ and the _partial()_ functions accept a single object consisting of both the options and the locals. Previously with Express 1.x you may pass _user_ to a partial, along with _date_ like so:
partial('user', { object: user, locals: { date: new Date }})
@@ -157,7 +157,7 @@ or perhaps if you preferred not to use the inferred name _user_ you may used a l
### View Partial Lookup
Previously partials were loaded relative to the now removed _view partials_ directory setting, or by default _views/partials_, now they are relative to the view calling them, read more on [view lookup](guide.html#View-Lookup).
Previously partials were loaded relative to the now removed _view partials_ directory setting, or by default _views/partials_, now they are relative to the view calling them, read more on [view lookup](guide.html#view-lookup).
### Mime Types
@@ -174,4 +174,4 @@ or perhaps if you preferred not to use the inferred name _user_ you may used a l
Previously when using options the `root` option would be used for this:
app.use(express.staticProvider({ root: __dirname + '/public', maxAge: oneYear }));

View File

@@ -1,7 +1,21 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-25235225-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#tagline {
margin-left: 75px;

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -38,16 +35,15 @@ var users = {
tj: {
name: 'tj'
, salt: 'randomly-generated-salt'
, pass: md5('foobar' + 'randomly-generated-salt')
, pass: hash('foobar', 'randomly-generated-salt')
}
};
// Used to generate a hash of the plain-text password + salt
function md5(str) {
return crypto.createHash('md5').update(str).digest('hex');
function hash(msg, key) {
return crypto.createHmac('sha256', key).update(msg).digest('hex');
}
// Authenticate using our plain-object database of doom!
function authenticate(name, pass, fn) {
@@ -55,9 +51,9 @@ function authenticate(name, pass, fn) {
// query the db for the given username
if (!user) return fn(new Error('cannot find user'));
// apply the same algorithm to the POSTed password, applying
// the md5 against the pass / salt, if there is a match we
// the hash against the pass / salt, if there is a match we
// found the user
if (user.pass == md5(pass + user.salt)) return fn(null, user);
if (user.pass == hash(pass, user.salt)) return fn(null, user);
// Otherwise password is invalid
fn(new Error('invalid password'));
}

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -0,0 +1,47 @@
/**
* Module dependencies.
*/
var express = require('../../lib/express');
var app = express.createServer();
var users = [
{ name: 'tobi' }
, { name: 'loki' }
, { name: 'jane' }
];
function provides(type) {
return function(req, res, next){
if (req.accepts(type)) return next();
next('route');
}
}
// curl http://localhost:3000/users -H "Accept: application/json"
app.get('/users', provides('json'), function(req, res){
res.send(users);
});
// curl http://localhost:3000/users -H "Accept: text/html"
app.get('/users', provides('html'), function(req, res){
res.send('<ul>' + users.map(function(user){
return '<li>' + user.name + '</li>';
}).join('\n') + '</ul>');
});
// curl http://localhost:3000/users -H "Accept: text/plain"
app.get('/users', function(req, res, next){
res.contentType('txt');
res.send(users.map(function(user){
return user.name;
}).join(', '));
});
app.listen(3000);
console.log('Express server listening on port 3000');

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -14,7 +11,7 @@ var app = express.createServer(
express.favicon(),
// Custom logger format
express.logger({ format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time' }),
express.logger({ format: '\x1b[36m:method\x1b[0m \x1b[90m:url\x1b[0m :response-time' }),
// Provides req.cookies
express.cookieParser(),
@@ -39,9 +36,8 @@ app.get('/forget', function(req, res){
});
app.post('/', function(req, res){
if (req.body.remember) {
res.cookie('remember', '1', { path: '/', expires: new Date(Date.now() + 900000), httpOnly: true });
}
var minute = 60000;
if (req.body.remember) res.cookie('remember', 1, { maxAge: minute });
res.redirect('back');
});

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -24,13 +21,20 @@ app.get('/files/:file(*)', function(req, res, next){
, path = __dirname + '/files/' + file;
// either res.download(path) and let
// express handle failures, or provide
// a callback
// a callback as shown below
res.download(path, function(err){
// if an error occurs in this callback
// the file most likely does not exist,
// and it's safe to respond or next(err)
if (err) return next(err);
// the response has invoked .end()
// so you cannnot respond here (of course)
// but the callback is handy for statistics etc.
// the file has been transferred, do not respond
// from here, though you may use this callback
// for stats etc.
console.log('transferred %s', path);
}, function(err){
// this second optional callback is used when
// an error occurs during transmission
});
});

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -9,13 +9,15 @@ require.paths.unshift(__dirname + '/../../support');
var express = require('../../lib/express');
var app = express.createServer();
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
// Serve default connect favicon
app.use(express.favicon());
// Logger is placed below favicon, so favicon.ico
// requests will not be logged
app.use(express.logger({ format: '":method :url" :status' }));
app.use(express.logger('":method :url" :status'));
// "app.router" positions our routes
// specifically above the middleware
@@ -23,59 +25,38 @@ app.use(express.logger({ format: '":method :url" :status' }));
app.use(app.router);
// When no more middleware require execution, aka
// our router is finished and did not respond, we
// can assume that it is "not found". Instead of
// letting Connect deal with this, we define our
// custom middleware here to simply pass a NotFound
// exception
// Since this is the last non-error-handling
// middleware use()d, we assume 404, as nothing else
// responded.
app.use(function(req, res, next){
next(new NotFound(req.url));
// the status option, or res.statusCode = 404
// are equivalent, however with the option we
// get the "status" local available as well
res.render('404', { status: 404, url: req.url });
});
app.set('views', __dirname + '/views');
// error-handling middleware, take the same form
// as regular middleware, however they require an
// arity of 4, aka the signature (err, req, res, next).
// when connect has an error, it will invoke ONLY error-handling
// middleware.
// Provide our app with the notion of NotFound exceptions
// If we were to next() here any remaining non-error-handling
// middleware would then be executed, or if we next(err) to
// continue passing the error, only error-handling middleware
// would remain being executed, however here
// we simply respond with an error page.
function NotFound(path){
this.name = 'NotFound';
if (path) {
Error.call(this, 'Cannot find ' + path);
this.path = path;
} else {
Error.call(this, 'Not Found');
}
Error.captureStackTrace(this, arguments.callee);
}
/**
* Inherit from `Error.prototype`.
*/
NotFound.prototype.__proto__ = Error.prototype;
// We can call app.error() several times as shown below.
// Here we check for an instanceof NotFound and show the
// 404 page, or we pass on to the next error handler.
// These handlers could potentially be defined within
// configure() blocks to provide introspection when
// in the development environment.
app.error(function(err, req, res, next){
if (err instanceof NotFound) {
res.render('404.jade', { status: 404, error: err });
} else {
next(err);
}
});
// Here we assume all errors as 500 for the simplicity of
// this demo, however you can choose whatever you like
app.error(function(err, req, res){
res.render('500.jade', { status: 500, error: err });
app.use(function(err, req, res, next){
// we may use properties of the error object
// here and next(err) appropriately, or if
// we possibly recovered from the error, simply next().
res.render('500', {
status: err.status || 500
, error: err
});
});
// Routes
@@ -84,8 +65,14 @@ app.get('/', function(req, res){
res.render('index.jade');
});
app.get('/404', function(req, res){
throw new NotFound(req.url);
app.get('/404', function(req, res, next){
next();
});
app.get('/403', function(req, res, next){
var err = new Error('not allowed!');
err.status = 403;
next(err);
});
app.get('/500', function(req, res, next){

View File

@@ -1,4 +1 @@
- if (error.path)
h2 Cannot find #{error.path}
- else
h2 Page Not Found
h2 Cannot find #{url}

View File

@@ -5,4 +5,7 @@ ul
a(href="/500") 500
li
| visit
a(href="/404") 404
a(href="/404") 404
li
| visit
a(href='/403') 403

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -25,4 +22,5 @@ app.get('/next', function(req, res, next){
// text/html, and application/json responses to aid in development
app.use('/', express.errorHandler({ dump: true, stack: true }));
app.listen(3000);
app.listen(3000);
console.log('app listening on port 3000');

View File

@@ -0,0 +1,66 @@
/**
* Module dependencies.
*/
var express = require('../../lib/express');
var app = express.createServer();
// Register ejs as .html
app.register('.html', require('ejs'));
// Optional since express defaults to CWD/views
app.set('views', __dirname + '/views');
app.set('view engine', 'html');
// Dummy users
var users = [
{ name: 'tj', email: 'tj@sencha.com' }
, { name: 'ciaran', email: 'ciaranj@gmail.com' }
, { name: 'aaron', email: 'aaron.heckmann+github@gmail.com' }
];
// dynamic helpers are simply functions that are invoked
// per request (once), passed both the request and response
// objects. These can be used for request-specific
// details within a view, such telling the layout which
// scripts to include.
app.dynamicHelpers({
// by simply returning an object here
// we can set it's properties such as "page.title"
// within a view, and it remains specific to that request,
// so it would be valid to do:
// page.title = user.name + "'s account"
page: function() {
return {};
},
// the scripts array here is assigned once,
// so by returning a closure, we can use script(path)
// in a template, instead of something like
// scripts.push(path).
script: function(req){
req._scripts = [];
return function(path){
req._scripts.push(path);
}
},
// to expose our scripts array for iteration within
// our views (typically the layout), we simply return it
// here, and since composite types are mutable, it will
// contain all of the paths pushed with the helper above.
scripts: function(req){
return req._scripts;
}
});
app.get('/', function(req, res){
res.render('users', { users: users });
});
app.listen(3000);
console.log('Express app started on port 3000');

View File

@@ -0,0 +1,11 @@
<html>
<head>
<title><%- page.title %></title>
<% for (var i in scripts) { %>
<script src="<%= scripts[i] %>"></script>
<% } %>
</head>
<body>
<%- body %>
</body>
</html>

View File

@@ -0,0 +1,8 @@
<% page.title = 'Users' %>
<% script('/javascripts/jquery.js') %>
<% script('/javascripts/users.js') %>
<h1>Users</h1>
<ul id="users">
<%- partial('user', users) %>
</ul>

View File

@@ -0,0 +1 @@
<li><%= user.name %> &lt;<%= user.email %>&gt;</li>

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -16,13 +13,17 @@ var pub = __dirname + '/public';
// and then serve with connect's staticProvider
var app = express.createServer();
app.use(express.logger('dev'));
app.use(express.compiler({ src: pub, enable: ['sass'] }));
app.use(app.router);
app.use(express.static(pub));
app.use(express.errorHandler({ dump: true, stack: true }));
// Optional since express defaults to CWD/views
// we're using jade's template inheritance, so we dont need
// the express layout concept
app.set('view options', { layout: false });
// Optional since express defaults to CWD/views
app.set('views', __dirname + '/views');
// Set our default template engine to "jade"
@@ -37,30 +38,15 @@ function User(name, email) {
// Dummy users
var users = [
new User('tj', 'tj@vision-media.ca')
, new User('ciaran', 'ciaranj@gmail.com')
, new User('aaron', 'aaron.heckmann+github@gmail.com')
new User('Tobi', 'tobi@learnboost.com')
, new User('Loki', 'loki@learnboost.com')
, new User('Jane', 'jane@learnboost.com')
];
app.get('/', function(req, res){
res.render('users', { users: users });
});
app.get('/users/callback', function(req, res){
// a callback is also accepted
res.partial('users/user', users, function(err, html){
if (err) throw err;
res.send(html);
});
});
app.get('/users', function(req, res){
// we can use res.partial() as if
// we were in a view, utilizing the same api
// to render a fragment
res.partial('users/user', users);
});
app.get('/users/list', function(req, res){
// use "object" to utilize the name deduced from
// the view filename. The examples below are equivalent

View File

@@ -1,6 +1,8 @@
!!!
doctype 5
html
head
title Jade Example
block title
title Jade Example
link(rel="stylesheet", href="/stylesheets/style.css")
body!= body
body
block content

View File

@@ -1,3 +1,12 @@
- if (users.length)
h1 Users
#users!= partial('user', users)
extends ../layout
block title
title Users
block content
if users.length
h1 Users
#users
for user in users
include ./user

View File

@@ -1,3 +1,4 @@
ul#users
- each user in list
li!= partial('user', user)
for user in list
li
include ./user

View File

@@ -0,0 +1,26 @@
/**
* Module dependencies.
*/
var express = require('../../');
app = express.createServer();
// load the config for this environment (NODE_ENV)
var config = require('./config')[app.settings.env];
// apply settings
for (var key in config) app.set(key, config[key]);
// apply middleware
config.middleware.forEach(app.use.bind(app));
app.get('/', function(req, res){
res.render('index', { layout: false });
});
app.listen(3000);

View File

@@ -0,0 +1,19 @@
// ok so it's not JSON, but close enough :)
var express = require('../../');
exports.development = {
'view engine': 'jade'
, 'views': __dirname + '/views'
, 'title': 'My Site'
, 'middleware': [
express.logger('dev')
, app.router
, express.static(__dirname + '/public')
]
};
exports.production = {
};

View File

@@ -0,0 +1,4 @@
html
body
h1 #{settings.title}
p Simple example

View File

@@ -1,5 +1,3 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.

View File

@@ -1,15 +1,10 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
// $ npm install markdown
/**
* Module dependencies.
*/
var express = require('../../lib/express')
, md = require('markdown').markdown;
, md = require('node-markdown').Markdown;
var app = express.createServer();
@@ -19,7 +14,7 @@ var app = express.createServer();
app.register('.md', {
compile: function(str, options){
var html = md.toHTML(str);
var html = md(str);
return function(locals){
return html.replace(/\{([^}]+)\}/g, function(_, name){
return locals[name];

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

44
examples/say/app.js Normal file
View File

@@ -0,0 +1,44 @@
/**
* Module dependencies.
*/
var express = require('../../')
, path = require('path')
, exec = require('child_process').exec
, fs = require('fs');
/**
* Error handler.
*/
function errorHandler(voice) {
return function(err, req, res, next) {
var parts = err.stack.split('\n')[1].split(/[()]/)[1].split(':')
, filename = parts.shift()
, basename = path.basename(filename)
, lineno = parts.shift()
, col = parts.shift()
, lines = fs.readFileSync(filename, 'utf8').split('\n')
, line = lines[lineno - 1].replace(/\./, ' ');
exec('say -v "' + voice + '" '
+ err.message
+ ' on line ' + lineno
+ ' of ' + basename + '.'
+ ' The contents of this line is '
+ ' "' + line + '".');
res.send(500);
}
}
var app = express.createServer();
app.get('/', function(request, response){
if (request.is(foo)) response.end('bar');
});
app.use(errorHandler('Vicki'));
app.listen(3000);

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,28 +1,30 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('../../lib/express');
// $ npm install connect-redis
var RedisStore = require('connect-redis');
// pass the express to the connect redis module
// allowing it to inherit from express.session.Store
var RedisStore = require('connect-redis')(express);
var app = express.createServer(
express.logger(),
var app = express.createServer();
// Required by session() middleware
express.cookieDecoder(),
app.use(express.favicon());
// Populates:
// - req.session
// - req.sessionStore
// - req.sessionID (or req.session.id)
express.session({ secret: 'keyboard cat', store: new RedisStore })
);
// request logging
app.use(express.logger());
// required to parse the session cookie
app.use(express.cookieParser());
// Populates:
// - req.session
// - req.sessionStore
// - req.sessionID (or req.session.id)
app.use(express.session({ secret: 'keyboard cat', store: new RedisStore }));
app.get('/', function(req, res){
var body = '';

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

View File

@@ -1,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/

121
examples/web-service/app.js Normal file
View File

@@ -0,0 +1,121 @@
/**
* Module dependencies.
*/
var express = require('../../lib/express');
var app = express.createServer();
// configuration
// if we wanted to supply more than JSON, we could
// use something similar to the content-negotiation
// example.
// here we validate the API key,
// by mounting this middleware to /api/v1
// meaning only paths prefixed with "/api/v1"
// will cause this middleware to be invoked
app.use('/api/v1', function(req, res, next){
var key = req.query['api-key'];
// key isnt present
if (!key) return next(new Error('api key required'));
// key is invalid
if (!~apiKeys.indexOf(key)) return next(new Error('invalid api key'));
// all good, store req.key for route access
req.key = key;
next();
});
// position our routes above the error handling middleware,
// and below our API middleware, since we want the API validation
// to take place BEFORE our routes
app.use(app.router);
// middleware with an arity of 4 are considered
// error handling middleware. When you next(err)
// it will be passed through the defined middleware
// in order, but ONLY those with an arity of 4, ignoring
// regular middleware.
app.use(function(err, req, res, next){
// whatever you want here, feel free to populate
// properties on `err` to treat it differently in here,
// or when you next(err) set res.statusCode= etc.
res.send({ error: err.message }, 500);
});
// 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({ error: "Lame, can't find that" }, 404);
});
/**
* Generate our unique identifier.
*/
function uid() {
return [
Math.random() * 0xffff | 0
, Math.random() * 0xffff | 0
, Math.random() * 0xffff | 0
, Date.now()
].join('-');
}
// map of valid api keys, typically mapped to
// account info with some sort of database like redis.
// api keys do _not_ serve as authentication, merely to
// track API usage or help prevent malicious behavior etc.
var apiKeys = [uid(), uid(), uid()];
console.log('valid keys:\n ', apiKeys.join('\n '));
// these two objects will serve as our faux database
var repos = [
{ name: 'express', url: 'http://github.com/visionmedia/express' }
, { name: 'stylus', url: 'http://github.com/learnboost/stylus' }
, { name: 'cluster', url: 'http://github.com/learnboost/cluster' }
];
var users = [
{ name: 'tobi' }
, { name: 'loki' }
, { name: 'jane' }
];
var userRepos = {
tobi: [repos[0], repos[1]]
, loki: [repos[1]]
, jane: [repos[2]]
};
// we now can assume the api key is valid,
// and simply expose the data
app.get('/api/v1/users', function(req, res, next){
res.send(users);
});
app.get('/api/v1/repos', function(req, res, next){
res.send(repos);
});
app.get('/api/v1/user/:name/repos', function(req, res, next){
var name = req.params.name
, user = userRepos[name];
if (user) res.send(user);
else next();
});
app.listen(3000);
console.log('Express server listening on port 3000');

View File

@@ -28,7 +28,7 @@ var exports = module.exports = connect.middleware;
* Framework version.
*/
exports.version = '2.3.3';
exports.version = '2.5.9';
/**
* Shortcut for `new Server(...)`.
@@ -58,7 +58,8 @@ exports.Route = Route;
* View extensions.
*/
require('./view');
exports.View =
exports.view = require('./view');
/**
* Response extensions.

View File

@@ -1,4 +1,3 @@
/*!
* Express - HTTPServer
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
@@ -12,8 +11,10 @@
var qs = require('qs')
, connect = require('connect')
, router = require('./router')
, methods = router.methods.concat(['del', 'all'])
, Router = require('./router')
, view = require('./view')
, toArray = require('./utils').toArray
, methods = router.methods.concat('del', 'all')
, url = require('url')
, utils = connect.utils;
@@ -64,7 +65,6 @@ app.init = function(middleware){
this.dynamicViewHelpers = {};
this.errorHandlers = [];
this.set('home', '/');
this.set('env', process.env.NODE_ENV || 'development');
// expose objects to each other
@@ -86,11 +86,11 @@ app.init = function(middleware){
// apply middleware
if (middleware) middleware.forEach(self.use.bind(self));
// use router, expose as app.get(), etc
var fn = router(function(app){ self.routes = app; }, this);
// router
this.routes = new Router(this);
this.__defineGetter__('router', function(){
this.__usedRouter = true;
return fn;
return self.routes.middleware;
});
// default locals
@@ -113,38 +113,62 @@ app.init = function(middleware){
// so that they disregard definition position.
this.on('listening', this.registerErrorHandlers.bind(this));
// route lookup methods
this.remove = function(url){
return self.remove.all(url);
};
this.match = function(url){
return self.match.all(url);
};
this.lookup = function(url){
return self.lookup.all(url);
};
// route manipulation methods
methods.forEach(function(method){
self.match[method] = function(url){
return self.router.match(url, 'all' == method
? null
: method);
};
self.remove[method] = function(url){
return self.router.remove(url, 'all' == method
? null
: method);
};
self.lookup[method] = function(path){
return self.router.lookup(path, 'all' == method
? null
: method);
return self.routes.lookup(method, path);
};
self.match[method] = function(path){
return self.routes.match(method, path);
};
self.remove[method] = function(path){
return self.routes.lookup(method, path).remove();
};
});
// del -> delete
self.lookup.del = self.lookup.delete;
self.match.del = self.match.delete;
self.remove.del = self.remove.delete;
};
/**
* Remove routes matching the given `path`.
*
* @param {Route} path
* @return {Boolean}
* @api public
*/
app.remove = function(path){
return this.routes.lookup('all', path).remove();
};
/**
* Lookup routes defined with a path
* equivalent to `path`.
*
* @param {Stirng} path
* @return {Array}
* @api public
*/
app.lookup = function(path){
return this.routes.lookup('all', path);
};
/**
* Lookup routes matching the given `url`.
*
* @param {Stirng} url
* @return {Array}
* @api public
*/
app.match = function(url){
return this.routes.match('all', url);
};
/**
@@ -182,7 +206,7 @@ app.registerErrorHandlers = function(){
*/
app.use = function(route, middleware){
var app, home, handle;
var app, base, handle;
if ('string' != typeof route) {
middleware = route, route = '/';
@@ -208,9 +232,10 @@ app.use = function(route, middleware){
// mounted an app, invoke the hook
// and adjust some settings
if (app) {
home = app.set('home');
if ('/' == home) home = '';
app.set('home', app.route + home);
base = this.set('basepath') || this.route;
if ('/' == base) base = '';
base = base + (app.set('basepath') || app.route);
app.set('basepath', base);
app.parent = this;
if (app.__mounted) app.__mounted.call(app, this);
}
@@ -286,57 +311,86 @@ app.dynamicHelpers = function(obj){
};
/**
* Map the given param placeholder `name`(s) to the given callback `fn`.
* Map the given param placeholder `name`(s) to the given callback(s).
*
* Param mapping is used to provide pre-conditions to routes
* which us normalized placeholders. For example ":user_id" may
* attempt to load the user from the database, where as ":num" may
* pass the value through `parseInt(num, 10)`.
* which us normalized placeholders. This callback has the same
* signature as regular middleware, for example below when ":userId"
* is used this function will be invoked in an attempt to load the user.
*
* When the callback function accepts only a single argument, the
* value of placeholder is passed:
* app.param('userId', function(req, res, next, id){
* User.find(id, function(err, user){
* if (err) {
* next(err);
* } else if (user) {
* req.user = user;
* next();
* } else {
* next(new Error('failed to load user'));
* }
* });
* });
*
* app.param('page', function(n){ return parseInt(n, 10); });
* Passing a single function allows you to map logic
* to the values passed to `app.param()`, for example
* this is useful to provide coercion support in a concise manner.
*
* After which "/users/:page" would automatically provide us with
* an integer for `req.params.page`. If desired we could use the callback
* signature shown below, and immediately `next(new Error('invalid page'))`
* when `parseInt` fails.
* The following example maps regular expressions to param values
* ensuring that they match, otherwise passing control to the next
* route:
*
* Alternatively the callback may accept the request, response, next, and
* the value, acting like middlware:
* app.param(function(name, regexp){
* if (regexp instanceof RegExp) {
* return function(req, res, next, val){
* var captures;
* if (captures = regexp.exec(String(val))) {
* req.params[name] = captures;
* next();
* } else {
* next('route');
* }
* }
* }
* });
*
* app.param('userId', function(req, res, next, id){
* User.find(id, function(err, user){
* if (err) {
* next(err);
* } else if (user) {
* req.user = user;
* next();
* } else {
* next(new Error('failed to load user'));
* }
* });
* });
* We can now use it as shown below, where "/commit/:commit" expects
* that the value for ":commit" is at 5 or more digits. The capture
* groups are then available as `req.params.commit` as we defined
* in the function above.
*
* Now every time ":userId" is present, the associated user object
* will be loaded and assigned before the route handler is invoked.
* app.param('commit', /^\d{5,}$/);
*
* @param {String|Array} name
* For more of this useful functionality take a look
* at [express-params](http://github.com/visionmedia/express-params).
*
* @param {String|Array|Function} name
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.param = function(name, fn){
var self = this
, fns = [].slice.call(arguments, 1);
// array
if (Array.isArray(name)) {
name.forEach(function(name){
this.param(name, fn);
}, this);
fns.forEach(function(fn){
self.param(name, fn);
});
});
// param logic
} else if ('function' == typeof name) {
this.routes.param(name);
// single
} else {
if (':' == name[0]) name = name.substr(1);
this.routes.param(name, fn);
fns.forEach(function(fn){
self.routes.param(name, fn);
});
}
return this;
};
@@ -455,49 +509,74 @@ app.redirect = function(key, url){
};
/**
* Configure callback for the given `env`.
* Configure callback for zero or more envs,
* when no env is specified that callback will
* be invoked for all environments. Any combination
* can be used multiple times, in any order desired.
*
* @param {String} env
* Examples:
*
* app.configure(function(){
* // executed for all envs
* });
*
* app.configure('stage', function(){
* // executed staging env
* });
*
* app.configure('stage', 'production', function(){
* // executed for stage and production
* });
*
* @param {String} env...
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.configure = function(env, fn){
if ('function' == typeof env) {
fn = env, env = 'all';
}
if ('all' == env || env == this.settings.env) {
fn.call(this);
}
var envs = 'all'
, args = toArray(arguments);
fn = args.pop();
if (args.length) envs = args;
if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
return this;
};
// Generate routing methods
/**
* Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
*/
function generateMethod(method){
methods.forEach(function(method){
app[method] = function(path){
var self = this;
// Lookup
if (1 == arguments.length) {
return this.router.lookup(path, 'all' == method
? null
: method);
}
// Ensure router is mounted
if (1 == arguments.length) return this.routes.lookup(method, path);
var args = [method].concat(toArray(arguments));
if (!this.__usedRouter) this.use(this.router);
return this.routes._route.apply(this.routes, args);
}
});
// Generate the route
this.routes[method].apply(this, arguments);
return this;
};
return arguments.callee;
}
/**
* Special-cased "all" method, applying the given route `path`,
* middleware, and callback to _every_ HTTP method.
*
* @param {String} path
* @param {Function} ...
* @return {Server} for chaining
* @api public
*/
methods.forEach(generateMethod);
app.all = function(path){
var args = arguments;
if (1 == args.length) return this.routes.lookup('all', path);
methods.forEach(function(method){
if ('all' == method || 'del' == method) return;
app[method].apply(this, args);
}, this);
return this;
};
// Alias delete as "del"
// del -> delete alias
app.del = app.delete;

View File

@@ -12,6 +12,7 @@
var http = require('http')
, req = http.IncomingMessage.prototype
, utils = require('./utils')
, parse = require('url').parse
, mime = require('mime');
/**
@@ -65,6 +66,39 @@ req.header = function(name, defaultValue){
}
};
/**
* Get `field`'s `param` value, defaulting to ''.
*
* Examples:
*
* req.get('content-disposition', 'filename');
* // => "something.png"
*
* @param {String} field
* @param {String} param
* @return {String}
* @api public
*/
req.get = function(field, param){
var val = this.header(field);
if (!val) return '';
var regexp = new RegExp(param + ' *= *(?:"([^"]+)"|([^;]+))', 'i');
if (!regexp.exec(val)) return '';
return RegExp.$1 || RegExp.$2;
};
/**
* Short-hand for `require('url').parse(req.url).pathname`.
*
* @return {String}
* @api public
*/
req.__defineGetter__('path', function(){
return parse(this.url).pathname;
});
/**
* Check if the _Accept_ header is present, and includes the given `type`.
*
@@ -101,21 +135,19 @@ req.accepts = function(type){
// normalize extensions ".json" -> "json"
if (type && '.' == type[0]) type = type.substr(1);
// when Accept does not exist, or is '*/*' return true
if (!accept || '*/*' == accept) {
// when Accept does not exist, or contains '*/*' return true
if (!accept || ~accept.indexOf('*/*')) {
return true;
} else if (type) {
// allow "html" vs "text/html" etc
if (type.indexOf('/') < 0) {
type = mime.lookup(type);
}
if (!~type.indexOf('/')) type = mime.lookup(type);
// check if we have a direct match
if (~accept.indexOf(type)) return true;
// check if we have type/*
type = type.split('/')[0] + '/*';
return accept.indexOf(type) >= 0;
return !!~accept.indexOf(type);
} else {
return false;
}
@@ -197,10 +229,10 @@ req.flash = function(type, msg){
, args = arguments
, formatters = this.app.flashFormatters || {};
formatters.__proto__ = flashFormatters;
msg = utils.miniMarkdown(utils.escape(msg));
msg = utils.miniMarkdown(msg);
msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
var formatter = formatters[format];
if (formatter) return formatter(args[i++]);
if (formatter) return formatter(utils.escape(args[i++]));
});
return (msgs[type] = msgs[type] || []).push(msg);
} else if (type) {
@@ -260,16 +292,18 @@ req.flash = function(type, msg){
req.is = function(type){
var fn = this.app.is(type);
if (fn) return fn(this);
var contentType = this.headers['content-type'];
if (!contentType) return;
var ct = this.headers['content-type'];
if (!ct) return false;
ct = ct.split(';')[0];
if (!~type.indexOf('/')) type = mime.lookup(type);
if (~type.indexOf('*')) {
type = type.split('/')
contentType = contentType.split('/');
if ('*' == type[0] && type[1] == contentType[1]) return true;
if ('*' == type[1] && type[0] == contentType[0]) return true;
type = type.split('/');
ct = ct.split('/');
if ('*' == type[0] && type[1] == ct[1]) return true;
if ('*' == type[1] && type[0] == ct[0]) return true;
return false;
}
return ~contentType.indexOf(type);
return !! ~ct.indexOf(type);
};
// Callback for isXMLHttpRequest / xhr

View File

@@ -17,8 +17,9 @@ var fs = require('fs')
, parseRange = require('./utils').parseRange
, res = http.ServerResponse.prototype
, send = connect.static.send
, join = require('path').join
, mime = require('mime');
, mime = require('mime')
, basename = path.basename
, join = path.join;
/**
* Send a response with the given `body` and optional `headers` and `status` code.
@@ -51,7 +52,7 @@ res.send = function(body, headers, status){
status = status || this.statusCode;
// allow 0 args as 204
if (!arguments.length || undefined === body) body = status = 204;
if (!arguments.length || undefined === body) status = 204;
// determine content type
switch (typeof body) {
@@ -74,22 +75,13 @@ res.send = function(body, headers, status){
this.contentType('.bin');
}
} else {
if (!this.header('Content-Type')) {
this.charset = this.charset || 'utf-8';
this.contentType('.json');
}
body = JSON.stringify(body);
if (this.req.query.callback && this.app.set('jsonp callback')) {
this.charset = this.charset || 'utf-8';
this.header('Content-Type', 'text/javascript');
body = this.req.query.callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
}
return this.json(body, headers, status);
}
break;
}
// populate Content-Length
if (!this.header('Content-Length')) {
if (undefined !== body && !this.header('Content-Length')) {
this.header('Content-Length', Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body));
@@ -105,14 +97,62 @@ res.send = function(body, headers, status){
}
// strip irrelevant headers
if (204 === status) {
if (204 == status || 304 == status) {
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
body = '';
}
// respond
this.statusCode = status;
this.end('HEAD' == this.req.method ? undefined : body);
this.end('HEAD' == this.req.method ? null : body);
return this;
};
/**
* Send JSON response with `obj`, optional `headers`, and optional `status`.
*
* Examples:
*
* res.json(null);
* res.json({ user: 'tj' });
* res.json('oh noes!', 500);
* res.json('I dont have that', 404);
*
* @param {Mixed} obj
* @param {Object|Number} headers or status
* @param {Number} status
* @return {ServerResponse}
* @api public
*/
res.json = function(obj, headers, status){
var body = JSON.stringify(obj)
, callback = this.req.query.callback
, jsonp = this.app.enabled('jsonp callback');
this.charset = this.charset || 'utf-8';
this.header('Content-Type', 'application/json');
if (callback && jsonp) {
this.header('Content-Type', 'text/javascript');
body = callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
}
return this.send(body, headers, status);
};
/**
* Set status `code`.
*
* @param {Number} code
* @return {ServerResponse}
* @api public
*/
res.status = function(code){
this.statusCode = code;
return this;
};
/**
@@ -141,7 +181,7 @@ res.sendfile = function(path, options, fn){
options = {};
}
options.path = path;
options.path = encodeURIComponent(path);
options.callback = fn;
send(this.req, this, next, options);
};
@@ -180,7 +220,7 @@ res.contentType = function(type){
res.attachment = function(filename){
if (filename) this.contentType(filename);
this.header('Content-Disposition', filename
? 'attachment; filename="' + path.basename(filename) + '"'
? 'attachment; filename="' + basename(filename) + '"'
: 'attachment');
return this;
};
@@ -189,7 +229,7 @@ res.attachment = function(filename){
* Transfer the file at the given `path`, with optional
* `filename` as an attachment and optional callback `fn(err)`,
* and optional `fn2(err)` which is invoked when an error has
* occurred after headers have been sent.
* occurred after header has been sent.
*
* @param {String} path
* @param {String|Function} filename or fn
@@ -231,17 +271,14 @@ res.download = function(path, filename, fn, fn2){
*
* @param {String} name
* @param {String} val
* @return {String}
* @return {ServerResponse} for chaining
* @api public
*/
res.header = function(name, val){
if (val === undefined) {
return this.getHeader(name);
} else {
this.setHeader(name, val);
return val;
}
if (1 == arguments.length) return this.getHeader(name);
this.setHeader(name, val);
return this;
};
/**
@@ -265,6 +302,7 @@ res.clearCookie = function(name, options){
* Options:
*
* - `maxAge` max-age in milliseconds, converted to `expires`
* - `path` defaults to the "basepath" setting which is typically "/"
*
* Examples:
*
@@ -283,6 +321,7 @@ res.clearCookie = function(name, options){
res.cookie = function(name, val, options){
options = options || {};
if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge);
if (undefined === options.path) options.path = this.app.set('basepath');
var cookie = utils.serializeCookie(name, val, options);
this.header('Set-Cookie', cookie);
};
@@ -294,8 +333,8 @@ res.cookie = function(name, val, options){
* The given `url` can also be the name of a mapped url, for
* example by default express supports "back" which redirects
* to the _Referrer_ or _Referer_ headers or the application's
* "home" setting. Express also supports "home" out of the box,
* which can be set via `app.set('home', '/blog');`, and defaults
* "basepath" setting. Express also supports "basepath" out of the box,
* which can be set via `app.set('basepath', '/blog');`, and defaults
* to '/'.
*
* Redirect Mapping:
@@ -335,8 +374,9 @@ res.cookie = function(name, val, options){
res.redirect = function(url, status){
var app = this.app
, req = this.req
, base = app.set('home') || '/'
, base = app.set('basepath') || app.route
, status = status || 302
, head = 'HEAD' == req.method
, body;
// Setup redirect map
@@ -359,16 +399,13 @@ res.redirect = function(url, status){
// Relative
if (!~url.indexOf('://')) {
// Respect mount-point
if (app.route) {
url = join(app.route, url);
}
if ('/' != base && 0 != url.indexOf(base)) url = base + url;
// Absolute
var host = req.headers.host
, tls = req.connection.encrypted;
url = 'http' + (tls ? 's' : '') + '://' + host + url;
}
// Support text/{plain,html} by default
if (req.accepts('html')) {
@@ -382,7 +419,7 @@ res.redirect = function(url, status){
// Respond
this.statusCode = status;
this.header('Location', url);
this.end(body);
this.end(head ? null : body);
};
/**

53
lib/router/collection.js Normal file
View File

@@ -0,0 +1,53 @@
/*!
* Express - router - Collection
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Expose `Collection`.
*/
module.exports = Collection;
/**
* Initialize a new route `Collection`
* with the given `router`.
*
* @param {Router} router
* @api private
*/
function Collection(router) {
Array.apply(this, arguments);
this.router = router;
}
/**
* Inherit from `Array.prototype`.
*/
Collection.prototype.__proto__ = Array.prototype;
/**
* Remove the routes in this collection.
*
* @return {Collection} of routes removed
* @api public
*/
Collection.prototype.remove = function(){
var router = this.router
, len = this.length
, ret = new Collection(this.router);
for (var i = 0; i < len; ++i) {
if (router.remove(this[i])) {
ret.push(this[i]);
}
}
return ret;
};

View File

@@ -1,6 +1,6 @@
/*!
* Express - router
* Express - Router
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
@@ -9,321 +9,390 @@
* Module dependencies.
*/
var utils = require('../utils')
var Route = require('./route')
, Collection = require('./collection')
, utils = require('../utils')
, parse = require('url').parse
, _methods = require('./methods')
, Route = require('./route');
, toArray = utils.toArray;
/**
* Expose router.
* Expose `Router` constructor.
*/
exports = module.exports = router;
exports = module.exports = Router;
/**
* Expose methods.
* Expose HTTP methods.
*/
exports.methods = _methods;
var methods = exports.methods = require('./methods');
/**
* Provides Sinatra-like routing capabilities.
*
* @param {Function} fn
* @return {Function}
* Initialize a new `Router` with the given `app`.
*
* @param {express.HTTPServer} app
* @api private
*/
function router(fn, app){
var self = this
, methods = {}
, routes = {}
, params = {};
function Router(app) {
var self = this;
this.app = app;
this.routes = {};
this.params = {};
this._params = [];
if (!fn) throw new Error('router provider requires a callback function');
// Generate method functions
_methods.forEach(function(method){
methods[method] = generateMethodFunction(method.toUpperCase());
});
// Alias del -> delete
methods.del = methods.delete;
// Apply callback to all methods
methods.all = function(){
var args = arguments;
_methods.forEach(function(name){
methods[name].apply(this, args);
});
return self;
this.middleware = function(req, res, next){
self._dispatch(req, res, next);
};
}
// Register param callback
methods.param = function(name, fn){
params[name] = fn;
};
fn.call(this, methods);
/**
* Register a param callback `fn` for the given `name`.
*
* @param {String|Function} name
* @param {Function} fn
* @return {Router} for chaining
* @api public
*/
function generateMethodFunction(name) {
var localRoutes = routes[name] = routes[name] || [];
return function(path, fn){
var keys = []
, middleware = [];
// slice middleware
if (arguments.length > 2) {
middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
fn = middleware.pop();
middleware = utils.flatten(middleware);
}
if (!path) throw new Error(name + ' route requires a path');
if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
var options = { sensitive: app.enabled('case sensitive routes') };
var route = new Route(name, path, fn, options);
route.middleware = middleware;
localRoutes.push(route);
return self;
};
Router.prototype.param = function(name, fn){
// param logic
if ('function' == typeof name) {
this._params.push(name);
return;
}
function router(req, res, next){
var route
, self = this;
// apply param functions
var params = this._params
, len = params.length
, ret;
(function pass(i){
if (route = match(req, routes, i)) {
var i = 0
, keys = route.keys;
req.params = route.params;
// Param preconditions
(function param(err) {
try {
var key = keys[i++]
, val = req.params[key]
, fn = params[key];
if ('route' == err) {
pass(req._route_index + 1);
// Error
} else if (err) {
next(err);
// Param has callback
} else if (fn) {
// Return style
if (1 == fn.length) {
req.params[key] = fn(val);
param();
// Middleware style
} else {
fn(req, res, param, val);
}
// Finished processing params
} else if (!key) {
// route middleware
i = 0;
(function nextMiddleware(err){
var fn = route.middleware[i++];
if ('route' == err) {
pass(req._route_index + 1);
} else if (err) {
next(err);
} else if (fn) {
fn(req, res, nextMiddleware);
} else {
route.callback.call(self, req, res, function(err){
if (err) {
next(err);
} else {
pass(req._route_index + 1);
}
});
}
})();
// More params
} else {
param();
}
} catch (err) {
next(err);
}
})();
} else if ('OPTIONS' == req.method) {
options(req, res, routes);
} else {
next();
}
})();
};
router.remove = function(path, method, ret){
var ret = ret || []
, route;
// method specific remove
if (method) {
method = method.toUpperCase();
if (routes[method]) {
for (var i = 0; i < routes[method].length; ++i) {
route = routes[method][i];
if (path == route.path) {
route.index = i;
routes[method].splice(i, 1);
ret.push(route);
--i;
}
}
}
// global remove
} else {
_methods.forEach(function(method){
router.remove(path, method, ret);
});
for (var i = 0; i < len; ++i) {
if (ret = params[i](name, fn)) {
fn = ret;
}
}
return ret;
};
// ensure we end up with a
// middleware function
if ('function' != typeof fn) {
throw new Error('invalid param() call for ' + name + ', got ' + fn);
}
router.lookup = function(path, method, ret){
ret = ret || [];
// method specific lookup
if (method) {
method = method.toUpperCase();
if (routes[method]) {
routes[method].forEach(function(route, i){
if (path == route.path) {
route.index = i;
ret.push(route);
}
});
}
// global lookup
} else {
_methods.forEach(function(method){
router.lookup(path, method, ret);
});
}
return ret;
};
router.match = function(url, method, ret){
var ret = ret || []
, i = 0
, route
, req;
// method specific matches
if (method) {
method = method.toUpperCase();
req = { url: url, method: method };
while (route = match(req, routes, i)) {
i = req._route_index + 1;
route.index = i;
ret.push(route);
}
// global matches
} else {
_methods.forEach(function(method){
router.match(url, method, ret);
});
}
return ret;
};
return router;
}
(this.params[name] = this.params[name] || []).push(fn);
return this;
};
/**
* Respond to OPTIONS.
* Return a `Collection` of all routes defined.
*
* @param {ServerRequest} req
* @param {ServerResponse} req
* @param {Array} routes
* @return {Collection}
* @api public
*/
Router.prototype.all = function(){
return this.find(function(){
return true;
});
};
/**
* Remove the given `route`, returns
* a bool indicating if the route was present
* or not.
*
* @param {Route} route
* @return {Boolean}
* @api public
*/
Router.prototype.remove = function(route){
var routes = this.routes[route.method]
, len = routes.length;
for (var i = 0; i < len; ++i) {
if (route == routes[i]) {
routes.splice(i, 1);
return true;
}
}
};
/**
* Return routes with route paths matching `path`.
*
* @param {String} method
* @param {String} path
* @return {Collection}
* @api public
*/
Router.prototype.lookup = function(method, path){
return this.find(function(route){
return path == route.path
&& (route.method == method
|| method == 'all');
});
};
/**
* Return routes with regexps that match the given `url`.
*
* @param {String} method
* @param {String} url
* @return {Collection}
* @api public
*/
Router.prototype.match = function(method, url){
return this.find(function(route){
return route.match(url)
&& (route.method == method
|| method == 'all');
});
};
/**
* Find routes based on the return value of `fn`
* which is invoked once per route.
*
* @param {Function} fn
* @return {Collection}
* @api public
*/
Router.prototype.find = function(fn){
var len = methods.length
, ret = new Collection(this)
, method
, routes
, route;
for (var i = 0; i < len; ++i) {
method = methods[i];
routes = this.routes[method];
if (!routes) continue;
for (var j = 0, jlen = routes.length; j < jlen; ++j) {
route = routes[j];
if (fn(route)) ret.push(route);
}
}
return ret;
};
/**
* Route dispatcher aka the route "middleware".
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Function} next
* @api private
*/
function options(req, res, routes) {
var pathname = parse(req.url).pathname
, body = optionsFor(pathname, routes).join(',');
res.send(body, { Allow: body });
}
Router.prototype._dispatch = function(req, res, next){
var params = this.params
, self = this;
// route dispatch
(function pass(i, err){
var paramCallbacks
, paramIndex = 0
, paramVal
, route
, keys
, key
, ret;
// match next route
function nextRoute(err) {
pass(req._route_index + 1, err);
}
// match route
req.route = route = self._match(req, i);
// implied OPTIONS
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
// no route
if (!route) return next(err);
// we have a route
// start at param 0
req.params = route.params;
keys = route.keys;
i = 0;
// param callbacks
function param(err) {
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
try {
if ('route' == err) {
nextRoute();
} else if (err) {
i = 0;
callbacks(err);
} else if (paramCallbacks && undefined !== paramVal) {
paramCallback();
} else if (key) {
param();
} else {
i = 0;
callbacks();
}
} catch (err) {
param(err);
}
};
param(err);
// single param callbacks
function paramCallback(err) {
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
}
// invoke route callbacks
function callbacks(err) {
var fn = route.callbacks[i++];
try {
if ('route' == err) {
nextRoute();
} else if (err && fn) {
if (fn.length < 4) return callbacks(err);
fn(err, req, res, callbacks);
} else if (fn) {
fn(req, res, callbacks);
} else {
nextRoute(err);
}
} catch (err) {
callbacks(err);
}
}
})(0);
};
/**
* Return OPTIONS array for the given `path`, matching `routes`.
* Respond to __OPTIONS__ method.
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @api private
*/
Router.prototype._options = function(req, res){
var path = parse(req.url).pathname
, body = this._optionsFor(path).join(',');
res.send(body, { Allow: body });
};
/**
* Return an array of HTTP verbs or "options" for `path`.
*
* @param {String} path
* @param {Array} routes
* @return {Array}
* @api private
*/
function optionsFor(path, routes) {
return _methods.filter(function(method){
var arr = routes[method.toUpperCase()];
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i].regexp.test(path)) return true;
Router.prototype._optionsFor = function(path){
var self = this;
return methods.filter(function(method){
var routes = self.routes[method];
if (!routes || 'options' == method) return;
for (var i = 0, len = routes.length; i < len; ++i) {
if (routes[i].match(path)) return true;
}
}).map(function(method){
return method.toUpperCase();
});
}
};
/**
* Attempt to match the given request to
* one of the routes. When successful
* a route function is returned.
* Attempt to match a route for `req`
* starting from offset `i`.
*
* @param {ServerRequest} req
* @param {Object} routes
* @return {Function}
* @param {IncomingMessage} req
* @param {Number} i
* @return {Route}
* @api private
*/
function match(req, routes, i) {
var captures
, method = req.method
, i = i || 0;
Router.prototype._match = function(req, i){
var method = req.method.toLowerCase()
, url = parse(req.url)
, path = url.pathname
, routes = this.routes
, captures
, route
, keys;
// pass HEAD to GET routes
if ('HEAD' == method) method = 'GET';
if ('head' == method) method = 'get';
// routes for this method
if (routes = routes[method]) {
var url = parse(req.url)
, pathname = url.pathname;
// matching routes
for (var len = routes.length; i < len; ++i) {
var route = routes[i]
, fn = route.callback
, path = route.regexp
, keys = route.keys;
// match
if (captures = path.exec(pathname)) {
route = routes[i];
if (captures = route.match(path)) {
keys = route.keys;
route.params = [];
for (var j = 1, l = captures.length; j < l; ++j) {
var key = keys[j-1],
val = 'string' == typeof captures[j]
// params from capture groups
for (var j = 1, jlen = captures.length; j < jlen; ++j) {
var key = keys[j-1]
, val = 'string' == typeof captures[j]
? decodeURIComponent(captures[j])
: captures[j];
if (key) {
route.params[key] = val;
route.params[key.name] = val;
} else {
route.params.push(val);
}
}
// all done
req._route_index = i;
return route;
}
}
}
}
};
/**
* Route `method`, `path`, and one or more callbacks.
*
* @param {String} method
* @param {String} path
* @param {Function} callback...
* @return {Router} for chaining
* @api private
*/
Router.prototype._route = function(method, path, callbacks){
var app = this.app
, callbacks = utils.flatten(toArray(arguments, 2));
// ensure path was given
if (!path) throw new Error('app.' + method + '() requires a path');
// create the route
var route = new Route(method, path, callbacks, {
sensitive: app.enabled('case sensitive routes')
, strict: app.enabled('strict routing')
});
// add it
(this.routes[method] = this.routes[method] || [])
.push(route);
return this;
};

View File

@@ -54,6 +54,14 @@ var RFC5323 = ['SEARCH'];
var RFC5789 = ['PATCH'];
/**
* PURGE Method for caching reverse-proxy
* http://wiki.squid-cache.org/SquidFaq/OperatingSquid#How_can_I_purge_an_object_from_my_cache.3F
* https://www.varnish-cache.org/docs/trunk/tutorial/purging.html
*/
var CACHE_PURGE = ['PURGE'];
/**
* Expose the methods.
*/
@@ -65,6 +73,7 @@ module.exports = [].concat(
, RFC3648
, RFC3744
, RFC5323
, RFC5789).map(function(method){
, RFC5789
, CACHE_PURGE).map(function(method){
return method.toLowerCase();
});

View File

@@ -13,27 +13,43 @@ module.exports = Route;
/**
* Initialize `Route` with the given HTTP `method`, `path`,
* and callback `fn` and `options`.
* and an array of `callbacks` and `options`.
*
* Options:
*
* - `sensitive` enable case-sensitive routes
* - `sensitive` enable case-sensitive routes
* - `strict` enable strict matching for trailing slashes
*
* @param {String} method
* @param {String} path
* @param {Function} fn
* @param {Array} callbacks
* @param {Object} options.
* @api private
*/
function Route(method, path, fn, options) {
function Route(method, path, callbacks, options) {
options = options || {};
this.callback = fn;
this.path = path;
this.regexp = normalize(path, this.keys = [], options.sensitive);
this.method = method;
this.callbacks = callbacks;
this.regexp = normalize(path
, this.keys = []
, options.sensitive
, options.strict);
}
/**
* Check if this route matches `path` and return captures made.
*
* @param {String} path
* @return {Array}
* @api private
*/
Route.prototype.match = function(path){
return this.regexp.exec(path);
};
/**
* Normalize the given path string,
* returning a regular expression.
@@ -46,26 +62,27 @@ function Route(method, path, fn, options) {
* @param {String|RegExp} path
* @param {Array} keys
* @param {Boolean} sensitive
* @param {Boolean} strict
* @return {RegExp}
* @api private
*/
function normalize(path, keys, sensitive) {
if (path instanceof RegExp) return path;
function normalize(path, keys, sensitive, strict) {
if (path instanceof RegExp) return path;
path = path
.concat('/?')
.concat(strict ? '' : '/?')
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
keys.push(key);
keys.push({ name: key, optional: !! optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (format || '') + (capture || '([^/]+?)') + ')'
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
+ (optional || '');
})
.replace(/([\/.])/g, '\\$1')
.replace(/\*/g, '(.+)');
.replace(/\*/g, '(.*)');
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}
}

View File

@@ -5,6 +5,19 @@
* MIT Licensed
*/
/**
* Check if `path` looks absolute.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
exports.isAbsolute = function(path){
if ('/' == path[0]) return true;
if (':' == path[1] && '\\' == path[2]) return true;
};
/**
* Merge object `b` with `a` giving precedence to
* values in object `a`.
@@ -120,3 +133,20 @@ exports.parseRange = function(size, str){
});
return valid ? arr : undefined;
};
/**
* Fast alternative to `Array.prototype.slice.call()`.
*
* @param {Arguments} args
* @param {Number} n
* @return {Array}
* @api public
*/
exports.toArray = function(args, i){
var arr = []
, len = args.length
, i = i || 0;
for (; i < len; ++i) arr.push(args[i]);
return arr;
};

View File

@@ -33,6 +33,98 @@ exports = module.exports = View;
exports.register = View.register;
/**
* Lookup and compile `view` with cache support by supplying
* both the `cache` object and `cid` string,
* followed by `options` passed to `exports.lookup()`.
*
* @param {String} view
* @param {Object} cache
* @param {Object} cid
* @param {Object} options
* @return {View}
* @api private
*/
exports.compile = function(view, cache, cid, options){
if (cache && cid && cache[cid]){
options.filename = cache[cid].path;
return cache[cid];
}
// lookup
view = exports.lookup(view, options);
// hints
if (!view.exists) {
if (options.hint) hintAtViewPaths(view.original, options);
var err = new Error('failed to locate view "' + view.original.view + '"');
err.view = view.original;
throw err;
}
// compile
options.filename = view.path;
view.fn = view.templateEngine.compile(view.contents, options);
cache[cid] = view;
return view;
};
/**
* Lookup `view`, returning an instanceof `View`.
*
* Options:
*
* - `root` root directory path
* - `defaultEngine` default template engine
* - `parentView` parent `View` object
* - `cache` cache object
* - `cacheid` optional cache id
*
* Lookup:
*
* - partial `_<name>`
* - any `<name>/index`
* - non-layout `../<name>/index`
* - any `<root>/<name>`
* - partial `<root>/_<name>`
*
* @param {String} view
* @param {Object} options
* @return {View}
* @api private
*/
exports.lookup = function(view, options){
var orig = view = new View(view, options)
, partial = options.isPartial
, layout = options.isLayout;
// Try _ prefix ex: ./views/_<name>.jade
// taking precedence over the direct path
if (partial) {
view = new View(orig.prefixPath, options);
if (!view.exists) view = orig;
}
// Try index ex: ./views/user/index.jade
if (!layout && !view.exists) view = new View(orig.indexPath, options);
// Try ../<name>/index ex: ../user/index.jade
// when calling partial('user') within the same dir
if (!layout && !view.exists) view = new View(orig.upIndexPath, options);
// Try root ex: <root>/user.jade
if (!view.exists) view = new View(orig.rootPath, options);
// Try root _ prefix ex: <root>/_user.jade
if (!view.exists && partial) view = new View(view.prefixPath, options);
view.original = orig;
return view;
};
/**
* Partial render helper.
*
@@ -42,11 +134,6 @@ exports.register = View.register;
function renderPartial(res, view, options, parentLocals, parent){
var collection, object, locals;
// Inherit parent view extension when not present
if (parent && !~view.indexOf('.')) {
view += parent.extension;
}
if (options) {
// collection
if (options.collection) {
@@ -77,12 +164,12 @@ function renderPartial(res, view, options, parentLocals, parent){
// Inherit locals from parent
union(options, parentLocals);
// Merge locals
if (locals) merge(options, locals);
// Partials dont need layouts
options.renderPartial = true;
options.isPartial = true;
options.layout = false;
// Deduce name from view path
@@ -95,8 +182,6 @@ function renderPartial(res, view, options, parentLocals, parent){
options[name] = object;
} else if (name === global) {
merge(options, object);
} else {
options.scope = object;
}
}
return res.render(view, options, null, parent, true);
@@ -120,7 +205,7 @@ function renderPartial(res, view, options, parentLocals, parent){
options.lastInCollection = i == len - 1;
object = val;
buf += render();
}
}
} else {
keys = Object.keys(collection);
len = keys.length;
@@ -145,20 +230,20 @@ function renderPartial(res, view, options, parentLocals, parent){
};
/**
* Render `view` partial with the given `options`. Optionally a
* Render `view` partial with the given `options`. Optionally a
* callback `fn(err, str)` may be passed instead of writing to
* the socket.
*
* Options:
*
* - `object` Single object with name derived from the view (unless `as` is present)
* - `object` Single object with name derived from the view (unless `as` is present)
*
* - `as` Variable name for each `collection` value, defaults to the view name.
* * as: 'something' will add the `something` local variable
* * as: this will use the collection value as the template context
* * as: global will merge the collection value's properties with `locals`
*
* - `collection` Array of objects, the name is derived from the view name itself.
* - `collection` Array of objects, the name is derived from the view name itself.
* For example _video.html_ will have a object _video_ available to it.
*
* @param {String} view
@@ -171,6 +256,7 @@ function renderPartial(res, view, options, parentLocals, parent){
res.partial = function(view, options, fn){
var app = this.app
, options = options || {}
, viewEngine = app.set('view engine')
, parent = {};
// accept callback as second argument
@@ -183,9 +269,7 @@ res.partial = function(view, options, fn){
parent.dirname = app.set('views') || process.cwd() + '/views';
// utilize "view engine" option
if (app.set('view engine')) {
parent.extension = '.' + app.set('view engine');
}
if (viewEngine) parent.engine = viewEngine;
// render the partial
try {
@@ -213,7 +297,7 @@ res.partial = function(view, options, fn){
* automatically, however otherwise a response of _200_ and _text/html_ is given.
*
* Options:
*
*
* - `scope` Template evaluation context (the value of `this`)
* - `debug` Output debugging information
* - `status` Response status code
@@ -236,8 +320,7 @@ res.render = function(view, opts, fn, parent, sub){
// callback given
if (fn) {
fn(err);
// unwind to root call to prevent
// several next(err) calls
// unwind to root call to prevent multiple callbacks
} else if (sub) {
throw err;
// root template, next(err)
@@ -256,11 +339,12 @@ res._render = function(view, opts, fn, parent, sub){
, helpers = app._locals
, dynamicHelpers = app.dynamicViewHelpers
, viewOptions = app.set('view options')
, cacheViews = app.enabled('view cache')
, root = app.set('views') || process.cwd() + '/views';
// cache id
var cid = view + (parent ? ':' + parent.path : '');
var cid = app.enabled('view cache')
? view + (parent ? ':' + parent.path : '')
: false;
// merge "view options"
if (viewOptions) merge(options, viewOptions);
@@ -280,7 +364,7 @@ res._render = function(view, opts, fn, parent, sub){
// capture attempts
options.attempts = [];
var partial = options.renderPartial
var partial = options.isPartial
, layout = options.layout;
// Layout support
@@ -328,46 +412,9 @@ res._render = function(view, opts, fn, parent, sub){
return renderPartial(self, path, opts, options, view);
};
// cached view
if (app.cache[cid]) {
view = app.cache[cid];
options.filename = view.path;
// resolve view
} else {
var orig = view = new View(view, options);
// Try _ prefix ex: ./views/_<name>.jade
if (partial) {
view = new View(orig.prefixPath, options);
if (!view.exists) view = orig;
}
// Try index ex: ./views/user/index.jade
if (!view.exists) view = new View(orig.indexPath, options);
// Try ../<name>/index ex: ../user/index.jade
// when calling partial('user') within the same dir
if (!view.exists && !options.isLayout) view = new View(orig.upIndexPath, options);
// Try root ex: <root>/user.jade
if (!view.exists) view = new View(orig.rootPath, options);
// Try root _ prefix ex: <root>/_user.jade
if (!view.exists && partial) view = new View(view.prefixPath, options);
// Does not exist
if (!view.exists) {
if (app.enabled('hints')) hintAtViewPaths(orig, options);
var err = new Error('failed to locate view "' + orig.view + '"');
err.view = orig;
throw err;
}
options.filename = view.path;
var engine = view.templateEngine;
view.fn = engine.compile(view.contents, options)
if (cacheViews) app.cache[cid] = view;
}
// View lookup
options.hint = app.enabled('hints');
view = exports.compile(view, app.cache, cid, options);
// layout helper
options.layout = function(path){
@@ -386,7 +433,7 @@ res._render = function(view, opts, fn, parent, sub){
// partial return
} else if (partial) {
return str;
// render complete, and
// render complete, and
// callback given
} else if (fn) {
fn(null, str);
@@ -410,4 +457,4 @@ function hintAtViewPaths(view, options) {
console.error(' - %s', path);
});
console.error();
}
}

View File

@@ -10,6 +10,7 @@
*/
var path = require('path')
, utils = require('../utils')
, extname = path.extname
, dirname = path.dirname
, basename = path.basename
@@ -49,7 +50,10 @@ function View(view, options) {
this.name = this.basename.replace(this.extension, '');
this.path = this.resolvePath();
this.dirname = dirname(this.path);
if (options.attempts) options.attempts.push(this.path);
if (options.attempts) {
if (!~options.attempts.indexOf(this.path))
options.attempts.push(this.path);
}
};
/**
@@ -96,7 +100,7 @@ View.prototype.resolvePath = function(){
// Implicit engine
if (!~this.basename.indexOf('.')) path += this.extension;
// Absolute
if ('/' == path[0]) return path;
if (utils.isAbsolute(path)) return path;
// Relative to parent
if (this.relative && this.parent) return this.parent.dirname + '/' + path;
// Relative to root

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "2.3.3",
"version": "2.5.9",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
@@ -10,13 +10,30 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
],
"dependencies": {
"connect": ">= 1.4.0 < 2.0.0",
"mime": ">= 0.0.1",
"qs": ">= 0.0.6"
"connect": "1.x",
"mime": "1.2.4",
"qs": "0.4.x",
"mkdirp": "0.3.0"
},
"devDependencies": {
"connect-form": "0.2.1",
"ejs": "0.4.2",
"expresso": "0.9.2",
"hamljs": "0.6.x",
"jade": "0.16.2",
"stylus": "0.13.0",
"should": "0.3.2",
"express-messages": "0.0.2",
"node-markdown": ">= 0.0.1",
"connect-redis": ">= 0.0.1"
},
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
"repository": "git://github.com/visionmedia/express",
"main": "index",
"bin": { "express": "./bin/express" },
"engines": { "node": ">= 0.4.1 < 0.5.0" }
"scripts": {
"test": "make test",
"prepublish" : "npm prune"
},
"engines": { "node": ">= 0.4.1 < 0.7.0" }
}

Submodule support/connect deleted from af32d828a3

Submodule support/ejs deleted from 499c4815a5

Submodule support/expresso deleted from 855e0dea07

Submodule support/haml deleted from 55bb6fdc79

Submodule support/jade deleted from bdeb8b9fb4

Submodule support/mime deleted from ade33a43be

Submodule support/qs deleted from 2b9796e54e

Submodule support/should deleted from 607f8734e8

View File

@@ -3,7 +3,7 @@
* Module dependencies.
*/
var express = require('express')
var express = require('../')
, connect = require('connect')
, assert = require('assert')
, should = require('should')
@@ -37,22 +37,22 @@ module.exports = {
'test basic server': function(){
var server = express.createServer();
server.get('/', function(req, res){
server.set('env').should.equal('test');
res.writeHead(200, {});
res.end('wahoo');
});
server.put('/user/:id', function(req, res){
res.writeHead(200, {});
res.end('updated user ' + req.params.id)
});
server.del('/something', function(req, res){
res.send('Destroyed');
});
server.delete('/something/else', function(req, res){
res.send('Destroyed');
});
@@ -61,7 +61,7 @@ module.exports = {
req.staff = { id: req.params.id };
next();
});
server.get('/staff/:id', function(req, res){
res.send('GET Staff ' + req.staff.id);
});
@@ -73,7 +73,7 @@ module.exports = {
server.all('*', function(req, res){
res.send('requested ' + req.url);
});
assert.response(server,
{ url: '/' },
{ body: 'wahoo' });
@@ -81,15 +81,15 @@ module.exports = {
assert.response(server,
{ url: '/user/12', method: 'PUT' },
{ body: 'updated user 12' });
assert.response(server,
{ url: '/something', method: 'DELETE' },
{ body: 'Destroyed' });
assert.response(server,
{ url: '/something/else', method: 'DELETE' },
{ body: 'Destroyed' });
assert.response(server,
{ url: '/staff/12' },
{ body: 'GET Staff 12' });
@@ -105,17 +105,17 @@ module.exports = {
'test constructor middleware': function(beforeExit){
var calls = [];
function one(req, res, next){
calls.push('one');
next();
}
function two(req, res, next){
calls.push('two');
next();
}
var app = express.createServer(one, two);
app.get('/', function(req, res){
res.writeHead(200, {});
@@ -144,14 +144,14 @@ module.exports = {
assert.response(app,
{ url: '/' },
{ body: 'Internal Server Error' });
// Custom handler
var app = express.createServer();
app.error(function(err, req, res){
res.send('Shit: ' + err.message, 500);
});
app.get('/', function(req, res, next){
next(new Error('broken'));
});
@@ -174,7 +174,7 @@ module.exports = {
app.error(function(err, req, res, next){
res.send(err.message, 500);
});
app.get('/', function(req, res, next){
throw new Error('broken');
});
@@ -220,7 +220,7 @@ module.exports = {
'test #use()': function(){
var app = express.createServer();
app.get('/users', function(req, res, next){
next(new Error('fail!!'));
});
@@ -236,7 +236,6 @@ module.exports = {
var server = express.createServer();
server.set('env', 'development');
// Config blocks
var ret = server.configure(function(){
assert.equal(this, server, 'Test context of configure() is the server');
calls.push('any');
@@ -245,13 +244,13 @@ module.exports = {
}).configure('production', function(){
calls.push('production');
});
should.equal(ret, server, 'Test #configure() returns server for chaining');
assert.response(server,
{ url: '/' },
{ body: 'Cannot GET /' });
beforeExit(function(){
calls.should.eql(['any', 'dev']);
});
@@ -259,7 +258,7 @@ module.exports = {
'test #configure() immediate call': function(){
var app = express.createServer();
app.configure(function(){
app.use(connect.bodyParser());
});
@@ -267,18 +266,17 @@ module.exports = {
app.post('/', function(req, res){
res.send(req.param('name') || 'nope');
});
assert.response(app,
{ url: '/', method: 'POST', data: 'name=tj', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }},
{ body: 'tj' });
},
'test #configure() precedence': function(){
var app = express.createServer();
app.configure(function(){
app.use(function(req, res, next){
res.writeHead(200, {});
res.write('first');
next();
});
@@ -292,12 +290,28 @@ module.exports = {
res.write(' route ');
next();
});
assert.response(app,
{ url: '/' },
{ body: 'first route last' });
},
'test #configure() multiple envs': function(){
var app = express.createServer();
app.set('env', 'prod');
var calls = [];
app.configure('stage', 'prod', function(){
calls.push('stage/prod');
});
app.configure('prod', function(){
calls.push('prod');
});
calls.should.eql(['stage/prod', 'prod']);
},
'test #set()': function(){
var app = express.createServer();
var ret = app.set('title', 'My App').set('something', 'else');
@@ -305,7 +319,7 @@ module.exports = {
app.set('title').should.equal('My App');
app.set('something').should.equal('else');
},
'test .settings': function(){
var app = express.createServer();
app.set('title', 'My App');
@@ -337,56 +351,67 @@ module.exports = {
var app = express.createServer();
app.use(connect.bodyParser());
assert.equal(2, app.stack.length);
app.post('/', function(req, res){
res.send(JSON.stringify(req.body || ''));
});
app.get('/', function(){
});
assert.equal(3, app.stack.length);
assert.response(app,
{ url: '/', method: 'POST', data: 'name=tj', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }},
{ body: '{"name":"tj"}' });
},
'test "basepath" setting': function(){
var app = express.createServer();
app.set('basepath', '/shop');
app.get('/redirect', function(req, res){
res.redirect('/cart');
});
assert.response(app,
{ url: '/redirect', headers: { Host: 'foo.com' }},
{ headers: { Location: 'http://foo.com/shop/cart' }});
},
'test mounting': function(){
var called
, app = express.createServer()
, blog = express.createServer()
, map = express.createServer()
, reg = connect.createServer();
map.set('home', '/map');
map.mounted(function(parent){
called = true;
assert.equal(this, map, 'mounted() is not in context of the child app');
assert.equal(app, parent, 'mounted() was not called with parent app');
});
reg.use(function(req, res){ res.end('hey'); });
app.use('/regular', reg);
app.use('/blog', blog);
app.use('/contact', map);
blog.route.should.equal('/blog');
map.route.should.equal('/contact');
should.equal(true, called);
app.set("test", "parent setting");
blog.set('test').should.equal('parent setting');
app.get('/', function(req, res){
app.set('home').should.equal('/');
blog.set('home').should.equal('/blog');
map.set('home').should.equal('/contact/map');
blog.set('basepath').should.equal('/blog');
map.set('basepath').should.equal('/contact');
res.send('main app');
});
blog.get('/', function(req, res){
res.send('blog index');
});
@@ -411,26 +436,26 @@ module.exports = {
{ url: '/regular' },
{ body: 'hey' });
},
'test .app property after returning control to parent': function() {
var app = express.createServer()
, blog = express.createServer();
// Mounted servers did not restore `req.app` and `res.app` when
// passing control back to parent via `out()` in `#handle()`.
blog.get('/', function(req, res, next){
req.app.should.equal(blog);
res.app.should.equal(blog);
next();
});
app.use(blog);
app.use(function(req, res, next) {
res.send((res.app === app) ? 'restored' : 'not-restored');
});
assert.response(app,
{ url: '/' },
{ body: 'restored' }
@@ -441,25 +466,37 @@ module.exports = {
function handle(req, res) {
res.send('got ' + req.string);
}
var app = express.createServer();
app.get('/', function(req, res, next){
req.string = '/';
next();
}, handle);
app.get('/another', function(req, res, next){
req.string = '/another';
next();
}, handle);
assert.response(app,
{ url: '/' },
{ body: 'got /' });
assert.response(app,
{ url: '/another' },
{ body: 'got /another' });
},
'invalid chars': function(){
var app = express.createServer();
app.get('/:name', function(req, res, next){
res.send('invalid');
});
assert.response(app,
{ url: '/%a0' },
{ status: 500 });
}
};

View File

@@ -1 +1,3 @@
h1 Forum Thread
h1 Forum Thread
!= partial('../hello')
!= partial('../hello.haml')

View File

@@ -3,4 +3,4 @@
- else if (lastInCollection)
li.last= word
- else
li(class: 'word-' + indexInCollection)= word
li(class='word-' + indexInCollection)= word

View File

@@ -1 +0,0 @@
p #{label} #{this.name}

View File

@@ -0,0 +1 @@
hello

View File

@@ -3,12 +3,24 @@
* Module dependencies.
*/
var express = require('express')
var express = require('../')
, connect = require('connect')
, assert = require('assert')
, should = require('should');
module.exports = {
'test #path': function(){
var app = express.createServer();
app.get('/search', function(req, res){
res.send(req.path);
});
assert.response(app,
{ url: '/search?q=tobi' },
{ body: '/search' });
},
'test #isXMLHttpRequest': function(){
var app = express.createServer();
@@ -155,12 +167,15 @@ module.exports = {
req.flash('info').should.eql(['one']);
req.flash('info', 'Email _sent_.');
req.flash('info', '<script>');
req.flash('info').should.eql(['Email <em>sent</em>.', '&lt;script&gt;']);
req.flash('info', '<em>%s</em>', 'html');
req.flash('info').should.eql(['Email <em>sent</em>.', '<em>html</em>']);
req.flash('info', 'Welcome _%s_ to %s', 'TJ', 'something');
req.flash('info').should.eql(['Welcome <em>TJ</em> to something']);
req.flash('info', 'Welcome %s', '<script>');
req.flash('info').should.eql(['Welcome &lt;script&gt;']);
req.flash('error', 'Foo %u', 'bar');
req.flash('error').should.eql(['Foo BAR']);
@@ -303,5 +318,36 @@ module.exports = {
assert.response(app,
{ url: '/incorrect', headers: { Referer: 'expressjs.com' }},
{ body: 'expressjs.com' });
},
'test #get(field, param)': function(){
var app = express.createServer();
app.get('/', function(req, res, next){
req.get('content-disposition', 'filename')
.should.equal('foo bar.jpg');
req.get('Content-Disposition', 'filename')
.should.equal('foo bar.jpg');
req.get('x-content-foo', 'foo').should.equal('bar');
req.get('x-content-foo', 'bar').should.equal('foo bar baz');
req.get('x-content-foo', 'woot').should.equal('tobi loki jane');
req.get('cache-control', 'max-age').should.equal('500');
req.get('foo').should.equal('');
req.get('foo', 'bar').should.equal('');
res.end();
});
var fields = {
'Content-Disposition': 'attachment; filename="foo bar.jpg"'
, 'X-Content-Foo': 'foo=bar; bar=foo bar baz; woot=tobi loki jane;'
, 'Cache-Control': 'max-age = 500'
};
assert.response(app,
{ url: '/', headers: fields },
{ body: '' });
}
};

View File

@@ -3,29 +3,73 @@
* Module dependencies.
*/
var express = require('express')
var express = require('../')
, Stream = require('stream').Stream
, assert = require('assert')
, should = require('should');
module.exports = {
'test #json()': function(){
var app = express.createServer()
, json = 'application/json; charset=utf-8';
app.get('/user', function(req, res, next){
res.json({ name: 'tj' });
});
app.get('/string', function(req, res, next){
res.json('whoop!');
});
app.get('/error', function(req, res, next){
res.json('oh noes!', 500);
});
app.get('/headers', function(req, res, next){
res.json(undefined, { 'X-Foo': 'bar' }, 302);
});
assert.response(app,
{ url: '/error' },
{ body: '"oh noes!"'
, status: 500
, headers: { 'Content-Type': json }});
assert.response(app,
{ url: '/string' },
{ body: '"whoop!"'
, headers: {
'Content-Type': json
, 'Content-Length': 8
}});
assert.response(app,
{ url: '/user' },
{ body: '{"name":"tj"}', headers: { 'Content-Type': json }});
},
'test #status()': function(){
var app = express.createServer();
app.get('/error', function(req, res, next){
res.status(500).send('OH NO');
});
assert.response(app,
{ url: '/error' },
{ body: 'OH NO', status: 500 });
},
'test #send()': function(){
var app = express.createServer();
app.get('/html', function(req, res){
res.send('<p>test</p>', { 'Content-Language': 'en' });
});
app.get('/json', function(req, res){
res.header('X-Foo', 'bar');
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
});
app.get('/jsonp', function(req, res){
app.enable('jsonp callback');
res.header('X-Foo', 'bar');
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
app.disable('jsonp callback');
res.header('X-Foo', 'bar')
.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
});
app.get('/text', function(req, res){
@@ -38,6 +82,10 @@ module.exports = {
res.send(404);
});
app.get('/status/text', function(req, res){
res.send('Oh noes!', 404);
});
app.get('/error', function(req, res){
res.send('Oh shit!', { 'Content-Type': 'text/plain' }, 500);
});
@@ -49,7 +97,11 @@ module.exports = {
app.get('/noargs', function(req, res, next){
res.send();
});
app.get('/no-content', function(req, res, next){
res.send(204);
});
app.get('/undefined', function(req, res, next){
res.send(undefined);
});
@@ -80,6 +132,82 @@ module.exports = {
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/text' },
{ body: 'wahoo'
, headers: {
'Content-Type': 'text/plain'
, 'X-Foo': 'bar'
}});
assert.response(app,
{ url: '/status/text' },
{ body: 'Oh noes!', status: 404 });
assert.response(app,
{ url: '/status' },
{ body: 'Not Found'
, status: 404
, headers: { 'Content-Type': 'text/plain' }});
assert.response(app,
{ url: '/error' },
{ body: 'Oh shit!'
, status: 500
, headers: {
'Content-Type': 'text/plain; charset=utf-8'
, 'Content-Length': '8'
}});
assert.response(app,
{ url: '/buffer' },
{ body: 'wahoo!'
, headers: {
'Content-Type': 'application/octet-stream'
, 'Content-Length': '6'
}});
assert.response(app,
{ url: '/no-content' },
{ status: 204 }, function(res){
assert.equal(undefined, res.headers['content-type']);
assert.equal(undefined, res.headers['content-length']);
});
assert.response(app,
{ url: '/noargs' },
{ status: 204 }, function(res){
assert.equal(undefined, res.headers['content-type']);
assert.equal(undefined, res.headers['content-length']);
});
assert.response(app,
{ url: '/undefined' },
{ status: 204 }, function(res){
assert.equal(undefined, res.headers['content-type']);
assert.equal(undefined, res.headers['content-length']);
});
assert.response(app,
{ url: '/json?callback=test' },
{ body: '{"foo":"bar"}'
, status: 201
, headers: {
'Content-Type': 'application/json; charset=utf-8'
, 'X-Foo': 'baz'
}});
},
'test #send() JSONP': function(){
var app = express.createServer();
app.enable('jsonp callback');
app.get('/jsonp', function(req, res){
res.header('X-Foo', 'bar');
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
});
assert.response(app,
{ url: '/jsonp?callback=test' },
{ body: 'test({"foo":"bar"});'
@@ -105,62 +233,45 @@ module.exports = {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
},
'test #json() JSONP': function(){
var app = express.createServer();
app.enable('jsonp callback');
app.get('/jsonp', function(req, res){
res.header('X-Foo', 'bar');
res.json({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
});
assert.response(app,
{ url: '/json?callback=test' },
{ body: '{"foo":"bar"}'
{ url: '/jsonp?callback=test' },
{ body: 'test({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'application/json; charset=utf-8'
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/text' },
{ body: 'wahoo'
, headers: {
'Content-Type': 'text/plain'
, 'X-Foo': 'bar'
{ url: '/jsonp?callback=baz' },
{ body: 'baz({"foo":"bar"});'
, status: 201, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/status' },
{ body: 'Not Found'
, status: 404
, headers: { 'Content-Type': 'text/plain' }});
assert.response(app,
{ url: '/error' },
{ body: 'Oh shit!'
, status: 500
{ url: '/jsonp?callback=invalid()[]' },
{ body: 'invalid({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/plain; charset=utf-8'
, 'Content-Length': '8'
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/buffer' },
{ body: 'wahoo!'
, headers: {
'Content-Type': 'application/octet-stream'
, 'Content-Length': '6'
}});
assert.response(app,
{ url: '/noargs' },
{ status: 204 }, function(res){
assert.equal(undefined, res.headers['content-type']);
assert.equal(undefined, res.headers['content-length']);
});
assert.response(app,
{ url: '/undefined' },
{ status: 204 }, function(res){
assert.equal(undefined, res.headers['content-type']);
assert.equal(undefined, res.headers['content-length']);
});
},
'test #contentType()': function(){
var app = express.createServer();
@@ -221,13 +332,13 @@ module.exports = {
var app = express.createServer()
, app2 = express.createServer();
app2.set('home', '/blog');
app2.set('basepath', '/blog');
app2.redirect('google', 'http://google.com');
app2.redirect('blog', function(req, res){
return req.params.id
? '/user/' + req.params.id + '/blog'
? '/user/' + req.params.id + '/posts'
: null;
});
@@ -268,59 +379,63 @@ module.exports = {
res.redirect('blog');
});
assert.response(app,
{ url: '/home', method: 'HEAD' },
{ body: '' });
assert.response(app,
{ url: '/html', headers: { Accept: 'text/html,text/plain', Host: 'foo.com' }},
{ body: '<p>Moved Temporarily. Redirecting to <a href="http://google.com">http://google.com</a></p>' });
assert.response(app,
{ url: '/', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Permanently. Redirecting to http://google.com'
, status: 301, headers: { Location: 'http://google.com' }});
assert.response(app,
{ url: '/back', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/'
, status: 302, headers: { Location: 'http://foo.com/', 'Content-Type': 'text/plain' }});
assert.response(app,
{ url: '/back', headers: { Referer: '/foo', Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/foo'
, status: 302, headers: { Location: 'http://foo.com/foo' }});
assert.response(app,
{ url: '/back', headers: { Referrer: '/foo', Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/foo'
, status: 302, headers: { Location: 'http://foo.com/foo' }});
assert.response(app,
{ url: '/home', headers: { Accept: 'text/plain', Host: 'foo.com' } },
{ body: 'Moved Temporarily. Redirecting to http://foo.com/'
, status: 302, headers: { Location: 'http://foo.com/' }});
assert.response(app2,
{ url: '/', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Permanently. Redirecting to http://google.com'
, status: 301, headers: { Location: 'http://google.com' }});
assert.response(app2,
{ url: '/back', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/blog'
, status: 302, headers: { Location: 'http://foo.com/blog' }});
assert.response(app2,
{ url: '/home', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/blog'
, status: 302, headers: { Location: 'http://foo.com/blog' }});
assert.response(app2,
{ url: '/google', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://google.com'
, status: 302, headers: { Location: 'http://google.com' }});
assert.response(app2,
{ url: '/user/12', headers: { Accept: 'text/plain', Host: 'foo.com' }},
{ body: 'Moved Temporarily. Redirecting to http://foo.com/user/12/blog'
, status: 302, headers: { Location: 'http://foo.com/user/12/blog', 'X-Foo': 'bar' }});
{ body: 'Moved Temporarily. Redirecting to http://foo.com/blog/user/12/posts'
, status: 302, headers: { Location: 'http://foo.com/blog/user/12/posts', 'X-Foo': 'bar' }});
},
'test #redirect() when mounted': function(){
@@ -395,10 +510,6 @@ module.exports = {
});
});
assert.response(app,
{ url: '/forum' },
{ body: 'got an error' });
assert.response(app,
{ url: '/does-not-exist' },
{ body: 'got an error' });
@@ -544,20 +655,64 @@ module.exports = {
assert.equal(null, res.headers['content-disposition']);
});
assert.response(app,
{ url: '/some%20random%20text%20file.txt' },
{ body: 'hello' });
beforeExit(function(){
calls.should.equal(1);
});
},
'test #cookie()': function(){
'test #cookie() path default': function(){
var app = express.createServer();
app.set('basepath', '/foo');
app.get('/', function(req, res){
res.cookie('rememberme', 'yes', { expires: new Date(1), httpOnly: true });
res.cookie('something', 'else');
res.redirect('/');
});
assert.response(app,
{ url: '/', headers: { Host: 'foo.com' }},
function(res){
res.headers['set-cookie']
.should.eql(['rememberme=yes; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else; path=/foo']);
});
},
'test #cookie() explicit path': function(){
var app = express.createServer();
app.set('/basepath', '/foo');
app.get('/', function(req, res){
res.cookie('rememberme', 'yes', { path: '/', expires: new Date(1), httpOnly: true });
res.cookie('something', 'else', { path: '/' });
res.redirect('/');
});
assert.response(app,
{ url: '/', headers: { Host: 'foo.com' }},
function(res){
res.headers['set-cookie']
.should.eql(['rememberme=yes; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else; path=/']);
});
},
'test #cookie() null path': function(){
var app = express.createServer();
app.set('/basepath', '/foo');
app.get('/', function(req, res){
res.cookie('rememberme', 'yes', { path: null, expires: new Date(1), httpOnly: true });
res.cookie('something', 'else', { path: null });
res.redirect('/');
});
assert.response(app,
{ url: '/', headers: { Host: 'foo.com' }},
function(res){
@@ -565,10 +720,30 @@ module.exports = {
.should.eql(['rememberme=yes; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else']);
});
},
'test #clearCookie()': function(){
'test #clearCookie() default path': function(){
var app = express.createServer();
app.set('basepath', '/foo');
app.get('/', function(req, res){
res.clearCookie('rememberme');
res.redirect('/');
});
assert.response(app,
{ url: '/' },
function(res){
res.headers['set-cookie']
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
});
},
'test #clearCookie() explicit path': function(){
var app = express.createServer();
app.set('basepath', '/bar');
app.get('/', function(req, res){
res.clearCookie('rememberme', { path: '/foo' });
res.redirect('/');
@@ -581,7 +756,7 @@ module.exports = {
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
});
},
'test HEAD': function(){
var app = express.createServer();

View File

@@ -3,7 +3,7 @@
* Module dependencies.
*/
var express = require('express')
var express = require('../')
, connect = require('connect')
, assert = require('assert')
, should = require('should')
@@ -53,7 +53,7 @@ module.exports = {
res.send(200);
});
app.get('/user/:user', [allow('member')], [[restrictAge(18)]], function(req, res){
app.get('/user/:user', [allow('member'), [[restrictAge(18)]]], function(req, res){
res.send(200);
});
@@ -74,6 +74,277 @@ module.exports = {
});
},
'test app.param() multiple mapping functions': function(){
var app = express.createServer();
app.param(function(name, fn){
if (fn.length < 3) {
return function(req, res, next, val){
val = req.params[name] = fn(val);
if (false === val) {
next('route');
} else {
next();
}
};
}
});
app.param(function(name, range){
if (!~String(range).indexOf('..')) return;
var parts = range.split('..')
, from = parseInt(parts.shift())
, to = parseInt(parts.shift());
return function(req, res, next, val){
if (val < from || val > to) return next('route');
next();
}
});
app.param('user', Number);
app.param('user', '0..5');
app.get('/user/:user', function(req, res){
res.json(req.params.user);
});
assert.response(app,
{ url: '/user/3' },
{ body: '3' });
assert.response(app,
{ url: '/user/6' },
{ status: 404 });
},
'test app.param() name passing': function(){
var app = express.createServer();
app.param(function(name, fn){
if (fn.length < 3) {
return function(req, res, next, val){
val = req.params[name] = fn(val);
if (false === val) {
next('route');
} else {
next();
}
};
}
});
function within(a, b) {
return function(req, res, next, val, name){
if (val < a || val > b) {
return next(new Error(name + ' should be within ' + a + '..' + b));
}
next();
}
}
app.param('user', Number);
app.param('user', within(0, 5));
app.get('/user/:user', function(req, res){
res.json(req.params.user);
});
app.use(function(err, req, res, next){
res.json({ error: err.message });
});
assert.response(app,
{ url: '/user/0' },
{ body: '0' });
assert.response(app,
{ url: '/user/6' },
{ body: '{"error":"user should be within 0..5"}' });
},
'test app.param() multiple callbacks and array of params': function(){
var app = express.createServer();
var users = [{ name: 'tj' }];
var pets = [['tobi', 'loki', 'jane', 'manny', 'luna']];
function loadUser(req, res, next, id) {
req.user = users[id];
next();
}
function loadUserPets(req, res, next, id) {
req.user.pets = pets[id];
next();
}
app.param(['user_id', 'user'], loadUser, loadUserPets);
app.get('/user/:user_id', function(req, res){
res.send(req.user);
});
app.get('/account/:user', function(req, res){
res.send(req.user);
});
assert.response(app,
{ url: '/account/0' },
{ body: '{"name":"tj","pets":["tobi","loki","jane","manny","luna"]}' });
assert.response(app,
{ url: '/user/0' },
{ body: '{"name":"tj","pets":["tobi","loki","jane","manny","luna"]}' });
},
'test app.param() multiple callbacks': function(){
var app = express.createServer();
var users = [{ name: 'tj' }];
var pets = [['tobi', 'loki', 'jane', 'manny', 'luna']];
function loadUser(req, res, next, id) {
req.user = users[id];
next();
}
function loadUserPets(req, res, next, id) {
req.user.pets = pets[id];
next();
}
app.param('user_id', loadUser, loadUserPets);
app.get('/user/:user_id', function(req, res){
res.send(req.user);
});
assert.response(app,
{ url: '/user/0' },
{ body: '{"name":"tj","pets":["tobi","loki","jane","manny","luna"]}' });
},
'test app.param() multiple calls with error': function(){
var app = express.createServer();
var commits = ['foo', 'bar', 'baz'];
app.param('commit', function(req, res, next, id){
req.commit = parseInt(id);
if (isNaN(req.commit)) return next('route');
next();
});
app.param('commit', function(req, res, next, id){
req.commit = commits[req.commit];
next(new Error('failed'));
});
app.get('/commit/:commit', function(req, res){
res.send(req.commit);
});
assert.response(app,
{ url: '/commit/0' },
{ status: 500 });
},
'test app.param() multiple calls': function(){
var app = express.createServer();
var commits = ['foo', 'bar', 'baz'];
app.param('commit', function(req, res, next, id){
req.commit = parseInt(id);
if (isNaN(req.commit)) return next('route');
next();
});
app.param('commit', function(req, res, next, id){
req.commit = commits[req.commit];
next();
});
app.get('/commit/:commit', function(req, res){
res.send(req.commit);
});
assert.response(app,
{ url: '/commit/0' },
{ body: 'foo' });
assert.response(app,
{ url: '/commit/0x01' },
{ body: 'bar' });
assert.response(app,
{ url: '/commit/asdf' },
{ status: 404 });
},
'test app.param(fn)': function(){
var app = express.createServer();
app.param(function(name, fn){
if (fn instanceof RegExp) {
return function(req, res, next, val){
var captures;
if (captures = fn.exec(String(val))) {
req.params[name] = captures[1];
next();
} else {
next('route');
}
}
}
});
app.param('commit', /^(\d+)$/);
app.get('/commit/:commit', function(req, res){
res.send(req.params.commit);
});
assert.response(app,
{ url: '/commit/12' },
{ body: '12' });
assert.response(app,
{ url: '/commit/asdf' },
{ status: 404 });
},
'test precedence': function(){
var app = express.createServer();
var hits = [];
app.all('*', function(req, res, next){
hits.push('all');
next();
});
app.get('/foo', function(req, res, next){
hits.push('GET /foo');
next();
});
app.get('/foo', function(req, res, next){
hits.push('GET /foo2');
next();
});
app.put('/foo', function(req, res, next){
hits.push('PUT /foo');
next();
});
assert.response(app,
{ url: '/foo' },
function(){
hits.should.eql(['all', 'GET /foo', 'GET /foo2']);
});
},
'test named capture groups': function(){
var app = express.createServer();
@@ -81,6 +352,22 @@ module.exports = {
res.send('user ' + req.params.id);
});
app.post('/pin/save/:lat(\\d+.\\d+)/:long(\\d+.\\d+)', function(req, res){
res.send(req.params.lat + ' ' + req.params.long);
});
app.post('/pin/save2/:lat([0-9]+.[0-9]+)/:long([0-9]+.[0-9]+)', function(req, res){
res.send(req.params.lat + ' ' + req.params.long);
});
assert.response(app,
{ url: '/pin/save/1.2/3.4', method: 'POST' },
{ body: '1.2 3.4' });
assert.response(app,
{ url: '/pin/save2/1.2/3.4', method: 'POST' },
{ body: '1.2 3.4' });
assert.response(app,
{ url: '/user/12' },
{ body: 'user 12' });
@@ -90,7 +377,51 @@ module.exports = {
{ body: 'Cannot GET /user/ab' });
},
'test .param()': function(){
'test named capture group after dot': function(){
var app = express.createServer();
app.get('/user/:name.:format?', function(req, res){
res.send(req.params.name + ' - ' + (req.params.format || ''));
});
assert.response(app,
{ url: '/user/foo' },
{ body: 'foo - ' });
assert.response(app,
{ url: '/user/foo.json' },
{ body: 'foo - json' });
assert.response(app,
{ url: '/user/foo.bar.json' },
{ body: 'foo.bar - json' });
},
'test optional * value': function(){
var app = express.createServer();
app.get('/admin*', function(req, res){
res.send(req.params[0]);
});
app.get('/file/*.*', function(req, res){
res.send(req.params[0] + ' - ' + req.params[1]);
});
assert.response(app,
{ url: '/file/some.foo.bar' },
{ body: 'some.foo - bar' });
assert.response(app,
{ url: '/admin', },
{ body: '', status: 200 });
assert.response(app,
{ url: '/adminify', },
{ body: 'ify', status: 200 });
},
'test app.param()': function(){
var app = express.createServer();
var users = [
@@ -101,9 +432,6 @@ module.exports = {
, { name: 'bandit' }
];
function integer(n){ return parseInt(n, 10); };
app.param(['to', 'from'], integer);
app.param('user', function(req, res, next, id){
if (req.user = users[id]) {
next();
@@ -116,13 +444,6 @@ module.exports = {
res.send('user ' + req.user.name);
});
app.get('/users/:from-:to', function(req, res, next){
var names = users.slice(req.params.from, req.params.to).map(function(user){
return user.name;
});
res.send('users ' + names.join(', '));
});
assert.response(app,
{ url: '/user/0' },
{ body: 'user tj' });
@@ -130,10 +451,35 @@ module.exports = {
assert.response(app,
{ url: '/user/1' },
{ body: 'user tobi' });
},
'test app.param() optional execution': function(beforeExit){
var app = express.createServer()
, calls = 0;
var months = ['Jan', 'Feb', 'Mar'];
app.param('month', function(req, res, next, n){
req.params.month = months[n];
++calls;
next();
});
app.get('/calendar/:month?', function(req, res, next){
res.send(req.params.month || months[0]);
});
assert.response(app,
{ url: '/users/0-3' },
{ body: 'users tj, tobi, loki' });
{ url: '/calendar' },
{ body: 'Jan' });
assert.response(app,
{ url: '/calendar/1' },
{ body: 'Feb' });
beforeExit(function(){
calls.should.equal(1);
});
},
'test OPTIONS': function(){
@@ -162,12 +508,11 @@ module.exports = {
var route = app.get('/user/:id')[0]
route.should.be.an.instanceof(Route);
route.callback.should.be.a('function');
route.callbacks.should.be.an.instanceof(Array);
route.path.should.equal('/user/:id');
route.regexp.should.be.an.instanceof(RegExp);
route.method.should.equal('GET');
route.index.should.equal(1);
route.keys.should.eql(['id']);
route.method.should.equal('get');
route.keys.should.eql([{ name: 'id', optional: false }]);
app.get('/user').should.have.length(1);
app.get('/user/:id').should.have.length(1);
@@ -225,13 +570,11 @@ module.exports = {
var route = app.match.get('/user/12')[0];
route.should.be.an.instanceof(Route);
route.callback.should.be.a('function');
route.path.should.equal('/user/:id');
route.regexp.should.be.an.instanceof(RegExp);
route.method.should.equal('GET');
route.index.should.equal(2);
route.keys.should.eql(['id']);
route.params.id.should.equal('12');
route.method.should.equal('get');
route.keys.should.eql([{ name: 'id', optional: false }]);
//route.params.id.should.equal('12');
app.match.get('/user').should.have.length(1);
app.match.get('/user/12').should.have.length(2);
@@ -242,7 +585,54 @@ module.exports = {
app.match.all('/user/123').should.have.length(3);
app.match('/user/123').should.have.length(3);
},
'test app.routes.all()': function(){
var app = express.createServer();
app.get('/user', function(){});
app.get('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/edit', function(){});
app.routes.all()[0].should.be.an.instanceof(Route);
app.routes.all().length.should.equal(5);
},
'test Collection': function(){
var app = express.createServer();
app.get('/user', function(){});
app.get('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/edit', function(){});
var ret = app.match.all('/user/12').remove();
ret.should.have.length(3);
app.match.all('/user/12').should.have.length(0);
app.get('/user/:id').should.have.length(0);
},
'test "strict routing" setting': function(){
var app = express.createServer();
app.enable('strict routing');
app.get('/:path', function(req, res, next){
res.send({ type: 'directory' });
});
app.get('/:path/', function(req, res, next){
res.send(['.', '..', 'foo.js', 'bar.js']);
});
assert.response(app,
{ url: '/lib' },
{ body: '{"type":"directory"}' });
assert.response(app,
{ url: '/lib/' },
{ body: '[".","..","foo.js","bar.js"]' });
},
'test "case sensitive routes" setting': function(){
var app = express.createServer();
@@ -263,5 +653,193 @@ module.exports = {
assert.response(app,
{ url: '/Account' },
{ body: 'Account' });
},
'override OPTIONS default': function(){
var app = express.createServer();
app.get('/', function(req, res, next){
});
app.options('/foo', function(req, res, next){
res.header('Allow', 'GET')
res.send('whatever');
});
assert.response(app,
{ url: '/', method: 'OPTIONS' },
{ body: 'GET', headers: { Allow: 'GET' }});
assert.response(app,
{ url: '/foo', method: 'OPTIONS' },
{ body: 'whatever', headers: { Allow: 'GET' }});
},
'test req.route': function(){
var app = express.createServer();
var routes = [];
app.get('/:foo?', function(req, res, next){
routes.push(req.route.path);
next();
});
app.get('/foo', function(req, res, next){
routes.push(req.route.path);
next();
});
assert.response(app,
{ url: '/foo' },
function(){
routes.should.eql(['/:foo?', '/foo']);
});
},
'test route callback error handling': function(){
var app = express.createServer()
, calls = [];
app.get('/user/:id', function(req, res, next){
calls.push('one');
next();
});
app.get('/user/:id', function(req, res, next){
calls.push('two');
next(new Error('fail'));
});
app.get('/user/:id', function(req, res, next){
calls.push('three');
next();
});
app.get('/user/*', function(err, req, res, next){
res.statusCode = 500;
res.send('error: ' + err.message);
});
app.get('/user/*', function(req, res, next){
calls.push('four');
next();
});
assert.response(app,
{ url: '/user/12' },
{ body: 'error: fail' }, function(){
calls.should.eql(['one', 'two']);
});
},
'test route callback thrown error handling': function(){
var app = express.createServer()
, calls = [];
app.get('/user/:id', function(req, res, next){
calls.push('one');
next();
});
app.get('/user/:id', function(req, res, next){
calls.push('two');
throw new Error('fail');
});
app.get('/user/:id', function(req, res, next){
calls.push('three');
next();
});
app.get('/user/*', function(err, req, res, next){
res.statusCode = 500;
res.send('error: ' + err.message);
});
app.get('/user/*', function(req, res, next){
calls.push('four');
next();
});
assert.response(app,
{ url: '/user/12' },
{ body: 'error: fail' }, function(){
calls.should.eql(['one', 'two']);
});
},
'test route callback error recovery': function(){
var app = express.createServer();
app.get('/user/:id', function(req, res, next){
next(new Error('fail'));
});
app.get('/user/*', function(err, req, res, next){
req.error = err;
next();
});
app.get('/user/*', function(req, res, next){
res.send('recovered from error: ' + req.error.message);
});
assert.response(app,
{ url: '/user/12' },
{ body: 'recovered from error: fail' });
},
'test multiple param callbacks': function(){
var app = express.createServer();
app.param('user', function(req, res, next, id){
req.user = { id: id };
next();
});
app.param('forum_id', function(req, res, next, id){
req.forum = { id: id };
next();
});
app.param('thread_id', function(req, res, next, id){
req.thread = { id: id };
next();
});
function array(req, res, next) {
req.arr = [req.user.id, req.forum.id, req.thread.id];
next();
}
app.get('/:user/:forum_id/:thread_id', array, function(req, res){
res.send(req.arr);
});
assert.response(app,
{ url: '/1/2/3' },
{ body: '["1","2","3"]' });
},
'test app.all for multiple deletes': function(beforeExit){
var app = express.createServer();
var deletes = 0;
app.all('*', function(req, res, next){
if (req.method === 'DELETE')
deletes++;
next();
});
assert.response(app,
{ url: '/', method: 'DELETE' },
{ status: 404 });
beforeExit(function(){
deletes.should.eql(1);
});
}
};

View File

@@ -3,7 +3,7 @@
* Module dependencies.
*/
var express = require('express')
var express = require('../')
, connect = require('connect')
, assert = require('assert')
, should = require('should')
@@ -105,6 +105,7 @@ module.exports = {
'test #render()': function(){
var app = create();
app.set('view engine', 'jade');
app.register('haml', require('hamljs'));
app.get('/', function(req, res){
res.render('index.jade', { layout: false });
@@ -452,19 +453,6 @@ module.exports = {
{ url: '/user' },
{ body: '<p>tj</p>' });
// as: this collection option
app.get('/person', function(req, res){
res.partial('person.jade', {
as: this,
collection: [{ name: 'tj' }],
locals: { label: 'name:' }
});
});
assert.response(app,
{ url: '/person' },
{ body: '<p>name: tj</p>' });
// as: global collection option
app.get('/videos', function(req, res){
res.partial('video.jade', {
@@ -512,19 +500,6 @@ module.exports = {
{ url: '/video-global' },
{ body: '<p>Tim Burton</p>' });
app.get('/person-this', function(req, res){
res.partial('person.jade', {
object: { name: 'tj' },
locals: { label: 'User:' },
as: this
});
});
// Non-collection as: this
assert.response(app,
{ url: '/person-this' },
{ body: '<p>User: tj</p>' });
// No options
app.get('/nothing', function(req, res){
res.partial('hello.ejs');
@@ -667,6 +642,67 @@ module.exports = {
{ body: '<p>two</p>' });
},
'test #partial() relative lookup with "view engine"': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res, next){
res.render('forum/thread', { layout: false });
});
app.get('/2', function(req, res, next){
res.render('forum/../forum/thread', { layout: false });
});
assert.response(app,
{ url: '/2' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
assert.response(app,
{ url: '/' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
},
'test #partial() relative lookup without "view engine"': function(){
var app = create();
app.get('/', function(req, res, next){
res.render('forum/thread.jade', { layout: false });
});
app.get('/2', function(req, res, next){
res.render('forum/../forum/thread.jade', { layout: false });
});
assert.response(app,
{ url: '/2' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
assert.response(app,
{ url: '/' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
},
'test #partial() relative lookup': function(){
var app = create();
app.get('/', function(req, res, next){
res.partial('forum/thread.jade');
});
app.get('/2', function(req, res, next){
res.partial('forum/../forum/thread.jade');
});
assert.response(app,
{ url: '/2' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
assert.response(app,
{ url: '/' },
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
},
'test #partial() with several calls': function(){
var app = create();