Compare commits

...

216 Commits

Author SHA1 Message Date
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
Tj Holowaychuk
8afb905a43 Release 2.3.3 2011-05-03 11:31:16 -07:00
Tj Holowaychuk
53667728a8 Fixed route-specific middleware when using the same callback function several times 2011-05-03 11:28:33 -07:00
Tj Holowaychuk
5f0a854e29 added test for route specific middleware regression 2011-05-03 11:25:54 -07:00
Tj Holowaychuk
e9ef3dd9cd ws 2011-05-03 09:41:59 -07:00
Tj Holowaychuk
f702884704 split methods supported by rfc [slaskis] 2011-05-03 09:24:51 -07:00
Tj Holowaychuk
0cb866845d npm 1.x docs 2011-05-02 12:48:45 -07:00
Tj Holowaychuk
26483029db docs for next("route"). Closes #650 2011-05-01 11:06:37 -07:00
Tj Holowaychuk
d2adcbdf67 Added "case sensitive routes" option. 2011-04-29 16:41:20 -07:00
Tj Holowaychuk
d2f963db2a fixed tests 2011-04-29 16:34:22 -07:00
TJ Holowaychuk
fc2bc1362f Merged pull request #645 from 8bitDesigner/patch-1.
Incorrect reference to template in a comment
2011-04-27 18:56:00 -07:00
8bitDesigner
6ae45d0fd3 The comment here refers to using "jade" for layouts, but you're using "ejs" instead. 2011-04-27 16:11:39 -07:00
Tj Holowaychuk
cc185a8c0e Release 2.3.2 2011-04-27 09:12:54 -07:00
Tj Holowaychuk
ae1078944c Fixed view hints
populate attempts on new View
2011-04-27 09:07:56 -07:00
Tj Holowaychuk
385a05dd10 bump 2011-04-26 15:26:24 -07:00
Tj Holowaychuk
2572a78648 Release 2.3.1 2011-04-26 15:26:04 -07:00
Tj Holowaychuk
470774cfba Fixed template caching collision issue. Closes #644
the parent view is resolved first so we always have the absolute path
available, so this should prevent collisions.
2011-04-26 15:24:31 -07:00
Tj Holowaychuk
351f6abe4c Merge branch 'refactor/router' 2011-04-26 14:36:51 -07:00
Tj Holowaychuk
53a16e1795 Added app.match() as app.match.all() 2011-04-26 13:59:59 -07:00
Tj Holowaychuk
ff77c8b205 Added app.lookup() as app.lookup.all() 2011-04-26 13:59:37 -07:00
Tj Holowaychuk
b33f38b109 Added app.remove() for app.remove.all() 2011-04-26 13:58:43 -07:00
Tj Holowaychuk
b9596d7ce8 Added app.remove.VERB() 2011-04-26 13:57:05 -07:00
Tj Holowaychuk
251175c025 test for previous commit 2011-04-26 13:30:47 -07:00
Tj Holowaychuk
fda1bc4630 moved fn.params to route.params 2011-04-26 13:30:21 -07:00
Tj Holowaychuk
83c2c176a9 misc 2011-04-26 13:28:20 -07:00
Tj Holowaychuk
9be5992f22 misc refactoring 2011-04-26 13:26:49 -07:00
Tj Holowaychuk
d8d23c0bf8 misc 2011-04-26 13:21:19 -07:00
Tj Holowaychuk
b2689fc40e docs 2011-04-26 13:16:19 -07:00
Tj Holowaychuk
a4cfde350f moved more tests 2011-04-26 13:14:21 -07:00
Tj Holowaychuk
7374027457 added router.test.js 2011-04-26 13:13:07 -07:00
Tj Holowaychuk
63328c2177 added .index to match() retvals 2011-04-26 13:01:53 -07:00
Tj Holowaychuk
c4e2ce23e5 more Route tests 2011-04-26 12:59:25 -07:00
Tj Holowaychuk
dacad53b2e Added instancoef Route test 2011-04-26 12:57:14 -07:00
Tj Holowaychuk
4ffd5280a7 expose Route 2011-04-26 12:55:31 -07:00
Tj Holowaychuk
74310fb464 misc refactoring 2011-04-26 12:53:57 -07:00
Tj Holowaychuk
8b2268cf38 Started Route implementation 2011-04-26 12:41:05 -07:00
Tj Holowaychuk
fb655f4981 added lib/router/methods.js 2011-04-26 11:49:51 -07:00
Tj Holowaychuk
7208c33d72 refactored view.js 2011-04-26 11:40:44 -07:00
Tj Holowaychuk
4efb25d048 refactored https.js 2011-04-26 11:39:09 -07:00
Tj Holowaychuk
a3678cd7f6 refactored http.js 2011-04-26 11:34:41 -07:00
Tj Holowaychuk
393d38f1ab stubbed Route 2011-04-26 11:31:31 -07:00
Tj Holowaychuk
805b9ac3a9 docs for "view cache" setting 2011-04-25 12:06:20 -07:00
Tj Holowaychuk
379b9812be refactored options() helper 2011-04-25 10:26:08 -07:00
Tj Holowaychuk
a9992b5647 docs 2011-04-25 10:19:13 -07:00
Tj Holowaychuk
b6c0a9b1b5 moved router to router/index 2011-04-25 10:17:13 -07:00
Tj Holowaychuk
1d2dd2a375 Merge branch 'feature/router' 2011-04-25 10:13:46 -07:00
Tj Holowaychuk
63db694aa2 utilizing local router 2011-04-25 10:13:30 -07:00
Tj Holowaychuk
b6aca36ad9 htmlEscape -> escape 2011-04-25 10:11:38 -07:00
Tj Holowaychuk
8420ae93fd removed old path utils 2011-04-25 10:10:43 -07:00
Tj Holowaychuk
6722716fa7 Moved router over from connect 2011-04-25 10:08:54 -07:00
Tj Holowaychuk
658e064220 Release 2.3.0 2011-04-25 09:49:54 -07:00
Tj Holowaychuk
66b472e567 connect >= 1.4.0 2011-04-25 09:32:57 -07:00
Tj Holowaychuk
3d242a607e Fixed caching of views when using several apps. Closes #637
simple fix :)
2011-04-25 09:25:23 -07:00
Tj Holowaychuk
bc68e5e7a3 misc 2011-04-25 09:20:31 -07:00
Tj Holowaychuk
aa6858189c misc refactor 2011-04-23 18:54:49 -07:00
Tj Holowaychuk
06fda62c9e Merge branch 'refactor/route-middleware' 2011-04-22 16:49:04 -07:00
Tj Holowaychuk
5688ea650d Fixed gotcha invoking app.param() callbacks once per route middleware. Closes #638 2011-04-22 16:48:54 -07:00
Tj Holowaychuk
f5c4e9d612 Updated connect submodule 2011-04-22 16:34:42 -07:00
Tj Holowaychuk
b9eda2a59d Updated connect submodule 2011-04-22 16:11:13 -07:00
Tj Holowaychuk
8c168b0971 connect >= 1.3.1 < 2.0.0 2011-04-22 16:11:07 -07:00
Tj Holowaychuk
9c20a50ee2 Added failing test for gotcha 2011-04-22 14:44:08 -07:00
Tj Holowaychuk
eac666574e viewHelpers -> _locals 2011-04-21 08:26:30 -07:00
Tj Holowaychuk
e4c840f6b8 Added res.helpers() as alias of res.locals()
to match app.locals() / app.helpers()
2011-04-20 15:26:22 -07:00
Daniel Shaw
3afbcd0acf JSON and JSONP default to UTF-8 in the same way as HTML. Closes #632.
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-04-20 09:00:02 -07:00
Daniel Shaw
8f054dbcaf JSON and JSONP default to UTF-8 in the same way as HTML. Introduces app.set('charset') to set charset default at the application level. Closes #632.
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-04-20 09:00:02 -07:00
Tj Holowaychuk
ccc39e5aa2 Fixed partial lookup precedence. Closes #631 2011-04-19 10:23:16 -07:00
Tj Holowaychuk
53d4da2a9c Added failing partial precedence test 2011-04-19 10:19:45 -07:00
Tj Holowaychuk
d9e7153fc9 Renamed "cache views" to "view cache". Closes #628 2011-04-17 16:42:03 -07:00
Tj Holowaychuk
dc02b0d5ae Added options support to res.clearCookie() 2011-04-17 16:37:16 -07:00
Aaron Heckmann
e0bc5711b8 auto set Content-Type in res.attachement
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-04-14 13:48:22 -07:00
Tj Holowaychuk
957cf45fa1 Release 2.2.2 2011-04-12 02:44:47 -07:00
Tj Holowaychuk
f4487343df Fixed filename option passing to template engine 2011-04-12 02:43:03 -07:00
Tj Holowaychuk
ca1bdb31e3 Updated jade submodule 2011-04-12 02:41:30 -07:00
Tj Holowaychuk
236a412459 docs 2011-04-11 09:46:28 -07:00
Tj Holowaychuk
759a57bdb6 Added second callback support for res.download() connection errors
since you can no longer respond, it will be helpful to have separate callbacks
2011-04-11 09:44:19 -07:00
Tj Holowaychuk
1abb674a07 temp fix for nodes res.removeHeader() after sent "bug" 2011-04-11 09:14:11 -07:00
Tj Holowaychuk
961146a287 link to express-expose 2011-04-08 17:51:41 -07:00
Tj Holowaychuk
573f940985 fixed example hasMessages
sessions are not always available
2011-04-08 12:44:03 -07:00
Tj Holowaychuk
882916bb2e Updated jade submodule 2011-04-08 12:36:41 -07:00
Tj Holowaychuk
47d1c62732 Updated connect submodule 2011-04-08 12:36:35 -07:00
Tj Holowaychuk
d39293c025 refactor 2011-04-08 12:36:26 -07:00
Tj Holowaychuk
2942dafdfd >= connect 1.2.3 2011-04-05 11:25:16 -07:00
Tj Holowaychuk
4e1aefa5b5 Release 2.2.1 2011-04-04 12:23:37 -07:00
Tj Holowaychuk
89383ecc57 misc refactoring 2011-04-03 14:27:40 -07:00
Tj Holowaychuk
08046f7692 Added better partial() object collection support
only respecting .length is fine in some cases, but
if the object has a length and has holes, it will likely
produce an unexpected result, or an undefined local in the
render call, which may some times be ideal, but most likely not.

this change allows arbitrary objects to be passed to "collection: ",
however unfortunately there is no way to arbitrarily assume the object
is a collection without passing it as the option
2011-04-03 14:27:05 -07:00
Tj Holowaychuk
885fb1fa92 docs 2011-04-01 17:55:26 -07:00
Tj Holowaychuk
6a58c71528 Merge branch 'feature/view-layout-control' 2011-04-01 17:47:11 -07:00
Tj Holowaychuk
371d66ba2a Added layout(path) helper to change the layout within a view. Closes #610 2011-04-01 17:47:03 -07:00
Tj Holowaychuk
25bddf3fb5 added layout control example 2011-04-01 17:39:18 -07:00
Tj Holowaychuk
f6e9fb13f8 Removed "request" and "response" locals
sorry, changed my mind. easy enough to expose these if you want to,
but they are to large by default
2011-04-01 17:25:05 -07:00
Tj Holowaychuk
f0df8434e7 markdown escaping 2011-03-31 08:24:43 -07:00
Tj Holowaychuk
e4d3f239e5 Updated connect submodule 2011-03-30 22:01:16 -07:00
Tj Holowaychuk
bcc22dfa6f Updated connect submodule 2011-03-30 21:59:17 -07:00
Tj Holowaychuk
f614709a01 errorHandler title 2011-03-30 21:59:15 -07:00
Tj Holowaychuk
11250d22c9 misc refactoring 2011-03-30 21:44:21 -07:00
Tj Holowaychuk
4b4de29725 Performance improved with better view caching
the entire View object is now cached in-memory, along with the lookup
logic as well. This increases rendering (with jade at least) by about 260 rps
2011-03-30 21:40:05 -07:00
Tj Holowaychuk
99b49d2718 updated docs 2011-03-30 12:11:22 -07:00
Tj Holowaychuk
6230ec55be more docs 2011-03-30 12:02:14 -07:00
Tj Holowaychuk
0733d3c585 connect 1.2.0 2011-03-30 11:58:26 -07:00
Tj Holowaychuk
6f8370ff0e Updated connect submodule 2011-03-30 11:58:09 -07:00
Tj Holowaychuk
bc244ed07e docs for app.match.VERB() 2011-03-30 11:56:14 -07:00
Tj Holowaychuk
41266aa8e4 docs for app.lookup.VERB() 2011-03-30 11:49:21 -07:00
Tj Holowaychuk
45faee3e5b Release 2.2.0 2011-03-30 11:40:47 -07:00
Tj Holowaychuk
74ff735e10 Updated haml submodule 2011-03-30 11:00:47 -07:00
Tj Holowaychuk
97879f2b16 Updated formidable submodule 2011-03-30 11:00:43 -07:00
Tj Holowaychuk
1a338251ad Updated connect-form submodule 2011-03-30 11:00:34 -07:00
Tj Holowaychuk
354dc768c1 Updated jade submodule 2011-03-30 11:00:25 -07:00
Tj Holowaychuk
8a0796cd94 Updated ejs submodule 2011-03-30 11:00:21 -07:00
Tj Holowaychuk
cbc3b26584 changed express(1) --help 2011-03-30 10:58:01 -07:00
Tj Holowaychuk
d628583db8 removed colors from express(1) 2011-03-30 10:56:11 -07:00
Tj Holowaychuk
058777be1e connect >= 1.1.6 2011-03-29 18:11:00 -07:00
Tj Holowaychuk
260d03a0c4 Merge branch 'feature/route-lookup' 2011-03-29 18:04:49 -07:00
Tj Holowaychuk
6dcf6f41cc Added app.VERB() -> [Function...], app.lookup.VERB(), and app.match.VERB(). Closes #606 2011-03-29 18:04:43 -07:00
Tj Holowaychuk
799f790886 Updated connect submodule 2011-03-29 17:40:14 -07:00
Tj Holowaychuk
cb3c4b0ea9 Updated connect submodule 2011-03-29 17:38:39 -07:00
Tj Holowaychuk
798d255ba6 Release 2.1.1 2011-03-29 10:40:26 -07:00
Tj Holowaychuk
28ba9e8ac5 Fixed res.partial(); next(err) when no callback is given [reported by aheckmann] 2011-03-29 09:56:58 -07:00
Tj Holowaychuk
7888cb0506 docs 2011-03-29 09:51:38 -07:00
Tj Holowaychuk
5e284a20cc Updated connect submodule 2011-03-29 09:49:45 -07:00
Tj Holowaychuk
770357e727 Updated expresso submodule 2011-03-29 09:49:18 -07:00
Aaron Heckmann
673ba22555 res.send(undefined) returns a 204
closes #600

Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-03-29 08:52:36 -07:00
Aaron Heckmann
fb38d9cfb7 add test for res.send(undefined)
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-03-29 08:52:34 -07:00
Aaron Heckmann
dda89a57ec ignore .swo .swp
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
2011-03-29 08:52:34 -07:00
Tj Holowaychuk
62df63d3a0 doc typo 2011-03-29 08:39:35 -07:00
Tj Holowaychuk
e71696cf34 expose err.view when failing to locate a view
allows for:

   err.view.path

etc
2011-03-28 14:44:12 -07:00
Tj Holowaychuk
b5d8d58670 repo 2011-03-27 14:23:43 -07:00
75 changed files with 2192 additions and 600 deletions

3
.gitignore vendored
View File

@@ -6,5 +6,8 @@ lib-cov
*.dat
*.out
*.pid
*.swp
*.swo
benchmarks/graphs
testing.js
node_modules/

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,120 @@
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
==================
* Added "case sensitive routes" option.
* Changed; split methods supported per rfc [slaskis]
* Fixed route-specific middleware when using the same callback function several times
2.3.2 / 2011-04-27
==================
* Fixed view hints
2.3.1 / 2011-04-26
==================
* Added `app.match()` as `app.match.all()`
* Added `app.lookup()` as `app.lookup.all()`
* Added `app.remove()` for `app.remove.all()`
* Added `app.remove.VERB()`
* Fixed template caching collision issue. Closes #644
* Moved router over from connect and started refactor
2.3.0 / 2011-04-25
==================
* Added options support to `res.clearCookie()`
* Added `res.helpers()` as alias of `res.locals()`
* Added; json defaults to UTF-8 with `res.send()`. Closes #632. [Daniel * Dependency `connect >= 1.4.0`
* Changed; auto set Content-Type in res.attachement [Aaron Heckmann]
* Renamed "cache views" to "view cache". Closes #628
* Fixed caching of views when using several apps. Closes #637
* Fixed gotcha invoking `app.param()` callbacks once per route middleware.
Closes #638
* Fixed partial lookup precedence. Closes #631
Shaw]
2.2.2 / 2011-04-12
==================
* Added second callback support for `res.download()` connection errors
* Fixed `filename` option passing to template engine
2.2.1 / 2011-04-04
==================
* Added `layout(path)` helper to change the layout within a view. Closes #610
* Fixed `partial()` collection object support.
Previously only anything with `.length` would work.
When `.length` is present one must still be aware of holes,
however now `{ collection: {foo: 'bar'}}` is valid, exposes
`keyInCollection` and `keysInCollection`.
* Performance improved with better view caching
* Removed `request` and `response` locals
* Changed; errorHandler page title is now `Express` instead of `Connect`
2.2.0 / 2011-03-30
==================
* Added `app.lookup.VERB()`, ex `app.lookup.put('/user/:id')`. Closes #606
* Added `app.match.VERB()`, ex `app.match.put('/user/12')`. Closes #606
* Added `app.VERB(path)` as alias of `app.lookup.VERB()`.
* Dependency `connect >= 1.2.0`
2.1.1 / 2011-03-29
==================
* Added; expose `err.view` object when failing to locate a view
* Fixed `res.partial()` call `next(err)` when no callback is given [reported by aheckmann]
* Fixed; `res.send(undefined)` responds with 204 [aheckmann]
2.1.0 / 2011-03-24
==================

View File

@@ -1,17 +1,13 @@
DOCS = $(shell find docs/*.md)
HTMLDOCS =$(DOCS:.md=.html)
TESTS = $(shell find test/*.test.js)
test:
@NODE_ENV=test ./support/expresso/bin/expresso \
@NODE_ENV=test ./node_modules/.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
$(TESTS)
test-cov:
@TESTFLAGS=--cov $(MAKE) test

View File

@@ -16,6 +16,10 @@
$ npm install express
or to access the `express(1)` executable install globally:
$ npm install -g express
## Features
* Robust routing
@@ -54,9 +58,12 @@ The following are the major contributors of Express (in no specific order).
## More Information
* [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)
@@ -71,6 +78,26 @@ 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
## 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
(The MIT License)

View File

@@ -11,7 +11,7 @@ var fs = require('fs')
* Framework version.
*/
var version = '2.1.0';
var version = '2.3.10';
/**
* Add session support.
@@ -37,14 +37,14 @@ var templateEngine = 'jade';
var usage = ''
+ '\n'
+ ' \x1b[1mUsage\x1b[0m: express [options] [PATH]\n'
+ ' Usage: express [options] [path]\n'
+ '\n'
+ ' \x1b[1mOptions\x1b[0m:\n'
+ ' -s, --sessions Add session support\n'
+ ' -t, --template ENGINE Add template ENGINE support (jade|ejs). Defaults to jade\n'
+ ' -c, --css ENGINE Add stylesheet ENGINE support (less|sass|stylus). Defaults to plain css\n'
+ ' -v, --version Output framework version\n'
+ ' -h, --help Output help information\n'
+ ' 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'
+ ' -v, --version output framework version\n'
+ ' -h, --help output help information\n'
;
/**
@@ -217,12 +217,8 @@ var app = [
, ' });'
, '});'
, ''
, '// Only listen on $ node app.js'
, ''
, 'if (!module.parent) {'
, ' app.listen(3000);'
, ' console.log("Express server listening on port %d", app.address().port);'
, '}'
, 'app.listen(3000);'
, 'console.log("Express server listening on port %d", app.address().port);'
, ''
].join('\n');
@@ -347,19 +343,22 @@ function createApplicationAt(path) {
// Template support
app = app.replace(':TEMPLATE', templateEngine);
write(path + '/app.js', app);
// package.json
var json = '{\n';
json += ' "name": "application-name"\n';
json += ' , "version": "0.0.1"\n';
json += ' , "private": true\n';
if (cssEngine || templateEngine) {
var comma = cssEngine ? ' ,' : '';
json += ' , "dependencies": {\n';
if (cssEngine) json += ' "' + cssEngine + '": ">= 0.0.1"\n';
if (templateEngine) json += ' ' + comma + ' "' + templateEngine + '": ">= 0.0.1"\n';
json += ' }\n';
}
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);
});
}

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

@@ -232,6 +232,8 @@
<li><a href="#app.error()">error()</a></li>
<li><a href="#app.helpers()">helpers()</a></li>
<li><a href="#app.dynamichelpers()">dynamicHelpers()</a></li>
<li><a href="#app.lookup">lookup</a></li>
<li><a href="#app.match">match</a></li>
<li><a href="#app.mounted()">mounted()</a></li>
<li><a href="#app.register()">register()</a></li>
<li><a href="#app.listen()">listen()</a></li>
@@ -338,6 +340,8 @@ app.configure('production', function(){
<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>
</ul>
@@ -429,7 +433,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>
@@ -517,6 +521,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>
@@ -593,6 +625,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>
@@ -1153,7 +1187,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>
@@ -1167,13 +1201,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
@@ -1222,7 +1265,7 @@ res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOn
<pre><code>res.cookie('rememberme', 'yes', { maxAge: 900000 });
</code></pre>
<p>To parse incoming <em>Cookie</em> headers, use the <em>cookieDecoder</em> middleware, which provides the <em>req.cookies</em> object:</p>
<p>To parse incoming <em>Cookie</em> headers, use the <em>cookieParser</em> middleware, which provides the <em>req.cookies</em> object:</p>
<pre><code>app.use(express.cookieParser());
@@ -1231,7 +1274,7 @@ 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>
@@ -1506,21 +1549,28 @@ should call <em>next(err)</em> if it does not wish to deal with the exception:</
<p>Registers static view helpers.</p>
<p> app.helpers({</p>
<pre><code> name: function(first, last){ return first + ', ' + last }
, firstName: 'tj'
, lastName: 'holowaychuk'
<pre><code>app.helpers({
name: function(first, last){ return first + ', ' + last }
, firstName: 'tj'
, lastName: 'holowaychuk'
});
</code></pre>
<p> });</p>
<p>Our view could now utilize the <em>firstName</em> and <em>lastName</em> variables,
as well as the <em>name()</em> function exposed.</p>
<pre><code>&lt;%= name(firstName, lastName) %&gt;
</code></pre>
<p>Express also provides a few locals by default:</p>
<pre><code>- `settings` the app's settings object
- `filename` the view's filename
- `layout(path)` specify the layout from within a view
</code></pre>
<p>This method is aliased as <em>app.locals()</em>.</p>
<h3 id="app.dynamichelpers()">app.dynamicHelpers(obj)</h3>
<p>Registers dynamic view helpers. Dynamic view helpers
@@ -1540,6 +1590,110 @@ becomes the local variable it is associated with.</p>
<pre><code>&lt;%= session.name %&gt;
</code></pre>
<h3 id="app.lookup">app.lookup</h3>
<p> The <em>app.lookup</em> http methods returns an array of callback functions
associated with the given <em>path</em>.</p>
<p> Suppose we define the following routes:</p>
<pre><code> app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
</code></pre>
<p> We can utilize this lookup functionality to check which routes
have been defined, which can be extremely useful for higher level
frameworks built on Express.</p>
<pre><code> app.lookup.get('/user/:id');
// =&gt; [Function]
app.lookup.get('/user/:id/:op?');
// =&gt; [Function]
app.lookup.put('/user/:id');
// =&gt; [Function]
app.lookup.all('/user/:id');
// =&gt; [Function, Function]
app.lookup.all('/hey');
// =&gt; []
</code></pre>
<p> To alias <em>app.lookup.VERB()</em>, we can simply invoke <em>app.VERB()</em>
without a callback, as a shortcut, for example the following are
equivalent:</p>
<pre><code> app.lookup.get('/user');
app.get('/user');
</code></pre>
<p> Each function returned has the following properties:</p>
<pre><code> var fn = app.get('/user/:id/:op?')[0];
fn.regexp
// =&gt; /^\/user\/(?:([^\/]+?))(?:\/([^\/]+?))?\/?$/i
fn.keys
// =&gt; ['id', 'op']
fn.path
// =&gt; '/user/:id/:op?'
fn.method
// =&gt; 'GET'
</code></pre>
<h3 id="app.match">app.match</h3>
<p> The <em>app.match</em> http methods return an array of callback functions
which match the given <em>url</em>, which may include a query string etc. This
is useful when you want reflect on which routes have the opportunity to
respond.</p>
<p> Suppose we define the following routes:</p>
<pre><code> app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
</code></pre>
<p> Our match against <strong>GET</strong> will return two functions, since the <em>:op</em>
in our second route is optional.</p>
<pre><code> app.match.get('/user/1');
// =&gt; [Function, Function]
</code></pre>
<p> This second call returns only the callback for <em>/user/:id/:op?</em>.</p>
<pre><code> app.match.get('/user/23/edit');
// =&gt; [Function]
</code></pre>
<p> We can also use <em>all()</em> to disregard the http method:</p>
<pre><code> app.match.all('/user/20');
// =&gt; [Function, Function, Function]
</code></pre>
<p> Each function matched has the following properties:</p>
<pre><code> var fn = app.match.get('/user/23/edit')[0];
fn.keys
// =&gt; ['id', 'op']
fn.params
// =&gt; { id: '23', op: 'edit' }
fn.method
// =&gt; 'GET'
</code></pre>
<h3 id="app.mounted()">app.mounted(fn)</h3>
<p>Assign a callback <em>fn</em> which is called when this <em>Server</em> is passed to <em>Server#use()</em>.</p>

View File

@@ -1,4 +1,3 @@
### Installation
$ npm install express
@@ -82,6 +81,8 @@ Express supports the following settings out of the box:
* _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
### Routing
@@ -165,7 +166,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
@@ -234,7 +235,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:
@@ -247,6 +248,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.
@@ -318,6 +346,8 @@ Commonly used "stacks" of middleware can be passed as an array (_applied recursi
For this example in full, view the [route middleware example](http://github.com/visionmedia/express/blob/master/examples/route-middleware/app.js) in the repository.
There are times when we may want to "skip" passed remaining route middleware, but continue matching subsequent routes. To do this we invoke `next()` with the string "route" `next('route')`. If no remaining routes match the request url then Express will respond with 404 Not Found.
### HTTP Methods
We have seen _app.get()_ a few times, however Express also exposes other familiar HTTP verbs in the same manor, such as _app.post()_, _app.del()_, etc.
@@ -450,14 +480,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
@@ -816,7 +838,7 @@ Options may also be passed to the internal _fs.createReadStream()_ call, for exa
// handle
});
### res.download(file[, filename[, callback]])
### res.download(file[, filename[, callback[, callback2]]])
Transfer the given _file_ as an attachment with optional alternative _filename_.
@@ -828,12 +850,20 @@ This is equivalent to:
res.attachment(file);
res.sendfile(file);
An optional callback may be supplied as either the second or third argument, which is passed to _res.sendfile()_:
An optional callback may be supplied as either the second or third argument, which is passed to _res.sendfile()_. Within this callback you may still respond, as the header has not been sent.
res.download(path, 'expenses.doc', function(err){
// handle
});
An optional second callback, _callback2_ may be given to allow you to act on connection related errors, however you should not attempt to respond.
res.download(path, function(err){
// error or finished
}, function(err){
// connection related error
});
### res.send(body|status[, headers|status[, status]])
The _res.send()_ method is a high level response utility allowing you to pass
@@ -878,7 +908,7 @@ The _maxAge_ property may be used to set _expires_ relative to _Date.now()_ in m
res.cookie('rememberme', 'yes', { maxAge: 900000 });
To parse incoming _Cookie_ headers, use the _cookieDecoder_ middleware, which provides the _req.cookies_ object:
To parse incoming _Cookie_ headers, use the _cookieParser_ middleware, which provides the _req.cookies_ object:
app.use(express.cookieParser());
@@ -886,7 +916,7 @@ To parse incoming _Cookie_ headers, use the _cookieDecoder_ middleware, which pr
// use req.cookies.rememberme
});
### res.clearCookie(name)
### res.clearCookie(name[, options])
Clear cookie _name_ by setting "expires" far in the past.
@@ -1145,9 +1175,7 @@ Express also provides a few locals by default:
- `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
This method is aliased as _app.locals()_.
@@ -1168,6 +1196,101 @@ All views would now have _session_ available so that session data can be accesse
<%= session.name %>
### app.lookup
The _app.lookup_ http methods returns an array of callback functions
associated with the given _path_.
Suppose we define the following routes:
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
We can utilize this lookup functionality to check which routes
have been defined, which can be extremely useful for higher level
frameworks built on Express.
app.lookup.get('/user/:id');
// => [Function]
app.lookup.get('/user/:id/:op?');
// => [Function]
app.lookup.put('/user/:id');
// => [Function]
app.lookup.all('/user/:id');
// => [Function, Function]
app.lookup.all('/hey');
// => []
To alias _app.lookup.VERB()_, we can simply invoke _app.VERB()_
without a callback, as a shortcut, for example the following are
equivalent:
app.lookup.get('/user');
app.get('/user');
Each function returned has the following properties:
var fn = app.get('/user/:id/:op?')[0];
fn.regexp
// => /^\/user\/(?:([^\/]+?))(?:\/([^\/]+?))?\/?$/i
fn.keys
// => ['id', 'op']
fn.path
// => '/user/:id/:op?'
fn.method
// => 'GET'
### app.match
The _app.match_ http methods return an array of callback functions
which match the given _url_, which may include a query string etc. This
is useful when you want reflect on which routes have the opportunity to
respond.
Suppose we define the following routes:
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.get('/user/:id/:op?', function(){});
Our match against __GET__ will return two functions, since the _:op_
in our second route is optional.
app.match.get('/user/1');
// => [Function, Function]
This second call returns only the callback for _/user/:id/:op?_.
app.match.get('/user/23/edit');
// => [Function]
We can also use _all()_ to disregard the http method:
app.match.all('/user/20');
// => [Function, Function, Function]
Each function matched has the following properties:
var fn = app.match.get('/user/23/edit')[0];
fn.keys
// => ['id', 'op']
fn.params
// => { id: '23', op: 'edit' }
fn.method
// => 'GET'
### app.mounted(fn)
Assign a callback _fn_ which is called when this _Server_ is passed to _Server#use()_.

View File

@@ -237,7 +237,7 @@ app.listen(3000);
<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-configure">express-configure</a> async configuration support (load settings from redis etc)</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>
</ul>

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.
*/
@@ -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

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

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

@@ -0,0 +1,32 @@
/**
* Module dependencies.
*/
var express = require('../../lib/express');
var app = express.createServer();
app.set('views', __dirname + '/views');
// set default layout, usually "layout"
app.set('view options', { layout: 'layouts/default' });
// Set our default template engine to "ejs"
// which prevents the need for extensions
// (although you can still mix and match)
app.set('view engine', 'ejs');
app.get('/', function(req, res){
res.render('pages/default');
});
app.get('/alternate', function(req, res){
// note that we do not explicitly
// state the layout here, the view does,
// although we could do it here as well.
res.render('pages/alternate');
});
app.listen(3000);
console.log('Express app started on port 3000');

View File

@@ -0,0 +1,6 @@
<html>
<body>
<h1>Alternate Layout</h1>
<%- body %>
</body>
</html>

View File

@@ -0,0 +1,6 @@
<html>
<body>
<h1>Default Layout</h1>
<%- body %>
</body>
</html>

View File

@@ -0,0 +1,2 @@
<% layout('layouts/alternate') %>
<h1>Page</h1>

View File

@@ -0,0 +1 @@
<h1>Page</h1>

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

@@ -14,7 +14,7 @@ exports.boot = function(app){
// App settings and middleware
function bootApplication(app) {
app.use(express.logger({ format: ':method :url :status' }));
app.use(express.logger(':method :url :status'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
@@ -45,6 +45,7 @@ function bootApplication(app) {
},
hasMessages: function(req){
if (!req.session) return false;
return Object.keys(req.session.flash || {}).length;
},

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,7 +1,4 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -15,7 +12,7 @@ var app = express.createServer(
express.logger(),
// Required by session() middleware
express.cookieDecoder(),
express.cookieParser(),
// Populates:
// - req.session

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

@@ -11,7 +11,8 @@
var connect = require('connect')
, HTTPSServer = require('./https')
, HTTPServer = require('./http');
, HTTPServer = require('./http')
, Route = require('./router/route')
/**
* Re-export connect auto-loaders.
@@ -27,7 +28,7 @@ var exports = module.exports = connect.middleware;
* Framework version.
*/
exports.version = '2.1.0';
exports.version = '2.3.10';
/**
* Shortcut for `new Server(...)`.
@@ -51,12 +52,14 @@ exports.createServer = function(options){
exports.HTTPServer = HTTPServer;
exports.HTTPSServer = HTTPSServer;
exports.Route = Route;
/**
* View extensions.
*/
require('./view');
exports.View =
exports.view = require('./view');
/**
* Response extensions.
@@ -69,3 +72,8 @@ require('./response');
*/
require('./request');
// Error handler title
exports.errorHandler.title = 'Express';

View File

@@ -11,12 +11,26 @@
var qs = require('qs')
, connect = require('connect')
, router = connect.router
, methods = router.methods.concat(['del', 'all'])
, router = require('./router')
, Router = require('./router')
, view = require('./view')
, toArray = require('./utils').toArray
, methods = router.methods.concat('del', 'all')
, url = require('url')
, utils = connect.utils;
/**
* Expose `HTTPServer`.
*/
exports = module.exports = HTTPServer;
/**
* Server proto.
*/
var app = HTTPServer.prototype;
/**
* Initialize a new `HTTPServer` with optional `middleware`.
*
@@ -24,7 +38,7 @@ var qs = require('qs')
* @api public
*/
var Server = exports = module.exports = function HTTPServer(middleware){
function HTTPServer(middleware){
connect.HTTPServer.call(this, []);
this.init(middleware);
};
@@ -33,7 +47,7 @@ var Server = exports = module.exports = function HTTPServer(middleware){
* Inherit from `connect.HTTPServer`.
*/
Server.prototype.__proto__ = connect.HTTPServer.prototype;
app.__proto__ = connect.HTTPServer.prototype;
/**
* Initialize the server.
@@ -42,19 +56,17 @@ Server.prototype.__proto__ = connect.HTTPServer.prototype;
* @api private
*/
Server.prototype.init = function(middleware){
app.init = function(middleware){
var self = this;
this.cache = {};
this.settings = {};
this.redirects = {};
this.isCallbacks = {};
this.viewHelpers = {};
this._locals = {};
this.dynamicViewHelpers = {};
this.errorHandlers = [];
// default "home" to /
this.set('home', '/');
// set "env" to NODE_ENV, defaulting to "development"
this.set('env', process.env.NODE_ENV || 'development');
// expose objects to each other
@@ -76,11 +88,11 @@ Server.prototype.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; });
// router
this.routes = new Router(this);
this.__defineGetter__('router', function(){
this.__usedRouter = true;
return fn;
return self.routes.middleware;
});
// default locals
@@ -89,12 +101,6 @@ Server.prototype.init = function(middleware){
, app: this
});
// default dynamic locals
this.dynamicHelpers({
request: function(req, res){ return req; }
, response: function(req, res){ return res; }
});
// default development configuration
this.configure('development', function(){
this.enable('hints');
@@ -102,19 +108,76 @@ Server.prototype.init = function(middleware){
// default production configuration
this.configure('production', function(){
this.enable('cache views');
this.enable('view cache');
});
// register error handlers on "listening"
// so that they disregard definition position.
this.on('listening', this.registerErrorHandlers.bind(this));
// route manipulation methods
methods.forEach(function(method){
self.lookup[method] = function(path){
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);
};
/**
* When using the vhost() middleware register error handlers.
*/
Server.prototype.onvhost = function(){
app.onvhost = function(){
this.registerErrorHandlers();
};
@@ -125,7 +188,7 @@ Server.prototype.onvhost = function(){
* @api public
*/
Server.prototype.registerErrorHandlers = function(){
app.registerErrorHandlers = function(){
this.errorHandlers.forEach(function(fn){
this.use(function(err, req, res, next){
fn.apply(this, arguments);
@@ -144,7 +207,7 @@ Server.prototype.registerErrorHandlers = function(){
* @api public
*/
Server.prototype.use = function(route, middleware){
app.use = function(route, middleware){
var app, home, handle;
if ('string' != typeof route) {
@@ -202,7 +265,7 @@ Server.prototype.use = function(route, middleware){
* @api public
*/
Server.prototype.mounted = function(fn){
app.mounted = function(fn){
this.__mounted = fn;
return this;
};
@@ -214,7 +277,7 @@ Server.prototype.mounted = function(fn){
* @api public
*/
Server.prototype.register = function(){
app.register = function(){
view.register.apply(this, arguments);
return this;
};
@@ -228,9 +291,9 @@ Server.prototype.register = function(){
* @api public
*/
Server.prototype.helpers =
Server.prototype.locals = function(obj){
utils.merge(this.viewHelpers, obj);
app.helpers =
app.locals = function(obj){
utils.merge(this._locals, obj);
return this;
};
@@ -243,7 +306,7 @@ Server.prototype.locals = function(obj){
* @api public
*/
Server.prototype.dynamicHelpers = function(obj){
app.dynamicHelpers = function(obj){
utils.merge(this.dynamicViewHelpers, obj);
return this;
};
@@ -252,54 +315,44 @@ Server.prototype.dynamicHelpers = function(obj){
* Map the given param placeholder `name`(s) to the given callback `fn`.
*
* 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); });
*
* 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.
*
* Alternatively the callback may accept the request, response, next, and
* the value, acting like middlware:
*
* 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'));
* }
* });
* });
*
* Now every time ":userId" is present, the associated user object
* will be loaded and assigned before the route handler is invoked.
*
* @param {String|Array} name
* @param {String|Array|Function} name
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
Server.prototype.param = function(name, fn){
app.param = function(name, fn){
// array
if (Array.isArray(name)) {
name.forEach(function(name){
this.param(name, fn);
}, this);
// 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);
}
return this;
};
@@ -312,7 +365,7 @@ Server.prototype.param = function(name, fn){
* @api public
*/
Server.prototype.error = function(fn){
app.error = function(fn){
this.errorHandlers.push(fn);
return this;
};
@@ -326,7 +379,7 @@ Server.prototype.error = function(fn){
* @api public
*/
Server.prototype.is = function(type, fn){
app.is = function(type, fn){
if (!fn) return this.isCallbacks[type];
this.isCallbacks[type] = fn;
return this;
@@ -342,7 +395,7 @@ Server.prototype.is = function(type, fn){
* @api public
*/
Server.prototype.set = function(setting, val){
app.set = function(setting, val){
if (val === undefined) {
if (this.settings.hasOwnProperty(setting)) {
return this.settings[setting];
@@ -363,7 +416,7 @@ Server.prototype.set = function(setting, val){
* @api public
*/
Server.prototype.enabled = function(setting){
app.enabled = function(setting){
return !!this.set(setting);
};
@@ -375,7 +428,7 @@ Server.prototype.enabled = function(setting){
* @api public
*/
Server.prototype.disabled = function(setting){
app.disabled = function(setting){
return !this.set(setting);
};
@@ -387,7 +440,7 @@ Server.prototype.disabled = function(setting){
* @api public
*/
Server.prototype.enable = function(setting){
app.enable = function(setting){
return this.set(setting, true);
};
@@ -399,7 +452,7 @@ Server.prototype.enable = function(setting){
* @api public
*/
Server.prototype.disable = function(setting){
app.disable = function(setting){
return this.set(setting, false);
};
@@ -412,7 +465,7 @@ Server.prototype.disable = function(setting){
* @api public
*/
Server.prototype.redirect = function(key, url){
app.redirect = function(key, url){
this.redirects[key] = url;
return this;
};
@@ -426,51 +479,46 @@ Server.prototype.redirect = function(key, url){
* @api public
*/
Server.prototype.configure = function(env, fn){
if ('function' == typeof env) {
fn = env, env = 'all';
}
if ('all' == env || env == this.settings.env) {
fn.call(this);
}
app.configure = function(env, fn){
if ('function' == typeof env) fn = env, env = 'all';
if ('all' == env || env == this.settings.env) fn.call(this);
return this;
};
// Generate routing methods
/**
* Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
*/
function generateMethod(method){
Server.prototype[method] = function(path, fn){
var self = this;
methods.forEach(function(method){
app[method] = function(path){
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);
}
});
// Ensure router is mounted
if (!this.__usedRouter) {
this.use(this.router);
}
/**
* 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
*/
// Route specific middleware support
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments, 1);
fn = args.pop();
(function stack(middleware){
middleware.forEach(function(fn){
if (Array.isArray(fn)) {
stack(fn);
} else {
self[method](path, fn);
}
});
})(args);
}
app.all = function(path){
var args = arguments;
if (1 == args.length) return this.routes.lookup('all', path);
methods.forEach(function(method){
if ('all' == method) return;
app[method].apply(this, args);
}, this);
return this;
};
// Generate the route
this.routes[method](path, fn);
return this;
};
return arguments.callee;
}
// del -> delete alias
methods.forEach(generateMethod);
app.del = app.delete;
// Alias delete as "del"
Server.prototype.del = Server.prototype.delete;

View File

@@ -13,6 +13,18 @@ var connect = require('connect')
, HTTPServer = require('./http')
, https = require('https');
/**
* Expose `HTTPSServer`.
*/
exports = module.exports = HTTPSServer;
/**
* Server proto.
*/
var app = HTTPSServer.prototype;
/**
* Initialize a new `HTTPSServer` with the
* given `options`, and optional `middleware`.
@@ -22,7 +34,7 @@ var connect = require('connect')
* @api public
*/
var Server = exports = module.exports = function HTTPSServer(options, middleware){
function HTTPSServer(options, middleware){
connect.HTTPSServer.call(this, options, []);
this.init(middleware);
};
@@ -31,10 +43,10 @@ var Server = exports = module.exports = function HTTPSServer(options, middleware
* Inherit from `connect.HTTPSServer`.
*/
Server.prototype.__proto__ = connect.HTTPSServer.prototype;
app.__proto__ = connect.HTTPSServer.prototype;
// mixin HTTPServer methods
Object.keys(HTTPServer.prototype).forEach(function(method){
Server.prototype[method] = HTTPServer.prototype[method];
app[method] = HTTPServer.prototype[method];
});

View File

@@ -197,7 +197,7 @@ req.flash = function(type, msg){
, args = arguments
, formatters = this.app.flashFormatters || {};
formatters.__proto__ = flashFormatters;
msg = utils.miniMarkdown(utils.htmlEscape(msg));
msg = utils.miniMarkdown(utils.escape(msg));
msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
var formatter = formatters[format];
if (formatter) return formatter(args[i++]);
@@ -269,7 +269,7 @@ req.is = function(type){
if ('*' == type[0] && type[1] == contentType[1]) return true;
if ('*' == type[1] && type[0] == contentType[0]) return true;
}
return ~contentType.indexOf(type);
return !! ~contentType.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) body = status = 204;
if (!arguments.length || undefined === body) body = status = 204;
// determine content type
switch (typeof body) {
@@ -75,10 +76,12 @@ res.send = function(body, headers, status){
}
} 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 + ');';
}
@@ -103,7 +106,7 @@ res.send = function(body, headers, status){
}
// strip irrelevant headers
if (204 === status) {
if (204 == status) {
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
}
@@ -130,6 +133,7 @@ res.send = function(body, headers, status){
*/
res.sendfile = function(path, options, fn){
var next = this.req.next;
options = options || {};
// support function as second arg
@@ -138,9 +142,9 @@ res.sendfile = function(path, options, fn){
options = {};
}
options.path = path;
options.path = encodeURIComponent(path);
options.callback = fn;
send(this.req, this, this.req.next, options);
send(this.req, this, next, options);
};
/**
@@ -175,40 +179,50 @@ 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;
};
/**
* Transfer the file at the given `path`, with optional
* `filename` as an attachment and optional callback `fn(err)`.
* `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.
*
* @param {String} path
* @param {String|Function} filename or fn
* @param {Function} fn
* @return {Type}
* @param {Function} fn2
* @api public
*/
res.download = function(path, filename, fn){
res.download = function(path, filename, fn, fn2){
var self = this;
// support callback as second arg
if ('function' == typeof filename) {
fn2 = fn;
fn = filename;
filename = null;
}
// transfer the file
this.attachment(filename || path).sendfile(path, function(err){
if (err) self.removeHeader('Content-Disposition');
if (fn) return fn(err);
var sentHeader = self._header;
if (err) {
self.req.next('ENOENT' == err.code
? null
: err);
if (!sentHeader) self.removeHeader('Content-Disposition');
if (sentHeader) {
fn2 && fn2(err);
} else if (fn) {
fn(err);
} else {
self.req.next(err);
}
} else if (fn) {
fn();
}
});
};
@@ -235,11 +249,15 @@ res.header = function(name, val){
* Clear cookie `name`.
*
* @param {String} name
* @param {Object} options
* @api public
*/
res.clearCookie = function(name){
this.cookie(name, '', { expires: new Date(1) });
res.clearCookie = function(name, options){
var opts = { expires: new Date(1) };
this.cookie(name, '', options
? utils.merge(options, opts)
: opts);
};
/**
@@ -394,7 +412,8 @@ res.local = function(name, val){
* @api public
*/
res.locals = function(obj){
res.locals =
res.helpers = function(obj){
if (obj) {
for (var key in obj) {
this.local(key, obj[key]);

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

383
lib/router/index.js Normal file
View File

@@ -0,0 +1,383 @@
/*!
* Express - Router
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Route = require('./route')
, Collection = require('./collection')
, utils = require('../utils')
, parse = require('url').parse
, toArray = utils.toArray;
/**
* Expose `Router` constructor.
*/
exports = module.exports = Router;
/**
* Expose HTTP methods.
*/
var methods = exports.methods = require('./methods');
/**
* Initialize a new `Router` with the given `app`.
*
* @param {express.HTTPServer} app
* @api private
*/
function Router(app) {
var self = this;
this.app = app;
this.routes = {};
this.params = {};
this._params = [];
this.middleware = function(req, res, next){
self._dispatch(req, res, next);
};
}
/**
* Register a param callback `fn` for the given `name`.
*
* @param {String|Function} name
* @param {Function} fn
* @return {Router} for chaining
* @api public
*/
Router.prototype.param = function(name, fn){
// param logic
if ('function' == typeof name) {
this._params.push(name);
return;
}
// apply param functions
var params = this._params
, len = params.length
, ret;
for (var i = 0; i < len; ++i) {
if (ret = params[i](name, fn)) {
fn = ret;
}
}
// ensure we end up with a
// middleware function
if ('function' != typeof fn) {
throw new Error('invalid param() call for ' + name + ', got ' + fn);
}
this.params[name] = fn;
return this;
};
/**
* 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
*/
Router.prototype._dispatch = function(req, res, next){
var params = this.params
, self = this;
// route dispatch
(function pass(i){
var route
, keys
, ret;
// match next route
function nextRoute() {
pass(req._route_index + 1);
}
// 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();
// we have a route
// start at param 0
req.params = route.params;
keys = route.keys;
i = 0;
(function param(err) {
var key = keys[i++]
, val = key && req.params[key.name]
, fn = key && params[key.name]
, ret;
try {
if ('route' == err) {
nextRoute();
} else if (err) {
next(err);
} else if (fn && undefined !== val) {
fn(req, res, param, val);
} else if (key) {
param();
} else {
i = 0;
middleware();
}
} catch (err) {
next(err);
}
})();
// invoke route middleware
function middleware(err) {
var fn = route.middleware[i++];
if ('route' == err) {
nextRoute();
} else if (err) {
next(err);
} else if (fn) {
fn(req, res, middleware);
} else {
done();
}
};
// invoke middleware callback
function done() {
route.callback.call(self, req, res, function(err){
if (err) return next(err);
pass(req._route_index + 1);
});
}
})(0);
};
/**
* 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
* @return {Array}
* @api private
*/
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 a route for `req`
* starting from offset `i`.
*
* @param {IncomingMessage} req
* @param {Number} i
* @return {Route}
* @api private
*/
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';
// routes for this method
if (routes = routes[method]) {
// matching routes
for (var len = routes.length; i < len; ++i) {
route = routes[i];
if (captures = route.match(path)) {
keys = route.keys;
route.params = [];
// 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.name] = val;
} else {
route.params.push(val);
}
}
// all done
req._route_index = i;
return route;
}
}
}
};
/**
* Route `method`, `path`, and optional middleware
* to the callback `fn`.
*
* @param {String} method
* @param {String} path
* @param {Function} ...
* @param {Function} fn
* @return {Router} for chaining
* @api private
*/
Router.prototype._route = function(method, path, fn){
var app = this.app
, middleware = [];
// slice middleware
if (arguments.length > 3) {
middleware = toArray(arguments, 2);
fn = middleware.pop();
middleware = utils.flatten(middleware);
}
// ensure path and callback are given
if (!path) throw new Error(method + 'route requires a path');
if (!fn) throw new Error(method + ' route ' + path + ' requires a callback');
// create the route
var route = new Route(method, path, fn, {
sensitive: app.enabled('case sensitive routes')
, middleware: middleware
});
// add it
(this.routes[method] = this.routes[method] || [])
.push(route);
return this;
};

70
lib/router/methods.js Normal file
View File

@@ -0,0 +1,70 @@
/*!
* Express - router - methods
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Hypertext Transfer Protocol -- HTTP/1.1
* http://www.ietf.org/rfc/rfc2616.txt
*/
var RFC2616 = ['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT'];
/**
* HTTP Extensions for Distributed Authoring -- WEBDAV
* http://www.ietf.org/rfc/rfc2518.txt
*/
var RFC2518 = ['PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK'];
/**
* Versioning Extensions to WebDAV
* http://www.ietf.org/rfc/rfc3253.txt
*/
var RFC3253 = ['VERSION-CONTROL', 'REPORT', 'CHECKOUT', 'CHECKIN', 'UNCHECKOUT', 'MKWORKSPACE', 'UPDATE', 'LABEL', 'MERGE', 'BASELINE-CONTROL', 'MKACTIVITY'];
/**
* Ordered Collections Protocol (WebDAV)
* http://www.ietf.org/rfc/rfc3648.txt
*/
var RFC3648 = ['ORDERPATCH'];
/**
* Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol
* http://www.ietf.org/rfc/rfc3744.txt
*/
var RFC3744 = ['ACL'];
/**
* Web Distributed Authoring and Versioning (WebDAV) SEARCH
* http://www.ietf.org/rfc/rfc5323.txt
*/
var RFC5323 = ['SEARCH'];
/**
* PATCH Method for HTTP
* http://www.ietf.org/rfc/rfc5789.txt
*/
var RFC5789 = ['PATCH'];
/**
* Expose the methods.
*/
module.exports = [].concat(
RFC2616
, RFC2518
, RFC3253
, RFC3648
, RFC3744
, RFC5323
, RFC5789).map(function(method){
return method.toLowerCase();
});

85
lib/router/route.js Normal file
View File

@@ -0,0 +1,85 @@
/*!
* Express - router - Route
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Expose `Route`.
*/
module.exports = Route;
/**
* Initialize `Route` with the given HTTP `method`, `path`,
* and callback `fn` and `options`.
*
* Options:
*
* - `sensitive` enable case-sensitive routes
* - `middleware` array of middleware
*
* @param {String} method
* @param {String} path
* @param {Function} fn
* @param {Object} options.
* @api private
*/
function Route(method, path, fn, options) {
options = options || {};
this.callback = fn;
this.path = path;
this.method = method;
this.regexp = normalize(path, this.keys = [], options.sensitive);
this.middleware = options.middleware;
}
/**
* 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.
*
* An empty array should be passed,
* which will contain the placeholder
* key names. For example "/user/:id" will
* then contain ["id"].
*
* @param {String|RegExp} path
* @param {Array} keys
* @param {Boolean} sensitive
* @return {RegExp}
* @api private
*/
function normalize(path, keys, sensitive) {
if (path instanceof RegExp) return path;
path = path
.concat('/?')
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
keys.push({ name: key, optional: !! optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (format || '') + (capture || '([^/]+?)') + ')'
+ (optional || '');
})
.replace(/([\/.])/g, '\\$1')
.replace(/\*/g, '(.+)');
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}

View File

@@ -5,64 +5,6 @@
* MIT Licensed
*/
/**
* Module dependencies.
*/
var path = require('path')
, basename = path.basename
, dirname = path.dirname
, extname = path.extname;
/**
* Memory cache.
*/
var cache = {
basename: {}
, dirname: {}
, extname: {}
};
/**
* Cached basename.
*
* @param {String} path
* @return {String}
* @api private
*/
exports.basename = function(path){
return cache.basename[path]
|| (cache.basename[path] = basename(path));
};
/**
* Cached dirname.
*
* @param {String} path
* @return {String}
* @api private
*/
exports.dirname = function(path){
return cache.dirname[path]
|| (cache.dirname[path] = dirname(path));
};
/**
* Cached extname.
*
* @param {String} path
* @return {String}
* @api private
*/
exports.extname = function(path){
return cache.extname[path]
|| (cache.extname[path] = extname(path));
};
/**
* Merge object `b` with `a` giving precedence to
* values in object `a`.
@@ -88,6 +30,27 @@ exports.union = function(a, b){
return a;
};
/**
* Flatten the given `arr`.
*
* @param {Array} arr
* @return {Array}
* @api private
*/
exports.flatten = function(arr, ret){
var ret = ret || []
, len = arr.length;
for (var i = 0; i < len; ++i) {
if (Array.isArray(arr[i])) {
exports.flatten(arr[i], ret);
} else {
ret.push(arr[i]);
}
}
return ret;
};
/**
* Parse mini markdown implementation.
* The following conversions are supported,
@@ -117,7 +80,7 @@ exports.miniMarkdown = function(str){
* @api private
*/
exports.htmlEscape = function(html) {
exports.escape = function(html) {
return String(html)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
@@ -157,3 +120,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

@@ -21,14 +21,6 @@ var path = require('path')
, http = require('http')
, res = http.ServerResponse.prototype;
/**
* Memory cache.
*
* @type Object
*/
var cache = {};
/**
* Expose constructors.
*/
@@ -41,6 +33,91 @@ 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]) 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
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);
// 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);
view.original = orig;
return view;
};
/**
* Partial render helper.
*
@@ -50,11 +127,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) {
@@ -113,16 +185,39 @@ function renderPartial(res, view, options, parentLocals, parent){
// Collection support
if (collection) {
var len = collection.length
, buf = '';
, buf = ''
, keys
, key
, val;
options.collectionLength = len;
for (var i = 0; i < len; ++i) {
var val = collection[i];
options.firstInCollection = i === 0;
options.indexInCollection = i;
options.lastInCollection = i === len - 1;
object = val;
buf += render();
if ('number' == typeof len || Array.isArray(collection)) {
for (var i = 0; i < len; ++i) {
val = collection[i];
options.firstInCollection = i == 0;
options.indexInCollection = i;
options.lastInCollection = i == len - 1;
object = val;
buf += render();
}
} else {
keys = Object.keys(collection);
len = keys.length;
options.collectionLength = len;
options.collectionKeys = keys;
for (var i = 0; i < len; ++i) {
key = keys[i];
val = collection[key];
options.keyInCollection = key;
options.firstInCollection = i == 0;
options.indexInCollection = i;
options.lastInCollection = i == len - 1;
object = val;
buf += render();
}
}
return buf;
} else {
return render();
@@ -156,6 +251,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
@@ -168,9 +264,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 {
@@ -179,8 +273,9 @@ res.partial = function(view, options, fn){
if (fn) {
fn(err);
} else {
throw err;
this.req.next(err);
}
return;
}
// callback or transfer
@@ -220,7 +315,8 @@ res.render = function(view, opts, fn, parent, sub){
// callback given
if (fn) {
fn(err);
// unwind to root call
// unwind to root call to prevent
// several next(err) calls
} else if (sub) {
throw err;
// root template, next(err)
@@ -236,10 +332,15 @@ res._render = function(view, opts, fn, parent, sub){
var options = {}
, self = this
, app = this.app
, helpers = app.viewHelpers
, helpers = app._locals
, dynamicHelpers = app.dynamicViewHelpers
, viewOptions = app.set('view options')
, cacheTemplates = app.set('cache views');
, root = app.set('views') || process.cwd() + '/views';
// cache id
var cid = app.enabled('view cache')
? view + (parent ? ':' + parent.path : '')
: false;
// merge "view options"
if (viewOptions) merge(options, viewOptions);
@@ -256,10 +357,10 @@ res._render = function(view, opts, fn, parent, sub){
// status support
if (options.status) this.statusCode = options.status;
// Defaults
var self = this
, root = app.set('views') || process.cwd() + '/views'
, partial = options.renderPartial
// capture attempts
options.attempts = [];
var partial = options.renderPartial
, layout = options.layout;
// Layout support
@@ -282,31 +383,6 @@ res._render = function(view, opts, fn, parent, sub){
// charset option
if (options.charset) this.charset = options.charset;
// Populate view
var orig = view = new View(view, options);
// Try _ prefix ex: ./views/_<name>.jade
if (!view.exists) view = new View(orig.prefixPath, options);
// 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);
throw new Error('failed to locate view "' + orig.view + '"');
}
// Dynamic helper support
if (false !== options.dynamicHelpers) {
// cache
@@ -332,15 +408,18 @@ res._render = function(view, opts, fn, parent, sub){
return renderPartial(self, path, opts, options, view);
};
// Provide filename to engine
// View lookup
options.hint = app.enabled('hints');
view = exports.compile(view, app.cache, cid, options);
options.filename = view.path;
// Attempt render
var engine = view.templateEngine
, template = cacheTemplates
? cache[view.path] || (cache[view.path] = engine.compile(view.contents, options))
: engine.compile(view.contents, options)
, str = template.call(options.scope, options);
// layout helper
options.layout = function(path){
layout = path;
};
// render
var str = view.fn.call(options.scope, options);
// layout expected
if (layout) {
@@ -371,10 +450,8 @@ res._render = function(view, opts, fn, parent, sub){
function hintAtViewPaths(view, options) {
console.error();
console.error('failed to locate view "' + view.view + '", tried:');
console.error(' - ' + new View(view.path, options).path);
console.error(' - ' + new View(view.prefixPath, options).path);
console.error(' - ' + new View(view.indexPath, options).path);
if (!options.isLayout) console.error(' - ' + new View(view.upIndexPath, options).path);
if (options.isLayout) console.error(' - ' + new View(view.rootPath, options).path);
options.attempts.forEach(function(path){
console.error(' - %s', path);
});
console.error();
}

View File

@@ -9,25 +9,25 @@
* Module dependencies.
*/
var utils = require('../utils')
, extname = utils.extname
, dirname = utils.dirname
, basename = utils.basename
var path = require('path')
, extname = path.extname
, dirname = path.dirname
, basename = path.basename
, fs = require('fs')
, stat = fs.statSync;
/**
* Memory cache.
* Expose `View`.
*/
exports = module.exports = View;
/**
* Require cache.
*/
var cache = {};
/**
* Existance cache.
*/
var exists = {};
/**
* Initialize a new `View` with the given `view` path and `options`.
*
@@ -36,9 +36,8 @@ var exists = {};
* @api private
*/
var View = exports = module.exports = function View(view, options) {
function View(view, options) {
options = options || {};
// TODO: more caching
this.view = view;
this.root = options.root;
this.relative = false !== options.relative;
@@ -50,6 +49,10 @@ var View = exports = module.exports = function View(view, options) {
this.name = this.basename.replace(this.extension, '');
this.path = this.resolvePath();
this.dirname = dirname(this.path);
if (options.attempts) {
if (!~options.attempts.indexOf(this.path))
options.attempts.push(this.path);
}
};
/**
@@ -60,16 +63,11 @@ var View = exports = module.exports = function View(view, options) {
*/
View.prototype.__defineGetter__('exists', function(){
var path = this.path;
if (null != exists[path]) {
return exists[path];
} else {
try {
stat(path);
return exists[path] = true;
} catch (err) {
return exists[path] = false;
}
try {
stat(this.path);
return true;
} catch (err) {
return false;
}
});

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "2.1.0",
"version": "2.3.10",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
@@ -10,11 +10,24 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
],
"dependencies": {
"connect": ">= 1.1.1 < 2.0.0",
"connect": ">= 1.4.1 < 2.0.0",
"mime": ">= 0.0.1",
"qs": ">= 0.0.6"
},
"devDependencies": {
"connect-form": "0.2.1",
"ejs": "0.4.2",
"expresso": "0.7.2",
"hamljs": "0.5.1",
"jade": "0.11.0",
"stylus": "0.13.0",
"should": "0.2.1",
"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" }

Submodule support/connect deleted from 2b3cea7744

Submodule support/ejs deleted from 673e6f23cb

Submodule support/expresso deleted from 2c8759f147

Submodule support/haml deleted from 4122210f38

Submodule support/jade deleted from 1e23782b6f

Submodule support/mime deleted from ade33a43be

Submodule support/qs deleted from 2b9796e54e

Submodule support/should deleted from 607f8734e8

View File

@@ -6,7 +6,8 @@
var express = require('express')
, connect = require('connect')
, assert = require('assert')
, should = require('should');
, should = require('should')
, Route = express.Route;
module.exports = {
'test inheritance': function(){
@@ -17,6 +18,7 @@ module.exports = {
'test constructor exports': function(){
express.should.have.property('HTTPServer');
express.should.have.property('HTTPSServer');
express.should.have.property('Route');
},
'test connect middleware autoloaders': function(){
@@ -435,131 +437,41 @@ module.exports = {
);
},
'test route middleware': function(){
var app = express.createServer();
function allow(role) {
return function(req, res, next) {
// this is totally not real, dont use this :)
// for tests only
if (req.headers['x-role'] == role) {
next();
} else {
res.send(401);
}
}
}
function restrictAge(age) {
return function(req, res, next){
if (req.headers['x-age'] >= age) {
next();
} else {
res.send(403);
}
}
'test routes with same callback': function(){
function handle(req, res) {
res.send('got ' + req.string);
}
app.get('/xxx', allow('member'), restrictAge(18), function(req, res){
res.send(200);
});
app.get('/booze', [allow('member')], restrictAge(18), function(req, res){
res.send(200);
});
app.get('/tobi', [allow('member')], [[restrictAge(18)]], function(req, res){
res.send(200);
});
['xxx', 'booze', 'tobi'].forEach(function(thing){
assert.response(app,
{ url: '/' + thing },
{ body: 'Unauthorized', status: 401 });
assert.response(app,
{ url: '/' + thing, headers: { 'X-Role': 'member' }},
{ body: 'Forbidden', status: 403 });
assert.response(app,
{ url: '/' + thing, headers: { 'X-Role': 'member', 'X-Age': 18 }},
{ body: 'OK', status: 200 });
});
},
'test named capture groups': function(){
var app = express.createServer();
app.get('/user/:id([0-9]{2,10})', function(req, res){
res.send('user ' + req.params.id);
});
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: '/user/12' },
{ body: 'user 12' });
{ url: '/' },
{ body: 'got /' });
assert.response(app,
{ url: '/user/ab' },
{ body: 'Cannot GET /user/ab' });
{ url: '/another' },
{ body: 'got /another' });
},
'test .param()': function(){
'invalid chars': function(){
var app = express.createServer();
var users = [
{ name: 'tj' }
, { name: 'tobi' }
, { name: 'loki' }
, { name: 'jane' }
, { 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();
} else {
next(new Error('failed to find user'));
}
app.get('/:name', function(req, res, next){
res.send('invalid');
});
app.get('/user/:user', function(req, res, next){
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' });
assert.response(app,
{ url: '/user/1' },
{ body: 'user tobi' });
assert.response(app,
{ url: '/users/0-3' },
{ body: 'users tj, tobi, loki' });
},
'test OPTIONS': function(){
var app = express.createServer();
app.get('/', function(){});
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
assert.response(app,
{ url: '/', method: 'OPTIONS' },
{ headers: { Allow: 'GET' }});
assert.response(app,
{ url: '/user/12', method: 'OPTIONS' },
{ headers: { Allow: 'GET,PUT' }});
{ url: '/%a0' },
{ status: 500 });
}
};

1
test/fixtures/_foobar.jade vendored Normal file
View File

@@ -0,0 +1 @@
p two

1
test/fixtures/error.jade vendored Normal file
View File

@@ -0,0 +1 @@
= user.name

1
test/fixtures/foobar.jade vendored Normal file
View File

@@ -0,0 +1 @@
p one

View File

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

2
test/fixtures/layout-switch.jade vendored Normal file
View File

@@ -0,0 +1,2 @@
- layout('layouts/alternate')
h1 My Page

1
test/fixtures/layouts/alternate.jade vendored Normal file
View File

@@ -0,0 +1 @@
#alternate!= body

1
test/fixtures/object-item.jade vendored Normal file
View File

@@ -0,0 +1 @@
li #{keyInCollection}: #{item}

View File

@@ -0,0 +1 @@
hello

View File

@@ -15,7 +15,7 @@ module.exports = {
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);
@@ -38,6 +38,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);
});
@@ -50,6 +54,10 @@ module.exports = {
res.send();
});
app.get('/undefined', function(req, res, next){
res.send(undefined);
});
app.get('/bool', function(req, res, next){
res.send(true);
});
@@ -57,7 +65,7 @@ module.exports = {
assert.response(app,
{ url: '/bool' },
{ body: 'true'
, headers: { 'Content-Type': 'application/json' }});
, headers: { 'Content-Type': 'application/json; charset=utf-8' }});
assert.response(app,
{ url: '/html' },
@@ -72,7 +80,7 @@ module.exports = {
{ body: '{"foo":"bar"}'
, status: 201
, headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json; charset=utf-8'
, 'X-Foo': 'baz'
}});
@@ -81,7 +89,7 @@ module.exports = {
{ body: 'test({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript'
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
@@ -89,7 +97,7 @@ module.exports = {
{ url: '/jsonp?callback=baz' },
{ body: 'baz({"foo":"bar"});'
, status: 201, headers: {
'Content-Type': 'text/javascript'
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
@@ -98,7 +106,7 @@ module.exports = {
{ body: 'invalid({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript'
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
@@ -107,7 +115,7 @@ module.exports = {
{ body: '{"foo":"bar"}'
, status: 201
, headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json; charset=utf-8'
, 'X-Foo': 'baz'
}});
@@ -118,7 +126,11 @@ module.exports = {
'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'
@@ -148,6 +160,13 @@ module.exports = {
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(){
@@ -196,12 +215,14 @@ module.exports = {
assert.response(app,
{ url: '/javascripts/jquery.js' },
{ body: 'whatever'
, headers: { 'Content-Disposition': 'attachment; filename="jquery.js"' }});
, headers: { 'Content-Type': 'application/javascript'
, 'Content-Disposition': 'attachment; filename="jquery.js"' }});
assert.response(app,
{ url: '/style.css' },
{ body: 'some stylezzz'
, headers: { 'Content-Disposition': 'attachment' }});
, headers: { 'Content-Type': 'text/html; charset=utf-8'
, 'Content-Disposition': 'attachment' }});
},
'test #redirect()': function(){
@@ -531,6 +552,10 @@ 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);
});
@@ -557,7 +582,7 @@ module.exports = {
var app = express.createServer();
app.get('/', function(req, res){
res.clearCookie('rememberme');
res.clearCookie('rememberme', { path: '/foo' });
res.redirect('/');
});
@@ -565,7 +590,7 @@ module.exports = {
{ url: '/' },
function(res){
res.headers['set-cookie']
.should.eql(['rememberme=; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
});
},

385
test/router.test.js Normal file
View File

@@ -0,0 +1,385 @@
/**
* Module dependencies.
*/
var express = require('express')
, connect = require('connect')
, assert = require('assert')
, should = require('should')
, Route = express.Route;
module.exports = {
'test route middleware': function(beforeExit){
var app = express.createServer()
, calls = 0;
function allow(role) {
return function(req, res, next) {
// this is totally not real, dont use this :)
// for tests only
if (req.headers['x-role'] == role) {
next();
} else {
res.send(401);
}
}
}
function restrictAge(age) {
return function(req, res, next){
if (req.headers['x-age'] >= age) {
next();
} else {
res.send(403);
}
}
}
app.param('user', function(req, res, next, user){
++calls;
next();
});
app.get('/xxx', allow('member'), restrictAge(18), function(req, res){
res.send(200);
});
app.get('/booze', [allow('member')], restrictAge(18), function(req, res){
res.send(200);
});
app.get('/tobi', [allow('member')], [[restrictAge(18)]], function(req, res){
res.send(200);
});
app.get('/user/:user', [allow('member'), [[restrictAge(18)]]], function(req, res){
res.send(200);
});
['xxx', 'booze', 'tobi', 'user/tj'].forEach(function(thing){
assert.response(app,
{ url: '/' + thing },
{ body: 'Unauthorized', status: 401 });
assert.response(app,
{ url: '/' + thing, headers: { 'X-Role': 'member' }},
{ body: 'Forbidden', status: 403 });
assert.response(app,
{ url: '/' + thing, headers: { 'X-Role': 'member', 'X-Age': 18 }},
{ body: 'OK', status: 200 });
});
beforeExit(function(){
calls.should.equal(3);
});
},
'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();
app.get('/user/:id([0-9]{2,10})', function(req, res){
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' });
assert.response(app,
{ url: '/user/ab' },
{ body: 'Cannot GET /user/ab' });
},
'test app.param()': function(){
var app = express.createServer();
var users = [
{ name: 'tj' }
, { name: 'tobi' }
, { name: 'loki' }
, { name: 'jane' }
, { name: 'bandit' }
];
app.param('user', function(req, res, next, id){
if (req.user = users[id]) {
next();
} else {
next(new Error('failed to find user'));
}
});
app.get('/user/:user', function(req, res, next){
res.send('user ' + req.user.name);
});
assert.response(app,
{ url: '/user/0' },
{ body: 'user tj' });
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: '/calendar' },
{ body: 'Jan' });
assert.response(app,
{ url: '/calendar/1' },
{ body: 'Feb' });
beforeExit(function(){
calls.should.equal(1);
});
},
'test OPTIONS': function(){
var app = express.createServer();
app.get('/', function(){});
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
assert.response(app,
{ url: '/', method: 'OPTIONS' },
{ headers: { Allow: 'GET' }});
assert.response(app,
{ url: '/user/12', method: 'OPTIONS' },
{ headers: { Allow: 'GET,PUT' }});
},
'test app.lookup': 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 route = app.get('/user/:id')[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.keys.should.eql([{ name: 'id', optional: false }]);
app.get('/user').should.have.length(1);
app.get('/user/:id').should.have.length(1);
app.get('/user/:id/:op?').should.have.length(1);
app.put('/user/:id').should.have.length(1);
app.get('/user/:id/edit').should.have.length(1);
app.get('/').should.have.be.empty;
app.all('/user/:id').should.have.length(2);
app.lookup.get('/user').should.have.length(1);
app.lookup.get('/user/:id').should.have.length(1);
app.lookup.get('/user/:id/:op?').should.have.length(1);
app.lookup.put('/user/:id').should.have.length(1);
app.lookup.get('/user/:id/edit').should.have.length(1);
app.lookup.get('/').should.have.be.empty;
app.lookup.all('/user/:id').should.have.length(2);
app.lookup('/user/:id').should.have.length(2);
},
'test app.remove': function(){
var app = express.createServer();
app.get('/user', function(){});
app.get('/user', function(){});
app.put('/user', function(){});
app.get('/user').should.have.length(2);
var removed = app.remove.get('/user');
removed.should.have.length(2);
var removed = app.remove.get('/user');
removed.should.have.length(0);
app.get('/user').should.have.length(0);
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.del('/user/:id', function(){});
app.remove.all('/user/:id').should.have.length(3);
app.remove.all('/user/:id').should.have.length(0);
app.get('/user/:id', function(){});
app.put('/user/:id', function(){});
app.del('/user/:id', function(){});
app.remove('/user/:id').should.have.length(3);
},
'test app.match': 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 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.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);
app.match.get('/user/12/:op?').should.have.length(1);
app.match.put('/user/100').should.have.length(1);
app.match.get('/user/5/edit').should.have.length(2);
app.match.get('/').should.have.be.empty;
app.match.all('/user/123').should.have.length(3);
app.match('/user/123').should.have.length(3);
},
'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 "case sensitive routes" setting': function(){
var app = express.createServer();
app.enable('case sensitive routes');
app.get('/account', function(req, res){
res.send('account');
});
app.get('/Account', function(req, res){
res.send('Account');
});
assert.response(app,
{ url: '/account' },
{ body: 'account' });
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']);
});
}
};

View File

@@ -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 });
@@ -287,6 +288,19 @@ module.exports = {
{ body: '<cool><p>Welcome</p></cool>' });
},
'test #render() view layout control': function(){
var app = create();
app.set('view engine', 'jade');
app.get('/', function(req, res){
res.render('layout-switch');
});
assert.response(app,
{ url: '/' },
{ body: '<div id="alternate"><h1>My Page</h1></div>' });
},
'test #render() "view engine" with periods in dirname': function(){
var app = create();
app.set('view engine', 'jade');
@@ -358,6 +372,19 @@ module.exports = {
});
},
'test #partial() collection object': function(){
var app = create();
app.get('/', function(req, res){
var items = { 2: 'foo', bar: 'bar' };
res.partial('object-item.jade', { as: 'item', collection: items });
});
assert.response(app,
{ url: '/' },
{ body: '<li>2: foo</li><li>bar: bar</li>' });
},
'test #partial()': function(){
var app = create();
@@ -619,8 +646,89 @@ module.exports = {
assert.response(app,
{ url: '/root/underscore' },
{ body: '<p>Testing</p>' });
// error in template
app.get('/error', function(req, res){
process.nextTick(function(){
res.partial('error');
});
});
assert.response(app,
{ url: '/error' },
{ status: 500 });
app.get('/underscore', function(req, res, next){
res.partial('foobar');
});
assert.response(app,
{ url: '/underscore' },
{ 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();