Comparar commits
267 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 003599cbda | |||
| bacfad37a9 | |||
| 6ef6ba7b8a | |||
| 93c0da0367 | |||
| 268d19e8a3 | |||
| a383b302c2 | |||
| 95ebbf2608 | |||
| f880574c78 | |||
| 350c310906 | |||
| 5026f7686e | |||
| ab3337ddf5 | |||
| c9268f2e3d | |||
| fa01388f97 | |||
| d8dc89ac7b | |||
| a659fd1fa9 | |||
| 27fa3ba34c | |||
| 663f42948e | |||
| baca8f0cdb | |||
| c5c3a82b23 | |||
| c0617864e1 | |||
| d9ef9ab497 | |||
| 65d9f783e1 | |||
| f70232d1b0 | |||
| b083aed245 | |||
| 0faac20d53 | |||
| cef14ca7cd | |||
| 090debe0df | |||
| 296f1270ee | |||
| 334a18a114 | |||
| dd4ddae082 | |||
| 4983e967bc | |||
| 0b5e2f14ed | |||
| 70102aecb5 | |||
| ae75153561 | |||
| dd23d60513 | |||
| db95481b76 | |||
| 3e0da535b9 | |||
| c74602551e | |||
| 1722e2c7a5 | |||
| 8a845e2951 | |||
| 72765bada0 | |||
| b380f180cc | |||
| f1799799a1 | |||
| 4bc8a8673d | |||
| cbdd907393 | |||
| aa7ff3ef47 | |||
| ebdf6434eb | |||
| 9b7470d204 | |||
| 9f3ab8683c | |||
| fa464aca90 | |||
| 2724ce368a | |||
| 89f7cbe877 | |||
| fab9af2206 | |||
| 4d01b4fa59 | |||
| c3365aa37e | |||
| 426e80d4c3 | |||
| 30a282d126 | |||
| 0a96621e3e | |||
| d9d30abe7a | |||
| 03b56d8140 | |||
| 1f79ce4e96 | |||
| 8d80f23a0f | |||
| d89b951451 | |||
| 1906a38ec8 | |||
| 8d91a3e3b8 | |||
| 3324259977 | |||
| ec2db2d541 | |||
| 5cf248c7f3 | |||
| 3876a566ff | |||
| 334e6cc618 | |||
| 64033fcfc2 | |||
| fbe3af4719 | |||
| 699ed4ee85 | |||
| 903f67a154 | |||
| 9a89dab9e0 | |||
| 435a2565b8 | |||
| 8267b8da57 | |||
| 8b5773c246 | |||
| fd4c88d202 | |||
| 9b26623aa5 | |||
| b115ec7c14 | |||
| a2e6e2a06a | |||
| f0eab06a8b | |||
| b6c839d693 | |||
| 177a724d58 | |||
| 949803dcf6 | |||
| b763a88e60 | |||
| 18c7f164a8 | |||
| e037ff6bba | |||
| 95bfda1ff0 | |||
| b9992e0b45 | |||
| f6ba793885 | |||
| bbe65a8ea3 | |||
| 20af7371d0 | |||
| a33fb0c439 | |||
| 87003c8b5b | |||
| ddd48ee79f | |||
| a9819df3ee | |||
| 15dffb22aa | |||
| 6ef5f1ff62 | |||
| acc28c68a4 | |||
| 6c2da60eb0 | |||
| 490081077d | |||
| f4a08092da | |||
| 0116efd4a3 | |||
| 9719a24f8a | |||
| 3a7d5a099b | |||
| 27dba655fc | |||
| a25dd387bf | |||
| a89767cc16 | |||
| a13df5b68d | |||
| 6a689504ff | |||
| 6521d2bcfb | |||
| 26d55b15de | |||
| 041b7e8a29 | |||
| 6eeb425e7a | |||
| d2d0eb829e | |||
| 058505b664 | |||
| 8adecedb40 | |||
| 838a51db21 | |||
| 4d4dfc27fc | |||
| 1444439cf3 | |||
| e64fc8d5ab | |||
| 9a31f772d9 | |||
| 12876ee4ce | |||
| a4a349da45 | |||
| ecbc3cae71 | |||
| 57609c87fb | |||
| bb2e24e028 | |||
| 5cafb26e93 | |||
| f74ef7b293 | |||
| 7ef8f531e7 | |||
| afb65c7b19 | |||
| 74a0673663 | |||
| 20fae6e512 | |||
| 509cd242f7 | |||
| f01ec516eb | |||
| 394b1000d2 | |||
| e0b9fba788 | |||
| 378ee29cc2 | |||
| 02bcef71dd | |||
| 248e7c8003 | |||
| a2e93ebd56 | |||
| 21a9454de1 | |||
| ec286eed08 | |||
| f16978d1fe | |||
| fa3adc7606 | |||
| 192144fc5d | |||
| a4d155c5e4 | |||
| 6c56e9d199 | |||
| bb18f87a98 | |||
| dda6a31381 | |||
| 6e25e18e98 | |||
| ffd978c24f | |||
| 4c4af03a5a | |||
| c0739bb8b7 | |||
| 5add7cb0b1 | |||
| 99f49f1061 | |||
| 401eaca9ab | |||
| da784fe02b | |||
| d507b29559 | |||
| 35ca13b38d | |||
| 04b92a31a8 | |||
| dd311d759d | |||
| b504ca9f0a | |||
| bd61c5c721 | |||
| 24694b0047 | |||
| 5258154285 | |||
| 221f993b46 | |||
| ae5ea70b93 | |||
| f59ffbeb63 | |||
| ce55275634 | |||
| 20c5892f9a | |||
| 9231245a3f | |||
| cbfed20e6f | |||
| 640274ed52 | |||
| 1507a39a2b | |||
| e6405653d8 | |||
| be1541a646 | |||
| f37f9bf5e3 | |||
| a5c39ca8c8 | |||
| cb7e7b2a74 | |||
| 2e8e705bab | |||
| ab3812432b | |||
| d2acb36e3b | |||
| 601743ff7a | |||
| 7eca74ac05 | |||
| 02b4ec04bb | |||
| 8ead9014d0 | |||
| 4920e82dd6 | |||
| 3150253661 | |||
| baa423b45a | |||
| 77e5d8ea96 | |||
| 9a8166d649 | |||
| da8c0cd2db | |||
| fc2711b543 | |||
| b4327bd36c | |||
| f7f75923aa | |||
| 0b4388bc5b | |||
| f08883071f | |||
| bb8a2ee3a6 | |||
| 6bfa7595d8 | |||
| 8308e501cd | |||
| 25909c0d9d | |||
| 2844b1ac38 | |||
| 6fd62b1cee | |||
| 1579f53a61 | |||
| 7af7851952 | |||
| 5dbd1a6dcc | |||
| 24881537a1 | |||
| 631c29b26b | |||
| ab249d4032 | |||
| 5bfa3f6274 | |||
| 82ee25dfaa | |||
| fd9a24553f | |||
| ae1c20fad9 | |||
| 734f777ff9 | |||
| 498115d5d9 | |||
| 033ff06b68 | |||
| c145a8e1a1 | |||
| ae5b0dbc29 | |||
| 95aa957c80 | |||
| 7dff9ddeeb | |||
| b4e755747f | |||
| 348fa402be | |||
| 344218ff3d | |||
| 90a1a2febf | |||
| e234a24894 | |||
| d9beb75bd6 | |||
| dadea8c83d | |||
| 31d40e5509 | |||
| a917e84b51 | |||
| f30b497206 | |||
| 706e32c20b | |||
| 4d98552c37 | |||
| 5311bd3f4e | |||
| 8d716fb7ba | |||
| 01b3caaf49 | |||
| cd73c0b9ca | |||
| 7ec5230402 | |||
| 9dbe026f83 | |||
| 3fae10da3e | |||
| d775154b92 | |||
| 6c306b3542 | |||
| 58b46ffeac | |||
| 0bb7b7c60e | |||
| 58adf9a385 | |||
| d6ba968ace | |||
| 1706721a62 | |||
| 7f88bd2eaf | |||
| b819366265 | |||
| 47ff7d9dba | |||
| e5f5254877 | |||
| 4778f728f9 | |||
| e80e4573be | |||
| fdf0f3f282 | |||
| a81fce47d0 | |||
| 2cd6989b11 | |||
| 21abb0d205 | |||
| 5e1a271ca2 | |||
| 868e4c41e1 | |||
| a6e23bb713 | |||
| 446406dd62 | |||
| 091f9f7eae | |||
| 10b1e06263 | |||
| a8865a51fd | |||
| 32343297fc |
@@ -1,4 +1,61 @@
|
||||
|
||||
1.0.0rc4 / 2010-10-14
|
||||
==================
|
||||
|
||||
* Added _NODE_ENV_ support, _EXPRESS_ENV_ is deprecated and will be removed in 1.0.0
|
||||
* Added route-middleware support (very helpful, see the [docs](http://expressjs.com/guide.html#Route-Middleware))
|
||||
* Added _jsonp callback_ setting to enable/disable jsonp autowrapping [Dav Glass]
|
||||
* Added callback query check on response.send to autowrap JSON objects for simple webservice implementations [Dav Glass]
|
||||
* Added `partial()` support for array-like collections. Closes #434
|
||||
* Added support for swappable querystring parsers
|
||||
* Added session usage docs. Closes #443
|
||||
* Added dynamic helper caching. Closes #439 [suggested by maritz]
|
||||
* Added authentication example
|
||||
* Added basic Range support to `res.sendfile()` (and `res.download()` etc)
|
||||
* Changed; `express(1)` generated app using 2 spaces instead of 4
|
||||
* Default env to "development" again [aheckmann]
|
||||
* Removed _context_ option is no more, use "scope"
|
||||
* Fixed; exposing _./support_ libs to examples so they can run without installs
|
||||
* Fixed mvc example
|
||||
|
||||
1.0.0rc3 / 2010-09-20
|
||||
==================
|
||||
|
||||
* Added confirmation for `express(1)` app generation. Closes #391
|
||||
* Added extending of flash formatters via `app.flashFormatters`
|
||||
* Added flash formatter support. Closes #411
|
||||
* Added streaming support to `res.sendfile()` using `sys.pump()` when >= "stream threshold"
|
||||
* Added _stream threshold_ setting for `res.sendfile()`
|
||||
* Added `res.send()` __HEAD__ support
|
||||
* Added `res.clearCookie()`
|
||||
* Added `res.cookie()`
|
||||
* Added `res.render()` headers option
|
||||
* Added `res.redirect()` response bodies
|
||||
* Added `res.render()` status option support. Closes #425 [thanks aheckmann]
|
||||
* Fixed `res.sendfile()` responding with 403 on malicious path
|
||||
* Fixed `res.download()` bug; when an error occurs remove _Content-Disposition_
|
||||
* Fixed; mounted apps settings now inherit from parent app [aheckmann]
|
||||
* Fixed; stripping Content-Length / Content-Type when 204
|
||||
* Fixed `res.send()` 204. Closes #419
|
||||
* Fixed multiple _Set-Cookie_ headers via `res.header()`. Closes #402
|
||||
* Fixed bug messing with error handlers when `listenFD()` is called instead of `listen()`. [thanks guillermo]
|
||||
|
||||
|
||||
1.0.0rc2 / 2010-08-17
|
||||
==================
|
||||
|
||||
* Added `app.register()` for template engine mapping. Closes #390
|
||||
* Added `res.render()` callback support as second argument (no options)
|
||||
* Added callback support to `res.download()`
|
||||
* Added callback support for `res.sendfile()`
|
||||
* Added support for middleware access via `express.middlewareName()` vs `connect.middlewareName()`
|
||||
* Added "partials" setting to docs
|
||||
* Added default expresso tests to `express(1)` generated app. Closes #384
|
||||
* Fixed `res.sendfile()` error handling, defer via `next()`
|
||||
* Fixed `res.render()` callback when a layout is used [thanks guillermo]
|
||||
* Fixed; `make install` creating ~/.node_libraries when not present
|
||||
* Fixed issue preventing error handlers from being defined anywhere. Closes #387
|
||||
|
||||
1.0.0rc / 2010-07-28
|
||||
==================
|
||||
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+7
-3
@@ -13,6 +13,7 @@ HTMLDOCS =$(DOCS:.md=.html)
|
||||
|
||||
install: install-docs
|
||||
@mkdir -p $(PREFIX)/bin
|
||||
@mkdir -p $(LIB_PREFIX)
|
||||
cp -f bin/express $(PREFIX)/bin/express
|
||||
cp -fr lib/express $(LIB_PREFIX)/express
|
||||
|
||||
@@ -29,14 +30,14 @@ uninstall-support:
|
||||
cd support/jade && $(MAKE) uninstall
|
||||
|
||||
install-docs:
|
||||
@mkdir -p $(PREFIX)/share/man/man1
|
||||
cp -f docs/executable.1 $(PREFIX)/share/man/man1/express.1
|
||||
|
||||
uninstall-docs:
|
||||
rm -f $(PREFIX)/share/man/man1/express.1
|
||||
|
||||
test:
|
||||
@CONNECT_ENV=test ./support/expresso/bin/expresso \
|
||||
--growl \
|
||||
@NODE_ENV=test ./support/expresso/bin/expresso \
|
||||
-I lib \
|
||||
-I support/connect/lib \
|
||||
-I support/haml/lib \
|
||||
@@ -44,6 +45,7 @@ test:
|
||||
-I support/ejs/lib \
|
||||
$(TESTFLAGS) \
|
||||
test/*.test.js
|
||||
|
||||
test-cov:
|
||||
@TESTFLAGS=--cov $(MAKE) test
|
||||
|
||||
@@ -52,7 +54,9 @@ docs: docs/api.html $(MANPAGES) $(HTMLDOCS)
|
||||
@./support/toc.js docs/guide.html
|
||||
|
||||
docs/api.html: lib/express/*.js
|
||||
dox --title Express \
|
||||
dox \
|
||||
--private \
|
||||
--title Express \
|
||||
--desc "High performance web framework for [node](http://nodejs.org)." \
|
||||
$(shell find lib/express/* -type f) > $@
|
||||
|
||||
|
||||
+11
-7
@@ -2,7 +2,7 @@
|
||||
# Express
|
||||
|
||||
Insanely fast (and small) server-side JavaScript web development framework
|
||||
built on [node](http://nodejs.org) and [Connect](http://github.com/extjs/Connect).
|
||||
built on [node](http://nodejs.org) and [Connect](http://github.com/senchalabs/connect).
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
@@ -39,7 +39,9 @@ git clone, first update the submodules:
|
||||
* Environment based configuration
|
||||
* Session based flash notifications
|
||||
* Built on [Connect](http://github.com/senchalabs/connect)
|
||||
* High test coverage
|
||||
* Executable for generating applications quickly
|
||||
* Application level view options
|
||||
|
||||
Via Connect:
|
||||
|
||||
@@ -64,19 +66,21 @@ The following are the major contributors of Express (in no specific order).
|
||||
|
||||
## More Information
|
||||
|
||||
* Express [Contrib](http://github.com/visionmedia/express-contrib) repo for additional functionality
|
||||
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
|
||||
* [Google Group](http://groups.google.com/group/express-js) for discussion
|
||||
* [JavaScript Extensions & Utilities](http://github.com/visionmedia/ext.js)
|
||||
* [JavaScript Sass](http://github.com/visionmedia/sass.js)
|
||||
* [JavaScript Haml](http://github.com/visionmedia/haml.js)
|
||||
* [JavaScript Jade](http://github.com/visionmedia/jade) Haml successor
|
||||
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
|
||||
|
||||
## Node Compatibility
|
||||
|
||||
The latest release of Express is compatible with node --version:
|
||||
|
||||
v0.1.100
|
||||
|
||||
v0.2.3
|
||||
|
||||
and connect --version:
|
||||
|
||||
0.2.6
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
+125
-19
@@ -12,7 +12,13 @@ var sys = require('sys'),
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
var version = '1.0.0rc';
|
||||
var version = '1.0.0rc4';
|
||||
|
||||
/**
|
||||
* stdin stream.
|
||||
*/
|
||||
|
||||
var stdin;
|
||||
|
||||
/**
|
||||
* Usage documentation.
|
||||
@@ -58,6 +64,32 @@ var jadeIndex = [
|
||||
'p Welcome to #{title}'
|
||||
].join('\n');
|
||||
|
||||
/**
|
||||
* App test template.
|
||||
*/
|
||||
|
||||
var appTest = [
|
||||
"",
|
||||
"// Run $ expresso",
|
||||
"",
|
||||
"/**",
|
||||
" * Module dependencies.",
|
||||
" */",
|
||||
"",
|
||||
"var app = require('../app');",
|
||||
"",
|
||||
"module.exports = {",
|
||||
" 'GET /': function(assert){",
|
||||
" assert.response(app,",
|
||||
" { url: '/' },",
|
||||
" { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }},",
|
||||
" function(res){",
|
||||
" assert.includes(res.body, '<title>Express</title>');",
|
||||
" });",
|
||||
" }",
|
||||
"};",
|
||||
].join('\n');
|
||||
|
||||
/**
|
||||
* App template.
|
||||
*/
|
||||
@@ -68,41 +100,45 @@ var app = [
|
||||
' * Module dependencies.',
|
||||
' */',
|
||||
'',
|
||||
'var express = require(\'express\'),',
|
||||
' connect = require(\'connect\');',
|
||||
'var express = require(\'express\');',
|
||||
'',
|
||||
'var app = express.createServer();',
|
||||
'var app = module.exports = express.createServer();',
|
||||
'',
|
||||
'// Configuration',
|
||||
'',
|
||||
'app.configure(function(){',
|
||||
' app.set(\'views\', __dirname + \'/views\');',
|
||||
' app.use(connect.bodyDecoder());',
|
||||
' app.use(connect.methodOverride());',
|
||||
' app.use(connect.compiler({ src: __dirname + \'/public\', enable: [\'less\'] }));',
|
||||
' app.use(app.router);',
|
||||
' app.use(connect.staticProvider(__dirname + \'/public\'));',
|
||||
' app.set(\'views\', __dirname + \'/views\');',
|
||||
' app.use(express.bodyDecoder());',
|
||||
' app.use(express.methodOverride());',
|
||||
' app.use(express.compiler({ src: __dirname + \'/public\', enable: [\'less\'] }));',
|
||||
' app.use(app.router);',
|
||||
' app.use(express.staticProvider(__dirname + \'/public\'));',
|
||||
'});',
|
||||
'',
|
||||
'app.configure(\'development\', function(){',
|
||||
' app.use(connect.errorHandler({ dumpExceptions: true, showStack: true })); ',
|
||||
' app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); ',
|
||||
'});',
|
||||
'',
|
||||
'app.configure(\'production\', function(){',
|
||||
' app.use(connect.errorHandler()); ',
|
||||
' app.use(express.errorHandler()); ',
|
||||
'});',
|
||||
'',
|
||||
'// Routes',
|
||||
'',
|
||||
'app.get(\'/\', function(req, res){',
|
||||
' res.render(\'index.jade\', {',
|
||||
' locals: {',
|
||||
' title: \'Express\'',
|
||||
' }',
|
||||
' });',
|
||||
' res.render(\'index.jade\', {',
|
||||
' locals: {',
|
||||
' title: \'Express\'',
|
||||
' }',
|
||||
' });',
|
||||
'});',
|
||||
'',
|
||||
'app.listen(3000);',
|
||||
'// Only listen on $ node app.js',
|
||||
'',
|
||||
'if (!module.parent) {',
|
||||
' app.listen(3000);',
|
||||
' console.log("Express server listening on port %d", app.address().port)',
|
||||
'}',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
@@ -129,6 +165,29 @@ while (args.length) {
|
||||
// Generate application
|
||||
|
||||
(function createApplication(path) {
|
||||
emptyDirectory(path, function(empty){
|
||||
if (empty) {
|
||||
createApplicationAt(path);
|
||||
} else {
|
||||
confirm('destination is not empty, continue? ', function(ok){
|
||||
if (ok) {
|
||||
stdin.destroy();
|
||||
createApplicationAt(path);
|
||||
} else {
|
||||
abort('aborting');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})(path);
|
||||
|
||||
/**
|
||||
* Create application at the given directory `path`.
|
||||
*
|
||||
* @param {String} path
|
||||
*/
|
||||
|
||||
function createApplicationAt(path) {
|
||||
mkdir(path, function(){
|
||||
mkdir(path + '/pids');
|
||||
mkdir(path + '/logs');
|
||||
@@ -141,9 +200,26 @@ while (args.length) {
|
||||
write(path + '/views/layout.jade', jadeLayout);
|
||||
write(path + '/views/index.jade', jadeIndex);
|
||||
});
|
||||
mkdir(path + '/test', function(){
|
||||
write(path + '/test/app.test.js', appTest);
|
||||
});
|
||||
write(path + '/app.js', app);
|
||||
});
|
||||
})(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given directory `path` is empty.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Function} fn
|
||||
*/
|
||||
|
||||
function emptyDirectory(path, fn) {
|
||||
fs.readdir(path, function(err, files){
|
||||
if (err && err.errno !== process.ENOENT) throw err;
|
||||
fn(!files || !files.length);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* echo str > path.
|
||||
@@ -157,6 +233,36 @@ function write(path, str) {
|
||||
sys.puts(' create : ' + path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt confirmation with the given `msg`.
|
||||
*
|
||||
* @param {String} msg
|
||||
* @param {Function} fn
|
||||
*/
|
||||
|
||||
function confirm(msg, fn) {
|
||||
prompt(msg, function(val){
|
||||
fn(/^ *y(es)?/i.test(val));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt input with the given `msg` and callback `fn`.
|
||||
*
|
||||
* @param {String} msg
|
||||
* @param {Function} fn
|
||||
*/
|
||||
|
||||
function prompt(msg, fn) {
|
||||
stdin = stdin || process.openStdin();
|
||||
sys[msg[msg.length - 1] == ' ' ? 'print' : 'puts'](msg);
|
||||
stdin.setEncoding('ascii');
|
||||
stdin.addListener('data', function(data){
|
||||
fn(data);
|
||||
stdin.removeListener('data', arguments.callee);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mkdir -p.
|
||||
*
|
||||
|
||||
+697
-335
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+8
-3
@@ -1,8 +1,11 @@
|
||||
.\" generated with Ronn/v0.6.6
|
||||
.\" http://github.com/rtomayko/ronn/
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "CONTRIB" "" "July 2010" "" ""
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBcontrib\fR
|
||||
.
|
||||
.SS "Development Dependencies"
|
||||
Express development dependencies are stored within the \fI\./support\fR directory\. To update them execute:
|
||||
.
|
||||
@@ -74,4 +77,6 @@ Do \fInot\fR alter release information such as the \fIversion\fR, or \fIHistory\
|
||||
Indents are \fI4\fR spaces\.
|
||||
.
|
||||
.IP "" 0
|
||||
|
||||
.
|
||||
.SS "Documentation"
|
||||
To contribute documentation edit the markdown files in \fI\./docs\fR, however do \fInot\fR run \fImake docs\fR, as they will be re\-built and published with each release\.
|
||||
|
||||
+36
-31
@@ -2,32 +2,7 @@
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
var done;
|
||||
$(document).scroll(function(event){
|
||||
if (!done) {
|
||||
$('#toc').animate({
|
||||
top: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#header {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
padding: 12px 0;
|
||||
text-indent: 40px;
|
||||
width: 100%;
|
||||
border-top: 1px solid rgba(0,0,0,0.7);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.7);
|
||||
background: rgba(255,255,255,0.1) url(http://www.sencha.com/favicon.ico) no-repeat 15px 50%;
|
||||
text-align: left;
|
||||
color: #fff;
|
||||
}
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
margin-bottom: 30px;
|
||||
@@ -41,8 +16,7 @@
|
||||
font: 14px/1.4 "Helvetica Neue", "Lucida Grande", "Arial";
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-moz-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: url(images/bg.jpg) 50% 0 no-repeat;
|
||||
color: #8b8b8b; }
|
||||
|
||||
@@ -65,13 +39,17 @@
|
||||
a:hover {
|
||||
opacity: 0.8; }
|
||||
|
||||
h1, h2, h3 {
|
||||
h1, h2, h3, h4 {
|
||||
margin: 45px 0 0 0;
|
||||
color: white;
|
||||
text-shadow: 1px 2px 2px rgba(0,0,0,0.6); }
|
||||
|
||||
h3 {
|
||||
font-size: 18px; }
|
||||
h4 {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 20px 10px;
|
||||
@@ -93,6 +71,12 @@
|
||||
margin: 0;
|
||||
padding: 2px 0;
|
||||
list-style: square; }
|
||||
ul li ul {
|
||||
margin: 0;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.man-name, #Express { display:none; }
|
||||
|
||||
.sect {
|
||||
margin-left: 40px; }
|
||||
@@ -123,17 +107,18 @@
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
padding-top: 110px;
|
||||
padding-top: 80px;
|
||||
width: 550px; }
|
||||
|
||||
#toc {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 0 0 15px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.2);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
#toc li {
|
||||
@@ -171,12 +156,23 @@
|
||||
-webkit-box-shadow: 1px 1px 1px rgba(0,0,0,0.4);
|
||||
-moz-box-shadow: 1px 1px 1px rgba(0,0,0,0.4); }
|
||||
</style>
|
||||
<script>
|
||||
$(function(){
|
||||
$('.section').hide();
|
||||
$('.toggle, a.section-title').toggle(function(){
|
||||
$(this).siblings('ul').fadeIn(300);
|
||||
return false;
|
||||
}, function(){
|
||||
$(this).siblings('ul').fadeOut(300);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a href='http://github.com/visionmedia/express'>
|
||||
<img alt='Fork me on GitHub' id='ribbon' src='http://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png' />
|
||||
</a>
|
||||
<div id="header"><strong>Sencha</strong> labs</div>
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<a href='http://github.com/visionmedia/express' id='logo'>Express</a>
|
||||
@@ -191,6 +187,10 @@
|
||||
<li><a href="migrate.html">1.x Migration</a></li>
|
||||
</ul>
|
||||
<div class='mp'>
|
||||
<h2 id="Express">Express</h2>
|
||||
<p class="man-name">
|
||||
<code>contrib</code>
|
||||
</p>
|
||||
<h3 id="Development-Dependencies">Development Dependencies</h3>
|
||||
|
||||
<p>Express development dependencies are stored within the <em>./support</em> directory. To
|
||||
@@ -231,6 +231,11 @@ simply execute:</p>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3 id="Documentation">Documentation</h3>
|
||||
|
||||
<p>To contribute documentation edit the markdown files in <em>./docs</em>, however
|
||||
do <em>not</em> run <em>make docs</em>, as they will be re-built and published with each release.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+5
-2
@@ -1,8 +1,11 @@
|
||||
.\" generated with Ronn/v0.6.6
|
||||
.\" http://github.com/rtomayko/ronn/
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "EXECUTABLE" "" "July 2010" "" ""
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBexecutable\fR
|
||||
.
|
||||
.SH "Synopsis"
|
||||
.
|
||||
.nf
|
||||
|
||||
+31
-31
@@ -2,32 +2,7 @@
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
var done;
|
||||
$(document).scroll(function(event){
|
||||
if (!done) {
|
||||
$('#toc').animate({
|
||||
top: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#header {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
padding: 12px 0;
|
||||
text-indent: 40px;
|
||||
width: 100%;
|
||||
border-top: 1px solid rgba(0,0,0,0.7);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.7);
|
||||
background: rgba(255,255,255,0.1) url(http://www.sencha.com/favicon.ico) no-repeat 15px 50%;
|
||||
text-align: left;
|
||||
color: #fff;
|
||||
}
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
margin-bottom: 30px;
|
||||
@@ -41,8 +16,7 @@
|
||||
font: 14px/1.4 "Helvetica Neue", "Lucida Grande", "Arial";
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-moz-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: url(images/bg.jpg) 50% 0 no-repeat;
|
||||
color: #8b8b8b; }
|
||||
|
||||
@@ -65,13 +39,17 @@
|
||||
a:hover {
|
||||
opacity: 0.8; }
|
||||
|
||||
h1, h2, h3 {
|
||||
h1, h2, h3, h4 {
|
||||
margin: 45px 0 0 0;
|
||||
color: white;
|
||||
text-shadow: 1px 2px 2px rgba(0,0,0,0.6); }
|
||||
|
||||
h3 {
|
||||
font-size: 18px; }
|
||||
h4 {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 20px 10px;
|
||||
@@ -93,6 +71,12 @@
|
||||
margin: 0;
|
||||
padding: 2px 0;
|
||||
list-style: square; }
|
||||
ul li ul {
|
||||
margin: 0;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.man-name, #Express { display:none; }
|
||||
|
||||
.sect {
|
||||
margin-left: 40px; }
|
||||
@@ -123,17 +107,18 @@
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
padding-top: 110px;
|
||||
padding-top: 80px;
|
||||
width: 550px; }
|
||||
|
||||
#toc {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 0 0 15px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.2);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
#toc li {
|
||||
@@ -171,12 +156,23 @@
|
||||
-webkit-box-shadow: 1px 1px 1px rgba(0,0,0,0.4);
|
||||
-moz-box-shadow: 1px 1px 1px rgba(0,0,0,0.4); }
|
||||
</style>
|
||||
<script>
|
||||
$(function(){
|
||||
$('.section').hide();
|
||||
$('.toggle, a.section-title').toggle(function(){
|
||||
$(this).siblings('ul').fadeIn(300);
|
||||
return false;
|
||||
}, function(){
|
||||
$(this).siblings('ul').fadeOut(300);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a href='http://github.com/visionmedia/express'>
|
||||
<img alt='Fork me on GitHub' id='ribbon' src='http://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png' />
|
||||
</a>
|
||||
<div id="header"><strong>Sencha</strong> labs</div>
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<a href='http://github.com/visionmedia/express' id='logo'>Express</a>
|
||||
@@ -191,6 +187,10 @@
|
||||
<li><a href="migrate.html">1.x Migration</a></li>
|
||||
</ul>
|
||||
<div class='mp'>
|
||||
<h2 id="Express">Express</h2>
|
||||
<p class="man-name">
|
||||
<code>executable</code>
|
||||
</p>
|
||||
<h2 id="Synopsis">Synopsis</h2>
|
||||
|
||||
<pre><code>express [-h|--help] [-v|--version] [PATH]
|
||||
|
||||
+569
-56
@@ -1,7 +1,10 @@
|
||||
.\" generated with Ronn/v0.6.6
|
||||
.\" http://github.com/rtomayko/ronn/
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "GUIDE" "" "July 2010" "" ""
|
||||
.TH "GUIDE" "" "October 2010" "" ""
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBguide\fR
|
||||
.
|
||||
.SS "Installation"
|
||||
curl:
|
||||
@@ -74,18 +77,18 @@ In the example below we only \fIdumpExceptions\fR, and respond with exception st
|
||||
.nf
|
||||
|
||||
app\.configure(function(){
|
||||
app\.use(connect\.methodOverride());
|
||||
app\.use(connect\.bodyDecoder());
|
||||
app\.use(express\.methodOverride());
|
||||
app\.use(express\.bodyDecoder());
|
||||
app\.use(app\.router);
|
||||
app\.use(connect\.staticProvider(__dirname + \'/public\'));
|
||||
app\.use(express\.staticProvider(__dirname + \'/public\'));
|
||||
});
|
||||
|
||||
app\.configure(\'development\', function(){
|
||||
app\.use(connect\.errorHandler({ dumpExceptions: true, showStack: true }));
|
||||
app\.use(express\.errorHandler({ dumpExceptions: true, showStack: true }));
|
||||
});
|
||||
|
||||
app\.configure(\'production\', function(){
|
||||
app\.use(connect\.errorHandler());
|
||||
app\.use(express\.errorHandler());
|
||||
});
|
||||
.
|
||||
.fi
|
||||
@@ -102,7 +105,7 @@ For internal and arbitrary settings Express provides the \fIset(key[, val])\fR,
|
||||
app\.configure(function(){
|
||||
app\.set(\'views\', __dirname + \'/views\');
|
||||
app\.set(\'views\');
|
||||
// => \"\.\.\. views directory \.\.\.\"
|
||||
// => "\.\.\. views directory \.\.\."
|
||||
|
||||
app\.enable(\'some feature\');
|
||||
// same as app\.set(\'some feature\', true);
|
||||
@@ -116,13 +119,13 @@ app\.configure(function(){
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
To alter the environment we can set the \fICONNECT_ENV\fR environment variable, or more specifically \fIEXPRESS_ENV\fR, for example:
|
||||
To alter the environment we can set the \fINODE_ENV\fR environment variable, for example:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ EXPRESS_ENV=production node app\.js
|
||||
$ NODE_ENV=production node app\.js
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -144,7 +147,13 @@ Express supports the following settings out of the box:
|
||||
\fIview engine\fR Default view engine name for views rendered without extensions
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIreload views\fR Reloads altered views, by default watches for \fImtime\fR changes with with a 500 millisecond interval\.
|
||||
\fIview options\fR An object specifying global view options
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIpartials\fR Root view partials directory defaulting to \fIviews\fR/partials\.
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIstream threshold\fR Bytesize indicating when a file should be streamed for \fIres\.sendfile()\fR using \fIfs\.ReadStream()\fR and \fIsys\.pump()\fR\.
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
@@ -170,7 +179,7 @@ A route is simple a string which is compiled to a \fIRegExp\fR internally\. For
|
||||
.
|
||||
.nf
|
||||
|
||||
\\/user\\/([^\\/]+)\\/?
|
||||
\e/user\e/([^\e/]+)\e/?
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -183,7 +192,7 @@ Regular expression literals may also be passed for complex uses\. Since capture
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.get(/^\\/users?(?:\\/(\\d+)(?:\\\.\\\.(\\d+))?)?/, function(req, res){
|
||||
app\.get(/^\e/users?(?:\e/(\ed+)(?:\e\.\e\.(\ed+))?)?/, function(req, res){
|
||||
res\.send(req\.params);
|
||||
});
|
||||
.
|
||||
@@ -203,9 +212,9 @@ Curl requests against the previously defined route:
|
||||
$ curl http://dev:3000/users
|
||||
[null,null]
|
||||
$ curl http://dev:3000/users/1
|
||||
[\"1\",null]
|
||||
["1",null]
|
||||
$ curl http://dev:3000/users/1\.\.15
|
||||
[\"1\",\"15\"]
|
||||
["1","15"]
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -218,30 +227,30 @@ Below are some route examples, and the associated paths that they may consume:
|
||||
.
|
||||
.nf
|
||||
|
||||
\"/user/:id\"
|
||||
"/user/:id"
|
||||
/user/12
|
||||
|
||||
\"/users/:id?\"
|
||||
"/users/:id?"
|
||||
/users/5
|
||||
/users
|
||||
|
||||
\"/files/*\"
|
||||
"/files/*"
|
||||
/files/jquery\.js
|
||||
/files/javascripts/jquery\.js
|
||||
|
||||
\"/file/*\.*\"
|
||||
"/file/*\.*"
|
||||
/files/jquery\.js
|
||||
/files/javascripts/jquery\.js
|
||||
|
||||
\"/user/:id/:operation?\"
|
||||
"/user/:id/:operation?"
|
||||
/user/1
|
||||
/user/1/edit
|
||||
|
||||
\"/products\.:format\"
|
||||
"/products\.:format"
|
||||
/products\.json
|
||||
/products\.xml
|
||||
|
||||
\"/products\.:format?\"
|
||||
"/products\.:format?"
|
||||
/products\.json
|
||||
/products\.xml
|
||||
/products
|
||||
@@ -251,13 +260,13 @@ Below are some route examples, and the associated paths that they may consume:
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Passing Route Control"
|
||||
We may pass control to the next \fImatching\fR route, by calling the \fIthird\fR argument, the \fInext()\fR function\. When a match cannot be made, control is passed back to Connect, and middleware continue to be invoked\.
|
||||
We may pass control to the next \fImatching\fR route, by calling the \fIthird\fR argument, the \fInext()\fR function\. When a match cannot be made, control is passed back to Connect, and middleware continue to be invoked\. The same is true for several routes which have the same path defined, they will simply be executed in order until one does \fInot\fR call \fInext()\fR\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.get(\'/users/:id?\', function(req, res){
|
||||
app\.get(\'/users/:id?\', function(req, res, next){
|
||||
var id = req\.params\.id;
|
||||
if (id) {
|
||||
// do something
|
||||
@@ -275,19 +284,18 @@ app\.get(\'/users\', function(req, res){
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Middleware"
|
||||
The Express \fIPlugin\fR is no more! middleware via Connect \fIhttp://github\.com/extjs/Connect\fR can be passed to \fIexpress\.createServer()\fR as you would with a regular Connect server\. For example:
|
||||
Middleware via Connect \fIhttp://github\.com/senchalabs/connect\fR can be passed to \fIexpress\.createServer()\fR as you would with a regular Connect server\. For example:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
var connect = require(\'connect\'),
|
||||
express = require(\'express\');
|
||||
var express = require(\'express\');
|
||||
|
||||
var app = express\.createServer(
|
||||
connect\.logger(),
|
||||
connect\.bodyDecoder()
|
||||
);
|
||||
express\.logger(),
|
||||
express\.bodyDecoder()
|
||||
);
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -300,7 +308,236 @@ Alternatively we can \fIuse()\fR them which is useful when adding middleware wit
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.use(connect\.logger({ format: \':method :uri\' }));
|
||||
app\.use(express\.logger({ format: \':method :uri\' }));
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Typically with connect middleware you would \fIrequire(\'connect\')\fR like so:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
var connect = require(\'connect\');
|
||||
app\.use(connect\.logger());
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
This is somewhat annoying, so express re\-exports these middleware properties, however they are \fIidentical\fR:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.use(express\.logger());
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "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\.
|
||||
.
|
||||
.P
|
||||
Typically async data retrieval might look similar to below, where we take the \fI:id\fR parameter, and attempt loading a user\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.get(\'/user/:id\', function(req, res, next){
|
||||
loadUser(req\.params\.id, function(err, user){
|
||||
if (err) return next(err);
|
||||
res\.send(\'Viewing user \' + user\.name);
|
||||
});
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
To keep things DRY and to increase readability we can apply this logic within a middleware\. As you can see below, abstracting this logic into middleware allows us to reuse it, and clean up our route at the same time\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
function loadUser(req, res, next) {
|
||||
// You would fetch your user from the db
|
||||
var user = users[req\.params\.id];
|
||||
if (user) {
|
||||
req\.user = user;
|
||||
next();
|
||||
} else {
|
||||
next(new Error(\'Failed to load user \' + req\.params\.id));
|
||||
}
|
||||
}
|
||||
|
||||
app\.get(\'/user/:id\', loadUser, function(req, res){
|
||||
res\.send(\'Viewing user \' + req\.user\.name);
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Multiple route middleware can be applied, and will be executed sequentially to apply further logic such as restricting access to a user account\. In the example below only the authenticated user may edit his/her account\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
function andRestrictToSelf(req, res, next) {
|
||||
req\.authenticatedUser\.id == req\.user\.id
|
||||
? next()
|
||||
: next(new Error(\'Unauthorized\'));
|
||||
}
|
||||
|
||||
app\.get(\'/user/:id/edit\', loadUser, andRestrictToSelf, function(req, res){
|
||||
res\.send(\'Editing user \' + req\.user\.name);
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Keeping in mind that middleware are simply functions, we can define function that \fIreturns\fR the middleware in order to create a more expressive and flexible solution as shown below\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
function andRestrictTo(role) {
|
||||
return function(req, res, next) {
|
||||
req\.authenticatedUser\.role == role
|
||||
? next()
|
||||
: next(new Error(\'Unauthorized\'));
|
||||
}
|
||||
}
|
||||
|
||||
app\.del(\'/user/:id\', loadUser, andRestrictTo(\'admin\'), function(req, res){
|
||||
res\.send(\'Deleted user \' + req\.user\.name);
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Commonly used "stacks" of middleware can be passed as an array (\fIapplied recursively\fR), which can be mixed and matched to any degree\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
var a = [middleware1, middleware2]
|
||||
, b = [middleware3, middleware4]
|
||||
, all = [a, b];
|
||||
|
||||
app\.get(\'/foo\', a, function(){});
|
||||
app\.get(\'/bar\', a, function(){});
|
||||
|
||||
app\.get(\'/\', a, middleware3, middleware4, function(){});
|
||||
app\.get(\'/\', a, b, function(){});
|
||||
app\.get(\'/\', all, function(){});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
For this example in full, view the route middleware example \fIhttp://github\.com/visionmedia/express/blob/master/examples/route\-middleware/app\.js\fR in the repository\.
|
||||
.
|
||||
.SS "HTTP Methods"
|
||||
We have seen \fIapp\.get()\fR a few times, however Express also exposes other familiar HTTP verbs in the same manor, such as \fIapp\.post()\fR, \fIapp\.del()\fR, etc\.
|
||||
.
|
||||
.P
|
||||
A common example for \fIPOST\fR usage, is when "submitting" a form\. Below we simply set our form method to "post" in our html, and control will be given to the route we have defined below it\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
<form method="post" action="/">
|
||||
<input type="text" name="user[name]" />
|
||||
<input type="text" name="user[email]" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
By default Express does not know what to do with this request body, so we should add the \fIbodyDecoder\fR middleware, which will parse \fIapplication/x\-www\-form\-urlencoded\fR request bodies and place the variables in \fIreq\.body\fR\. We can do this by "using" the middleware as shown below:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.use(express\.bodyDecoder());
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Our route below will now have access to the \fIreq\.body\.user\fR object which will contain the \fIname\fR and \fIemail\fR properties when defined\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.post(\'/\', function(req, res){
|
||||
console\.log(req\.body\.user);
|
||||
res\.redirect(\'back\');
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
When using methods such as \fIPUT\fR with a form, we can utilize a hidden input named \fI_method\fR, which can be used to alter the HTTP method\. To do so we first need the \fImethodOverride\fR middleware, which should be placed below \fIbodyDecoder\fR so that it can utilize it\'s \fIreq\.body\fR containing the form values\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.use(express\.bodyDecoder());
|
||||
app\.use(express\.methodOverride());
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
The reason that these are not always defaults, is simply because these are not required for Express to be fully functional\. Depending on the needs of your application, you may not need these at all, your methods such as \fIPUT\fR and \fIDELETE\fR can still be accessed by clients which can use them directly, although \fImethodOverride\fR provides a great solution for forms\. Below shows what the usage of \fIPUT\fR might look like:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
<form method="post" action="/">
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<input type="text" name="user[name]" />
|
||||
<input type="text" name="user[email]" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
|
||||
app\.put(\'/\', function(){
|
||||
console\.log(req\.body\.user);
|
||||
res\.redirect(\'back\');
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -336,6 +573,9 @@ app\.get(\'/500\', function(req, res){
|
||||
.P
|
||||
We can call \fIapp\.error()\fR several times as shown below\. Here we check for an instanceof \fINotFound\fR and show the 404 page, or we pass on to the next error handler\.
|
||||
.
|
||||
.P
|
||||
Note that these handlers can be defined anywhere, as they will be placed below the route handlers on \fIlisten()\fR\. This allows for definition within \fIconfigure()\fR blocks so we can handle exceptions in different ways based on the environment\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
@@ -372,13 +612,13 @@ app\.error(function(err, req, res){
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Our apps could also utilize the Connect \fIerrorHandler\fR middleware to report on exceptions\. For example if we wish to output exceptions in \"development\" mode to \fIstderr\fR we can use:
|
||||
Our apps could also utilize the Connect \fIerrorHandler\fR middleware to report on exceptions\. For example if we wish to output exceptions in "development" mode to \fIstderr\fR we can use:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.use(connect\.errorHandler({ dumpExceptions: true }));
|
||||
app\.use(express\.errorHandler({ dumpExceptions: true }));
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -391,7 +631,7 @@ Also during development we may want fancy html pages to show exceptions that are
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.use(connect\.errorHandler({ showStack: true, dumpExceptions: true }));
|
||||
app\.use(express\.errorHandler({ showStack: true, dumpExceptions: true }));
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -401,7 +641,7 @@ app\.use(connect\.errorHandler({ showStack: true, dumpExceptions: true }));
|
||||
The \fIerrorHandler\fR middleware also responds with \fIjson\fR if \fIAccept: application/json\fR is present, which is useful for developing apps that rely heavily on client\-side JavaScript\.
|
||||
.
|
||||
.SS "View Rendering"
|
||||
View filenames take the form \fINAME\fR\.\fIENGINE\fR, where \fIENGINE\fR is the name of the module that will be required\. For example the view \fIlayout\.ejs\fR will tell the view system to \fIrequire(\'ejs\')\fR, the module being loaded must (currently) export the method \fIexports\.render(str, options)\fR to comply with Express, however with will likely be extensible in the future\.
|
||||
View filenames take the form \fINAME\fR\.\fIENGINE\fR, where \fIENGINE\fR is the name of the module that will be required\. For example the view \fIlayout\.ejs\fR will tell the view system to \fIrequire(\'ejs\')\fR, the module being loaded must export the method \fIexports\.render(str, options)\fR to comply with Express, however \fIapp\.register()\fR can be used to map engines to file extensions, so that for example "foo\.html" can be rendered by jade\.
|
||||
.
|
||||
.P
|
||||
Below is an example using Haml\.js \fIhttp://github\.com/visionmedia/haml\.js\fR to render \fIindex\.html\fR, and since we do not use \fIlayout: false\fR the rendered contents of \fIindex\.html\fR will be passed as the \fIbody\fR local variable in \fIlayout\.haml\fR\.
|
||||
@@ -472,8 +712,91 @@ res\.render(\'another\-page\.ejs\');
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Express also provides the \fIview options\fR setting, which is applied each time a view is rendered, so for example if you rarely use layouts you may set:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.set(\'view options\', {
|
||||
layout: false
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Which can then be overridden within the \fBres\.render()\fR call if need be:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
res\.render(\'myview\.ejs\', { layout: true });
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
When an alternate layout is required, we may also specify a path\. For example if we have \fIview engine\fR set to \fIjade\fR and a file named \fI\./views/mylayout\.jade\fR we can simply pass:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
res\.render(\'page\', { layout: \'mylayout\' });
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Otherwise we must specify the extension:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
res\.render(\'page\', { layout: \'mylayout\.jade\' });
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
These paths may also be absolute:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
res\.render(\'page\', { layout: __dirname + \'/\.\./\.\./mylayout\.jade\' });
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
A good example of this is specifying custom \fIejs\fR opening and closing tags:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.set(\'view options\', {
|
||||
open: \'{{\',
|
||||
close: \'}}\'
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "View Partials"
|
||||
The Express view system has built\-in support for partials and collections, which are sort of \"mini\" views representing a document fragment\. For example rather than iterating in a view to display comments, we would use a partial with collection support:
|
||||
The Express view system has built\-in support for partials and collections, which are sort of "mini" views representing a document fragment\. For example rather than iterating in a view to display comments, we would use a partial with collection support:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -512,7 +835,7 @@ partial(\'comments\', comments);
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
When using the partial collection support a few \"magic\" variables are provided for free:
|
||||
When using the partial collection support a few "magic" variables are provided for free:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIfirstInCollection\fR True if this is the first object
|
||||
@@ -521,7 +844,7 @@ When using the partial collection support a few \"magic\" variables are provided
|
||||
\fIindexInCollection\fR Index of the object in the collection
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
_lastInCollection _ True if this is the last object
|
||||
\fIlastInCollection\fR True if this is the last object
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
@@ -529,16 +852,82 @@ _lastInCollection _ True if this is the last object
|
||||
Below are a few template engines commonly used with Express:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Jade \fIhttp://github\.com/visionmedia/jade\fR haml\.js successor
|
||||
Jade \fIhttp://jade\-lang\.com\fR haml\.js successor
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Haml \fIhttp://github\.com/visionmedia/haml\.js\fR indented templates
|
||||
Haml \fIhttp://github\.com/visionmedia/haml\.js\fR pythonic indented templates
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
EJS \fIhttp://github\.com/visionmedia/ejs\fR Embedded JavaScript
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "Session Support"
|
||||
Sessions support can be added by using Connect\'s \fIsession\fR middleware\. To do so we also need the \fIcookieDecoder\fR middleware place above it, which will parse and populate cookie data to \fIreq\.cookies\fR\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.use(express\.cookieDecoder());
|
||||
app\.use(express\.session());
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
By default the \fIsession\fR middleware uses the memory store bundled with Connect, however many implementations exist\. For example connect\-redis \fIhttp://github\.com/visionmedia/connect\-redis\fR supplies a Redis \fIhttp://code\.google\.com/p/redis/\fR session store and can be used as shown below:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
var RedisStore = require(\'connect\-redis\');
|
||||
app\.use(express\.cookieDecoder());
|
||||
app\.use(express\.session({ store: new RedisStore }));
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Now the \fIreq\.session\fR and \fIreq\.sessionStore\fR properties will be accessible to all routes and subsequent middleware\. Properties on \fIreq\.session\fR are automatically saved on a response, so for example if we wish to shopping cart data:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
var RedisStore = require(\'connect\-redis\');
|
||||
app\.use(express\.cookieDecoder());
|
||||
app\.use(express\.session({ store: new RedisStore }));
|
||||
app\.use(express\.bodyDecoder());
|
||||
|
||||
app\.post(\'/add\-to\-cart\', function(req, res){
|
||||
// Perhaps we posted several items with a form
|
||||
// (use the bodyDecoder() middleware for this)
|
||||
var items = req\.body\.items;
|
||||
req\.session\.items = items;
|
||||
res\.redirect(\'back\');
|
||||
});
|
||||
|
||||
app\.get(\'/add\-to\-cart\', function(req, res){
|
||||
// When redirected back to GET /add\-to\-cart
|
||||
// we could check req\.session\.items && req\.session\.items\.length
|
||||
// to print out a message
|
||||
if (req\.session\.items && req\.session\.items\.length) {
|
||||
req\.flash(\'info\', \'You have %s items in your cart\', req\.session\.items\.length);
|
||||
}
|
||||
res\.render(\'shopping\-cart\');
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
The \fIreq\.session\fR object also has methods such as \fISession#touch()\fR, \fISession#destroy()\fR, \fISession#regenerate()\fR among others to maintain and manipulate sessions\. For more information view the Connect Session \fIhttp://senchalabs\.github\.com/connect/session\.html\fR documentation\.
|
||||
.
|
||||
.SS "req\.header(key[, defaultValue])"
|
||||
Get the case\-insensitive request header \fIkey\fR, with optional \fIdefaultValue\fR:
|
||||
.
|
||||
@@ -558,7 +947,7 @@ req\.header(\'Accept\', \'*/*\');
|
||||
Check if the \fIAccept\fR header is present, and includes the given \fItype\fR\.
|
||||
.
|
||||
.P
|
||||
When the \fIAccept\fR header is not present \fItrue\fR is returned\. Otherwise the given \fItype\fR is matched by an exact match, and then subtypes\. You may pass the subtype such as \"html\" which is then converted internally to \"text/html\" using the mime lookup table\.
|
||||
When the \fIAccept\fR header is not present \fItrue\fR is returned\. Otherwise the given \fItype\fR is matched by an exact match, and then subtypes\. You may pass the subtype such as "html" which is then converted internally to "text/html" using the mime lookup table\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -598,7 +987,7 @@ Checks urlencoded body params (\fIreq\.body\fR), ex: id=12
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
To utilize urlencoded request bodies, \fIreq\.body\fR should be an object\. This can be done by using the \fIconnect\.bodyDecoder\fR middleware\.
|
||||
To utilize urlencoded request bodies, \fIreq\.body\fR should be an object\. This can be done by using the \fIexpress\.bodyDecoder\fR middleware\.
|
||||
.
|
||||
.SS "req\.flash(type[, msg])"
|
||||
Queue flash \fImsg\fR of the given \fItype\fR\.
|
||||
@@ -625,6 +1014,19 @@ req\.flash();
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Flash notification message may also utilize formatters, by default only the %s string formatter is available:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
req\.flash(\'info\', \'email delivery to _%s_ from _%s_ failed\.\', toUser, fromUser);
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "req\.isXMLHttpRequest"
|
||||
Also aliased as \fIreq\.xhr\fR, this getter checks the \fIX\-Requested\-With\fR header to see if it was issued by an \fIXMLHttpRequest\fR:
|
||||
.
|
||||
@@ -668,14 +1070,14 @@ Sets the \fIContent\-Type\fR response header to the given \fItype\fR\.
|
||||
|
||||
var filename = \'path/to/image\.png\';
|
||||
res\.contentType(filename);
|
||||
// res\.headers[\'Content\-Type\'] is now \"image/png\"
|
||||
// res\.headers[\'Content\-Type\'] is now "image/png"
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "res\.attachment([filename])"
|
||||
Sets the \fIContent\-Disposition\fR response header to \"attachment\", with optional \fIfilename\fR\.
|
||||
Sets the \fIContent\-Disposition\fR response header to "attachment", with optional \fIfilename\fR\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -701,7 +1103,26 @@ res\.sendfile(\'path/to/my\.file\');
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
This is \fInot\fR a substitution for Connect\'s \fIstaticProvider\fR middleware, it does not support HTTP caching, and does not perform any security checks\. This method is utilized by \fIres\.download()\fR to transfer static files, and allows you do to so from outside of the public directory, so suitable security checks should be applied\.
|
||||
This method accepts a callback which when given will be called on an exception, as well as when the transfer has completed\. When a callback is not given, and the file has \fBnot\fR been streamed, \fInext(err)\fR will be called on an exception\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
res\.sendfile(path, function(err, path){
|
||||
if (err) {
|
||||
// handle the error
|
||||
} else {
|
||||
console\.log(\'transferred %s\', path);
|
||||
}
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
When the filesize exceeds the \fIstream threshold\fR (defaulting to 32k), the file will be streamed using \fIfs\.ReadStream\fR and \fIsys\.pump()\fR\.
|
||||
.
|
||||
.SS "res\.download(file[, filename])"
|
||||
Transfer the given \fIfile\fR as an attachment with optional alternative \fIfilename\fR\.
|
||||
@@ -738,6 +1159,7 @@ The \fBres\.send()\fR method is a high level response utility allowing you to pa
|
||||
.
|
||||
.nf
|
||||
|
||||
res\.send(); // 204
|
||||
res\.send(new Buffer(\'wahoo\'));
|
||||
res\.send({ some: \'json\' });
|
||||
res\.send(\'<p>some html</p>\');
|
||||
@@ -770,7 +1192,51 @@ res\.redirect(\'back\');
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Express supports \"redirect mapping\", which by default provides \fIhome\fR, and \fIback\fR\. The \fIback\fR map checks the \fIReferrer\fR and \fIReferer\fR headers, while \fIhome\fR utilizes the \"home\" setting and defaults to \"/\"\.
|
||||
Express supports "redirect mapping", which by default provides \fIhome\fR, and \fIback\fR\. The \fIback\fR map checks the \fIReferrer\fR and \fIReferer\fR headers, while \fIhome\fR utilizes the "home" setting and defaults to "/"\.
|
||||
.
|
||||
.SS "res\.cookie(name, val[, options])"
|
||||
Sets the given cookie \fIname\fR to \fIval\fR, with \fIoptions\fR such as "httpOnly: true", "expires", "secure" etc\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
// "Remember me" for 15 minutes
|
||||
res\.cookie(\'rememberme\', \'yes\', { expires: new Date(Date\.now() + 900000), httpOnly: true });
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
To parse incoming \fICookie\fR headers, use the \fIcookieDecoder\fR middleware, which provides the \fIreq\.cookies\fR object:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.use(express\.cookieDecoder());
|
||||
|
||||
app\.get(\'/\', function(req, res){
|
||||
// use req\.cookies\.rememberme
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "res\.clearCookie(name)"
|
||||
Clear cookie \fIname\fR by setting "expires" far in the past\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
res\.clearCookie(\'rememberme\');
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "res\.render(view[, options[, fn]])"
|
||||
Render \fIview\fR with the given \fIoptions\fR and optional callback \fIfn\fR\. When a callback function is given a response will \fInot\fR be made automatically, however otherwise a response of \fI200\fR and \fItext/html\fR is given\.
|
||||
@@ -779,16 +1245,19 @@ Render \fIview\fR with the given \fIoptions\fR and optional callback \fIfn\fR\.
|
||||
Most engines accept one or more of the following options, both haml \fIhttp://github\.com/visionmedia/haml\.js\fR and jade \fIhttp://github\.com/visionmedia/jade\fR accept all:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIcontext|scope\fR Template evaluation context (\fIthis\fR)
|
||||
\fIscope\fR Template evaluation context (value of \fIthis\fR)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIlocals\fR Object containing local variables
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIcache\fR Cache intermediate JavaScript in memory (the default in \fIproduction\fR mode)
|
||||
\fIdebug\fR Output debugging information
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIdebug\fR Output debugging information
|
||||
\fIstatus\fR Response status code, defaults to 200
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fIheaders\fR Response headers object
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
@@ -832,7 +1301,7 @@ partial(\'movie\', movies);
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
To change the local from \fImovie\fR to \fIvideo\fR we can use the \"as\" option:
|
||||
To change the local from \fImovie\fR to \fIvideo\fR we can use the "as" option:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -860,7 +1329,7 @@ partial(\'movie\', { collection: movies, as: this });
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Another alternative is to \"explode\" the properties of the collection item into pseudo globals (local variables) by using \fIas: global\fR, which again is syntactic sugar:
|
||||
Another alternative is to "explode" the properties of the collection item into pseudo globals (local variables) by using \fIas: global\fR, which again is syntactic sugar:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -880,9 +1349,9 @@ Apply an application level setting \fIname\fR to \fIval\fR, or get the value of
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.set(\'reload views\', 200);
|
||||
app\.set(\'reload views\');
|
||||
// => 200
|
||||
app\.set(\'views\', __dirname + \'/views\');
|
||||
app\.set(\'views\');
|
||||
// => \.\.\.path\.\.\.
|
||||
.
|
||||
.fi
|
||||
.
|
||||
@@ -1073,7 +1542,7 @@ var app = express\.createServer(),
|
||||
|
||||
blog\.mounted(function(parent){
|
||||
// parent is app
|
||||
// \"this\" is blog
|
||||
// "this" is blog
|
||||
});
|
||||
|
||||
app\.use(blog);
|
||||
@@ -1082,6 +1551,50 @@ app\.use(blog);
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "app\.register(ext, exports)"
|
||||
Register the given template engine \fIexports\fR as \fIext\fR\. For example we may wish to map "\.html" files to jade:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.register(\'\.html\', require(\'jade\'));
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
This is also useful for libraries that may not match extensions correctly\. For example my haml\.js library is installed from npm as "hamljs" so instead of layout\.hamljs, we can register the engine as "\.haml":
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.register(\'\.haml\', require(\'haml\-js\'));
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
For engines that do not comply with the Express specification, we can also wrap their api this way\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
app\.register(\'\.foo\', {
|
||||
render: function(str, options) {
|
||||
// perhaps their api is
|
||||
// foo\.toHTML(str, options);
|
||||
}
|
||||
});
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SS "app\.listen([port[, host]])"
|
||||
Bind the app server to the given \fIport\fR, which defaults to 3000\. When \fIhost\fR is omitted all connections will be accepted via \fIINADDR_ANY\fR\.
|
||||
.
|
||||
|
||||
+387
-93
@@ -2,32 +2,7 @@
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
var done;
|
||||
$(document).scroll(function(event){
|
||||
if (!done) {
|
||||
$('#toc').animate({
|
||||
top: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#header {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
padding: 12px 0;
|
||||
text-indent: 40px;
|
||||
width: 100%;
|
||||
border-top: 1px solid rgba(0,0,0,0.7);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.7);
|
||||
background: rgba(255,255,255,0.1) url(http://www.sencha.com/favicon.ico) no-repeat 15px 50%;
|
||||
text-align: left;
|
||||
color: #fff;
|
||||
}
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
margin-bottom: 30px;
|
||||
@@ -41,8 +16,7 @@
|
||||
font: 14px/1.4 "Helvetica Neue", "Lucida Grande", "Arial";
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-moz-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: url(images/bg.jpg) 50% 0 no-repeat;
|
||||
color: #8b8b8b; }
|
||||
|
||||
@@ -65,13 +39,17 @@
|
||||
a:hover {
|
||||
opacity: 0.8; }
|
||||
|
||||
h1, h2, h3 {
|
||||
h1, h2, h3, h4 {
|
||||
margin: 45px 0 0 0;
|
||||
color: white;
|
||||
text-shadow: 1px 2px 2px rgba(0,0,0,0.6); }
|
||||
|
||||
h3 {
|
||||
font-size: 18px; }
|
||||
h4 {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 20px 10px;
|
||||
@@ -93,6 +71,12 @@
|
||||
margin: 0;
|
||||
padding: 2px 0;
|
||||
list-style: square; }
|
||||
ul li ul {
|
||||
margin: 0;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.man-name, #Express { display:none; }
|
||||
|
||||
.sect {
|
||||
margin-left: 40px; }
|
||||
@@ -123,17 +107,18 @@
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
padding-top: 110px;
|
||||
padding-top: 80px;
|
||||
width: 550px; }
|
||||
|
||||
#toc {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 0 0 15px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.2);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
#toc li {
|
||||
@@ -171,12 +156,23 @@
|
||||
-webkit-box-shadow: 1px 1px 1px rgba(0,0,0,0.4);
|
||||
-moz-box-shadow: 1px 1px 1px rgba(0,0,0,0.4); }
|
||||
</style>
|
||||
<script>
|
||||
$(function(){
|
||||
$('.section').hide();
|
||||
$('.toggle, a.section-title').toggle(function(){
|
||||
$(this).siblings('ul').fadeIn(300);
|
||||
return false;
|
||||
}, function(){
|
||||
$(this).siblings('ul').fadeOut(300);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a href='http://github.com/visionmedia/express'>
|
||||
<img alt='Fork me on GitHub' id='ribbon' src='http://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png' />
|
||||
</a>
|
||||
<div id="header"><strong>Sencha</strong> labs</div>
|
||||
<div id="wrapper">
|
||||
<div id="container"><ul id="toc">
|
||||
<li><a href="#Installation">Installation</a></li>
|
||||
@@ -186,34 +182,46 @@
|
||||
<li><a href="#Routing">Routing</a></li>
|
||||
<li><a href="#Passing-Route-Control">Passing Route Control</a></li>
|
||||
<li><a href="#Middleware">Middleware</a></li>
|
||||
<li><a href="#Route-Middleware">Route Middleware</a></li>
|
||||
<li><a href="#HTTP-Methods">HTTP Methods</a></li>
|
||||
<li><a href="#Error-Handling">Error Handling</a></li>
|
||||
<li><a href="#View-Rendering">View Rendering</a></li>
|
||||
<li><a href="#View-Partials">View Partials</a></li>
|
||||
<li><a href="#Template-Engines">Template Engines</a></li>
|
||||
<li><a href="#req-header-key-defaultValue-">req.header()</a></li>
|
||||
<li><a href="#req-accepts-type-">req.accepts()</a></li>
|
||||
<li><a href="#req-param-name-">req.param()</a></li>
|
||||
<li><a href="#req-flash-type-msg-">req.flash()</a></li>
|
||||
<li><a href="#req-isXMLHttpRequest">req.isXMLHttpRequest</a></li>
|
||||
<li><a href="#res-header-key-val-">res.header()</a></li>
|
||||
<li><a href="#res-contentType-type-">res.contentType()</a></li>
|
||||
<li><a href="#res-attachment-filename-">res.attachment()</a></li>
|
||||
<li><a href="#res-sendfile-path-">res.sendfile()</a></li>
|
||||
<li><a href="#res-download-file-filename-">res.download()</a></li>
|
||||
<li><a href="#res-send-body-status-headers-status-status-">res.send()</a></li>
|
||||
<li><a href="#res-redirect-url-status-">res.redirect()</a></li>
|
||||
<li><a href="#res-render-view-options-fn-">res.render()</a></li>
|
||||
<li><a href="#res-partial-view-options-">res.partial()</a></li>
|
||||
<li><a href="#app-set-name-val-">app.set()</a></li>
|
||||
<li><a href="#app-enable-name-">app.enable()</a></li>
|
||||
<li><a href="#app-disable-name-">app.disable()</a></li>
|
||||
<li><a href="#app-configure-env-function-function-">app.configure()</a></li>
|
||||
<li><a href="#app-redirect-name-val-">app.redirect()</a></li>
|
||||
<li><a href="#app-error-function-">app.error()</a></li>
|
||||
<li><a href="#app-helpers-obj-">app.helpers()</a></li>
|
||||
<li><a href="#app-dynamicHelpers-obj-">app.dynamicHelpers()</a></li>
|
||||
<li><a href="#app-mounted-fn-">app.mounted()</a></li>
|
||||
<li><a href="#app-listen-port-host-">app.listen()</a></li>
|
||||
<li><a href="#Session-Support">Session Support</a></li>
|
||||
<li><a href="#" class="toggle">+</a> <a class="section-title" href="#">Request</a><ul class="section" id="section-Request">
|
||||
<li><a href="#req-header-key-defaultValue-">header()</a></li>
|
||||
<li><a href="#req-accepts-type-">accepts()</a></li>
|
||||
<li><a href="#req-param-name-">param()</a></li>
|
||||
<li><a href="#req-flash-type-msg-">flash()</a></li>
|
||||
<li><a href="#req-isXMLHttpRequest">isXMLHttpRequest</a></li>
|
||||
</ul></li>
|
||||
<li><a href="#" class="toggle">+</a> <a class="section-title" href="#">Response</a><ul class="section" id="section-Response">
|
||||
<li><a href="#res-header-key-val-">header()</a></li>
|
||||
<li><a href="#res-contentType-type-">contentType()</a></li>
|
||||
<li><a href="#res-attachment-filename-">attachment()</a></li>
|
||||
<li><a href="#res-sendfile-path-">sendfile()</a></li>
|
||||
<li><a href="#res-download-file-filename-">download()</a></li>
|
||||
<li><a href="#res-send-body-status-headers-status-status-">send()</a></li>
|
||||
<li><a href="#res-redirect-url-status-">redirect()</a></li>
|
||||
<li><a href="#res-cookie-name-val-options-">cookie()</a></li>
|
||||
<li><a href="#res-clearCookie-name-">clearCookie()</a></li>
|
||||
<li><a href="#res-render-view-options-fn-">render()</a></li>
|
||||
<li><a href="#res-partial-view-options-">partial()</a></li>
|
||||
</ul></li>
|
||||
<li><a href="#" class="toggle">+</a> <a class="section-title" href="#">Server</a><ul class="section" id="section-Server">
|
||||
<li><a href="#app-set-name-val-">set()</a></li>
|
||||
<li><a href="#app-enable-name-">enable()</a></li>
|
||||
<li><a href="#app-disable-name-">disable()</a></li>
|
||||
<li><a href="#app-configure-env-function-function-">configure()</a></li>
|
||||
<li><a href="#app-redirect-name-val-">redirect()</a></li>
|
||||
<li><a href="#app-error-function-">error()</a></li>
|
||||
<li><a href="#app-helpers-obj-">helpers()</a></li>
|
||||
<li><a href="#app-dynamicHelpers-obj-">dynamicHelpers()</a></li>
|
||||
<li><a href="#app-mounted-fn-">mounted()</a></li>
|
||||
<li><a href="#app-register-ext-exports-">register()</a></li>
|
||||
<li><a href="#app-listen-port-host-">listen()</a></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
<a href='http://github.com/visionmedia/express' id='logo'>Express</a>
|
||||
<p id="tagline">
|
||||
@@ -227,6 +235,10 @@
|
||||
<li><a href="migrate.html">1.x Migration</a></li>
|
||||
</ul>
|
||||
<div class='mp'>
|
||||
<h2 id="Express">Express</h2>
|
||||
<p class="man-name">
|
||||
<code>guide</code>
|
||||
</p>
|
||||
<h3 id="Installation">Installation</h3>
|
||||
|
||||
<p>curl:</p>
|
||||
@@ -274,18 +286,18 @@ Note the use of <em>app.router</em>, which can (optionally) be used to mount the
|
||||
otherwise the first call to <em>app.{get,put,del,post}()</em> will mount the routes.</p>
|
||||
|
||||
<pre><code>app.configure(function(){
|
||||
app.use(connect.methodOverride());
|
||||
app.use(connect.bodyDecoder());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.bodyDecoder());
|
||||
app.use(app.router);
|
||||
app.use(connect.staticProvider(__dirname + '/public'));
|
||||
app.use(express.staticProvider(__dirname + '/public'));
|
||||
});
|
||||
|
||||
app.configure('development', function(){
|
||||
app.use(connect.errorHandler({ dumpExceptions: true, showStack: true }));
|
||||
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
|
||||
});
|
||||
|
||||
app.configure('production', function(){
|
||||
app.use(connect.errorHandler());
|
||||
app.use(express.errorHandler());
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
@@ -304,10 +316,9 @@ app.configure('production', function(){
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>To alter the environment we can set the <em>CONNECT_ENV</em> environment variable,
|
||||
or more specifically <em>EXPRESS_ENV</em>, for example:</p>
|
||||
<p>To alter the environment we can set the <em>NODE_ENV</em> environment variable, for example:</p>
|
||||
|
||||
<pre><code>$ EXPRESS_ENV=production node app.js
|
||||
<pre><code>$ NODE_ENV=production node app.js
|
||||
</code></pre>
|
||||
|
||||
<h3 id="Settings">Settings</h3>
|
||||
@@ -319,8 +330,9 @@ or more specifically <em>EXPRESS_ENV</em>, for example:</p>
|
||||
<li><em>home</em> Application base path used for <em>res.redirect()</em> and transparently handling mounted apps.</li>
|
||||
<li><em>views</em> Root views directory defaulting to <strong>CWD/views</strong></li>
|
||||
<li><em>view engine</em> Default view engine name for views rendered without extensions</li>
|
||||
<li><em>reload views</em> Reloads altered views, by default watches for <em>mtime</em> changes with
|
||||
with a 500 millisecond interval.</li>
|
||||
<li><em>view options</em> An object specifying global view options</li>
|
||||
<li><em>partials</em> Root view partials directory defaulting to <em>views</em>/partials.</li>
|
||||
<li><em>stream threshold</em> Bytesize indicating when a file should be streamed for <em>res.sendfile()</em> using <em>fs.ReadStream()</em> and <em>sys.pump()</em>.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -398,9 +410,9 @@ may consume:</p>
|
||||
|
||||
<p>We may pass control to the next <em>matching</em> route, by calling the <em>third</em> argument,
|
||||
the <em>next()</em> function. When a match cannot be made, control is passed back to Connect,
|
||||
and middleware continue to be invoked.</p>
|
||||
and middleware continue to be invoked. The same is true for several routes which have the same path defined, they will simply be executed in order until one does <em>not</em> call <em>next()</em>.</p>
|
||||
|
||||
<pre><code>app.get('/users/:id?', function(req, res){
|
||||
<pre><code>app.get('/users/:id?', function(req, res, next){
|
||||
var id = req.params.id;
|
||||
if (id) {
|
||||
// do something
|
||||
@@ -416,21 +428,154 @@ app.get('/users', function(req, res){
|
||||
|
||||
<h3 id="Middleware">Middleware</h3>
|
||||
|
||||
<p>The Express <em>Plugin</em> is no more! middleware via <a href="http://github.com/extjs/Connect">Connect</a> can be
|
||||
<p>Middleware via <a href="http://github.com/senchalabs/connect">Connect</a> can be
|
||||
passed to <em>express.createServer()</em> as you would with a regular Connect server. For example:</p>
|
||||
|
||||
<pre><code>var connect = require('connect'),
|
||||
express = require('express');
|
||||
<pre><code> var express = require('express');
|
||||
|
||||
var app = express.createServer(
|
||||
connect.logger(),
|
||||
connect.bodyDecoder()
|
||||
);
|
||||
express.logger(),
|
||||
express.bodyDecoder()
|
||||
);
|
||||
</code></pre>
|
||||
|
||||
<p>Alternatively we can <em>use()</em> them which is useful when adding middleware within <em>configure()</em> blocks:</p>
|
||||
|
||||
<pre><code>app.use(connect.logger({ format: ':method :uri' }));
|
||||
<pre><code>app.use(express.logger({ format: ':method :uri' }));
|
||||
</code></pre>
|
||||
|
||||
<p>Typically with connect middleware you would <em>require('connect')</em> like so:</p>
|
||||
|
||||
<pre><code>var connect = require('connect');
|
||||
app.use(connect.logger());
|
||||
</code></pre>
|
||||
|
||||
<p>This is somewhat annoying, so express re-exports these middleware properties, however they are <em>identical</em>:</p>
|
||||
|
||||
<pre><code>app.use(express.logger());
|
||||
</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>
|
||||
|
||||
<p>Typically async data retrieval might look similar to below, where we take the <em>:id</em> parameter, and attempt loading a user.</p>
|
||||
|
||||
<pre><code>app.get('/user/:id', function(req, res, next){
|
||||
loadUser(req.params.id, function(err, user){
|
||||
if (err) return next(err);
|
||||
res.send('Viewing user ' + user.name);
|
||||
});
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>To keep things DRY and to increase readability we can apply this logic within a middleware. As you can see below, abstracting this logic into middleware allows us to reuse it, and clean up our route at the same time.</p>
|
||||
|
||||
<pre><code>function loadUser(req, res, next) {
|
||||
// You would fetch your user from the db
|
||||
var user = users[req.params.id];
|
||||
if (user) {
|
||||
req.user = user;
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Failed to load user ' + req.params.id));
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/user/:id', loadUser, function(req, res){
|
||||
res.send('Viewing user ' + req.user.name);
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>Multiple route middleware can be applied, and will be executed sequentially to apply further logic such as restricting access to a user account. In the example below only the authenticated user may edit his/her account.</p>
|
||||
|
||||
<pre><code>function andRestrictToSelf(req, res, next) {
|
||||
req.authenticatedUser.id == req.user.id
|
||||
? next()
|
||||
: next(new Error('Unauthorized'));
|
||||
}
|
||||
|
||||
app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
|
||||
res.send('Editing user ' + req.user.name);
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>Keeping in mind that middleware are simply functions, we can define function that <em>returns</em> the middleware in order to create a more expressive and flexible solution as shown below.</p>
|
||||
|
||||
<pre><code>function andRestrictTo(role) {
|
||||
return function(req, res, next) {
|
||||
req.authenticatedUser.role == role
|
||||
? next()
|
||||
: next(new Error('Unauthorized'));
|
||||
}
|
||||
}
|
||||
|
||||
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
|
||||
res.send('Deleted user ' + req.user.name);
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>Commonly used "stacks" of middleware can be passed as an array (<em>applied recursively</em>), which can be mixed and matched to any degree.</p>
|
||||
|
||||
<pre><code>var a = [middleware1, middleware2]
|
||||
, b = [middleware3, middleware4]
|
||||
, all = [a, b];
|
||||
|
||||
app.get('/foo', a, function(){});
|
||||
app.get('/bar', a, function(){});
|
||||
|
||||
app.get('/', a, middleware3, middleware4, function(){});
|
||||
app.get('/', a, b, function(){});
|
||||
app.get('/', all, function(){});
|
||||
</code></pre>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<p> A common example for <em>POST</em> usage, is when "submitting" a form. Below we simply set our form method to "post" in our html, and control will be given to the route we have defined below it.</p>
|
||||
|
||||
<pre><code> <form method="post" action="/">
|
||||
<input type="text" name="user[name]" />
|
||||
<input type="text" name="user[email]" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
</code></pre>
|
||||
|
||||
<p>By default Express does not know what to do with this request body, so we should add the <em>bodyDecoder</em> middleware, which will parse <em>application/x-www-form-urlencoded</em> request bodies and place the variables in <em>req.body</em>. We can do this by "using" the middleware as shown below:</p>
|
||||
|
||||
<pre><code>app.use(express.bodyDecoder());
|
||||
</code></pre>
|
||||
|
||||
<p>Our route below will now have access to the <em>req.body.user</em> object which will contain the <em>name</em> and <em>email</em> properties when defined.</p>
|
||||
|
||||
<pre><code>app.post('/', function(req, res){
|
||||
console.log(req.body.user);
|
||||
res.redirect('back');
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>When using methods such as <em>PUT</em> with a form, we can utilize a hidden input named <em>_method</em>, which can be used to alter the HTTP method. To do so we first need the <em>methodOverride</em> middleware, which should be placed below <em>bodyDecoder</em> so that it can utilize it's <em>req.body</em> containing the form values.</p>
|
||||
|
||||
<pre><code>app.use(express.bodyDecoder());
|
||||
app.use(express.methodOverride());
|
||||
</code></pre>
|
||||
|
||||
<p>The reason that these are not always defaults, is simply because these are not required for Express to be fully functional. Depending on the needs of your application, you may not need these at all, your methods such as <em>PUT</em> and <em>DELETE</em> can still be accessed by clients which can use them directly, although <em>methodOverride</em> provides a great solution for forms. Below shows what the usage of <em>PUT</em> might look like:</p>
|
||||
|
||||
<pre><code><form method="post" action="/">
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<input type="text" name="user[name]" />
|
||||
<input type="text" name="user[email]" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
|
||||
app.put('/', function(){
|
||||
console.log(req.body.user);
|
||||
res.redirect('back');
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h3 id="Error-Handling">Error Handling</h3>
|
||||
@@ -460,6 +605,11 @@ app.get('/500', function(req, res){
|
||||
Here we check for an instanceof <em>NotFound</em> and show the
|
||||
404 page, or we pass on to the next error handler.</p>
|
||||
|
||||
<p>Note that these handlers can be defined anywhere, as they
|
||||
will be placed below the route handlers on <em>listen()</em>. This
|
||||
allows for definition within <em>configure()</em> blocks so we can
|
||||
handle exceptions in different ways based on the environment.</p>
|
||||
|
||||
<pre><code>app.error(function(err, req, res, next){
|
||||
if (err instanceof NotFound) {
|
||||
res.render('404.jade');
|
||||
@@ -485,13 +635,13 @@ this demo, however you can choose whatever you like</p>
|
||||
to report on exceptions. For example if we wish to output exceptions
|
||||
in "development" mode to <em>stderr</em> we can use:</p>
|
||||
|
||||
<pre><code>app.use(connect.errorHandler({ dumpExceptions: true }));
|
||||
<pre><code>app.use(express.errorHandler({ dumpExceptions: true }));
|
||||
</code></pre>
|
||||
|
||||
<p>Also during development we may want fancy html pages to show exceptions
|
||||
that are passed or thrown, so we can set <em>showStack</em> to true:</p>
|
||||
|
||||
<pre><code>app.use(connect.errorHandler({ showStack: true, dumpExceptions: true }));
|
||||
<pre><code>app.use(express.errorHandler({ showStack: true, dumpExceptions: true }));
|
||||
</code></pre>
|
||||
|
||||
<p>The <em>errorHandler</em> middleware also responds with <em>json</em> if <em>Accept: application/json</em>
|
||||
@@ -501,9 +651,8 @@ is present, which is useful for developing apps that rely heavily on client-side
|
||||
|
||||
<p>View filenames take the form <em>Express</em>.<em>ENGINE</em>, where <em>ENGINE</em> is the name
|
||||
of the module that will be required. For example the view <em>layout.ejs</em> will
|
||||
tell the view system to <em>require('ejs')</em>, the module being loaded must (currently)
|
||||
export the method <em>exports.render(str, options)</em> to comply with Express, however
|
||||
with will likely be extensible in the future.</p>
|
||||
tell the view system to <em>require('ejs')</em>, the module being loaded must export the method <em>exports.render(str, options)</em> to comply with Express, however
|
||||
<em>app.register()</em> can be used to map engines to file extensions, so that for example "foo.html" can be rendered by jade.</p>
|
||||
|
||||
<p>Below is an example using <a href="http://github.com/visionmedia/haml.js">Haml.js</a> to render <em>index.html</em>,
|
||||
and since we do not use <em>layout: false</em> the rendered contents of <em>index.html</em> will be passed as
|
||||
@@ -538,6 +687,41 @@ mix and match template engines:</p>
|
||||
<pre><code>res.render('another-page.ejs');
|
||||
</code></pre>
|
||||
|
||||
<p>Express also provides the <em>view options</em> setting, which is applied each time a view is rendered, so for example if you rarely use layouts you may set:</p>
|
||||
|
||||
<pre><code>app.set('view options', {
|
||||
layout: false
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>Which can then be overridden within the <code>res.render()</code> call if need be:</p>
|
||||
|
||||
<pre><code>res.render('myview.ejs', { layout: true });
|
||||
</code></pre>
|
||||
|
||||
<p>When an alternate layout is required, we may also specify a path. For example if we have <em>view engine</em> set to <em>jade</em> and a file named <em>./views/mylayout.jade</em> we can simply pass:</p>
|
||||
|
||||
<pre><code>res.render('page', { layout: 'mylayout' });
|
||||
</code></pre>
|
||||
|
||||
<p>Otherwise we must specify the extension:</p>
|
||||
|
||||
<pre><code>res.render('page', { layout: 'mylayout.jade' });
|
||||
</code></pre>
|
||||
|
||||
<p>These paths may also be absolute:</p>
|
||||
|
||||
<pre><code>res.render('page', { layout: __dirname + '/../../mylayout.jade' });
|
||||
</code></pre>
|
||||
|
||||
<p>A good example of this is specifying custom <em>ejs</em> opening and closing tags:</p>
|
||||
|
||||
<pre><code>app.set('view options', {
|
||||
open: '{{',
|
||||
close: '}}'
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h3 id="View-Partials">View Partials</h3>
|
||||
|
||||
<p>The Express view system has built-in support for partials and collections, which are
|
||||
@@ -565,7 +749,7 @@ for free:</p>
|
||||
<ul>
|
||||
<li><em>firstInCollection</em> True if this is the first object</li>
|
||||
<li><em>indexInCollection</em> Index of the object in the collection</li>
|
||||
<li>_lastInCollection _ True if this is the last object</li>
|
||||
<li><em>lastInCollection</em> True if this is the last object</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -574,12 +758,55 @@ for free:</p>
|
||||
<p>Below are a few template engines commonly used with Express:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://github.com/visionmedia/jade">Jade</a> haml.js successor</li>
|
||||
<li><a href="http://github.com/visionmedia/haml.js">Haml</a> indented templates</li>
|
||||
<li><a href="http://jade-lang.com">Jade</a> haml.js successor</li>
|
||||
<li><a href="http://github.com/visionmedia/haml.js">Haml</a> pythonic indented templates</li>
|
||||
<li><a href="http://github.com/visionmedia/ejs">EJS</a> Embedded JavaScript</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3 id="Session-Support">Session Support</h3>
|
||||
|
||||
<p>Sessions support can be added by using Connect's <em>session</em> middleware. To do so we also need the <em>cookieDecoder</em> middleware place above it, which will parse and populate cookie data to <em>req.cookies</em>.</p>
|
||||
|
||||
<pre><code>app.use(express.cookieDecoder());
|
||||
app.use(express.session());
|
||||
</code></pre>
|
||||
|
||||
<p>By default the <em>session</em> middleware uses the memory store bundled with Connect, however many implementations exist. For example <a href="http://github.com/visionmedia/connect-redis">connect-redis</a> supplies a <a href="http://code.google.com/p/redis/">Redis</a> session store and can be used as shown below:</p>
|
||||
|
||||
<pre><code>var RedisStore = require('connect-redis');
|
||||
app.use(express.cookieDecoder());
|
||||
app.use(express.session({ store: new RedisStore }));
|
||||
</code></pre>
|
||||
|
||||
<p>Now the <em>req.session</em> and <em>req.sessionStore</em> properties will be accessible to all routes and subsequent middleware. Properties on <em>req.session</em> are automatically saved on a response, so for example if we wish to shopping cart data:</p>
|
||||
|
||||
<pre><code>var RedisStore = require('connect-redis');
|
||||
app.use(express.cookieDecoder());
|
||||
app.use(express.session({ store: new RedisStore }));
|
||||
app.use(express.bodyDecoder());
|
||||
|
||||
app.post('/add-to-cart', function(req, res){
|
||||
// Perhaps we posted several items with a form
|
||||
// (use the bodyDecoder() middleware for this)
|
||||
var items = req.body.items;
|
||||
req.session.items = items;
|
||||
res.redirect('back');
|
||||
});
|
||||
|
||||
app.get('/add-to-cart', function(req, res){
|
||||
// When redirected back to GET /add-to-cart
|
||||
// we could check req.session.items && req.session.items.length
|
||||
// to print out a message
|
||||
if (req.session.items && req.session.items.length) {
|
||||
req.flash('info', 'You have %s items in your cart', req.session.items.length);
|
||||
}
|
||||
res.render('shopping-cart');
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>The <em>req.session</em> object also has methods such as <em>Session#touch()</em>, <em>Session#destroy()</em>, <em>Session#regenerate()</em> among others to maintain and manipulate sessions. For more information view the <a href="http://senchalabs.github.com/connect/session.html">Connect Session</a> documentation.</p>
|
||||
|
||||
<h3 id="req-header-key-defaultValue-">req.header(key[, defaultValue])</h3>
|
||||
|
||||
<p>Get the case-insensitive request header <em>key</em>, with optional <em>defaultValue</em>:</p>
|
||||
@@ -627,7 +854,7 @@ req.accepts('png');
|
||||
|
||||
<p>To utilize urlencoded request bodies, <em>req.body</em>
|
||||
should be an object. This can be done by using
|
||||
the <em>connect.bodyDecoder</em> middleware.</p>
|
||||
the <em>express.bodyDecoder</em> middleware.</p>
|
||||
|
||||
<h3 id="req-flash-type-msg-">req.flash(type[, msg])</h3>
|
||||
|
||||
@@ -648,6 +875,11 @@ req.flash();
|
||||
// => { error: ['email delivery failed'], info: [] }
|
||||
</code></pre>
|
||||
|
||||
<p>Flash notification message may also utilize formatters, by default only the %s string formatter is available:</p>
|
||||
|
||||
<pre><code>req.flash('info', 'email delivery to _%s_ from _%s_ failed.', toUser, fromUser);
|
||||
</code></pre>
|
||||
|
||||
<h3 id="req-isXMLHttpRequest">req.isXMLHttpRequest</h3>
|
||||
|
||||
<p>Also aliased as <em>req.xhr</em>, this getter checks the <em>X-Requested-With</em> header
|
||||
@@ -694,10 +926,18 @@ res.header('Content-Length');
|
||||
<pre><code>res.sendfile('path/to/my.file');
|
||||
</code></pre>
|
||||
|
||||
<p>This is <em>not</em> a substitution for Connect's <em>staticProvider</em> middleware, it does not
|
||||
support HTTP caching, and does not perform any security checks. This method is utilized
|
||||
by <em>res.download()</em> to transfer static files, and allows you do to so from outside of
|
||||
the public directory, so suitable security checks should be applied.</p>
|
||||
<p>This method accepts a callback which when given will be called on an exception, as well as when the transfer has completed. When a callback is not given, and the file has <strong>not</strong> been streamed, <em>next(err)</em> will be called on an exception.</p>
|
||||
|
||||
<pre><code>res.sendfile(path, function(err, path){
|
||||
if (err) {
|
||||
// handle the error
|
||||
} else {
|
||||
console.log('transferred %s', path);
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>When the filesize exceeds the <em>stream threshold</em> (defaulting to 32k), the file will be streamed using <em>fs.ReadStream</em> and <em>sys.pump()</em>.</p>
|
||||
|
||||
<h3 id="res-download-file-filename-">res.download(file[, filename])</h3>
|
||||
|
||||
@@ -719,7 +959,8 @@ res.sendfile(file);
|
||||
objects to respond with json, strings for html, arbitrary _Buffer_s or numbers for status
|
||||
code based responses. The following are all valid uses:</p>
|
||||
|
||||
<pre><code> res.send(new Buffer('wahoo'));
|
||||
<pre><code> res.send(); // 204
|
||||
res.send(new Buffer('wahoo'));
|
||||
res.send({ some: 'json' });
|
||||
res.send('<p>some html</p>');
|
||||
res.send('Sorry, cant find that', 404);
|
||||
@@ -746,6 +987,30 @@ res.redirect('back');
|
||||
The <em>back</em> map checks the <em>Referrer</em> and <em>Referer</em> headers, while <em>home</em> utilizes
|
||||
the "home" setting and defaults to "/".</p>
|
||||
|
||||
<h3 id="res-cookie-name-val-options-">res.cookie(name, val[, options])</h3>
|
||||
|
||||
<p>Sets the given cookie <em>name</em> to <em>val</em>, with <em>options</em> such as "httpOnly: true", "expires", "secure" etc.</p>
|
||||
|
||||
<pre><code>// "Remember me" for 15 minutes
|
||||
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
|
||||
</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>
|
||||
|
||||
<pre><code>app.use(express.cookieDecoder());
|
||||
|
||||
app.get('/', function(req, res){
|
||||
// use req.cookies.rememberme
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h3 id="res-clearCookie-name-">res.clearCookie(name)</h3>
|
||||
|
||||
<p>Clear cookie <em>name</em> by setting "expires" far in the past.</p>
|
||||
|
||||
<pre><code>res.clearCookie('rememberme');
|
||||
</code></pre>
|
||||
|
||||
<h3 id="res-render-view-options-fn-">res.render(view[, options[, fn]])</h3>
|
||||
|
||||
<p>Render <em>view</em> with the given <em>options</em> and optional callback <em>fn</em>.
|
||||
@@ -756,10 +1021,11 @@ automatically, however otherwise a response of <em>200</em> and <em>text/html</e
|
||||
both <a href="http://github.com/visionmedia/haml.js">haml</a> and <a href="http://github.com/visionmedia/jade">jade</a> accept all:</p>
|
||||
|
||||
<ul>
|
||||
<li><em>context|scope</em> Template evaluation context (<em>this</em>)</li>
|
||||
<li><em>scope</em> Template evaluation context (value of <em>this</em>)</li>
|
||||
<li><em>locals</em> Object containing local variables</li>
|
||||
<li><em>cache</em> Cache intermediate JavaScript in memory (the default in <em>production</em> mode)</li>
|
||||
<li><em>debug</em> Output debugging information</li>
|
||||
<li><em>status</em> Response status code, defaults to 200</li>
|
||||
<li><em>headers</em> Response headers object</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -816,9 +1082,9 @@ pseudo globals (local variables) by using <em>as: global</em>, which again is sy
|
||||
<p>Apply an application level setting <em>name</em> to <em>val</em>, or
|
||||
get the value of <em>name</em> when <em>val</em> is not present:</p>
|
||||
|
||||
<pre><code>app.set('reload views', 200);
|
||||
app.set('reload views');
|
||||
// => 200
|
||||
<pre><code>app.set('views', __dirname + '/views');
|
||||
app.set('views');
|
||||
// => ...path...
|
||||
</code></pre>
|
||||
|
||||
<h3 id="app-enable-name-">app.enable(name)</h3>
|
||||
@@ -941,6 +1207,34 @@ blog.mounted(function(parent){
|
||||
app.use(blog);
|
||||
</code></pre>
|
||||
|
||||
<h3 id="app-register-ext-exports-">app.register(ext, exports)</h3>
|
||||
|
||||
<p>Register the given template engine <em>exports</em>
|
||||
as <em>ext</em>. For example we may wish to map ".html"
|
||||
files to jade:</p>
|
||||
|
||||
<pre><code> app.register('.html', require('jade'));
|
||||
</code></pre>
|
||||
|
||||
<p>This is also useful for libraries that may not
|
||||
match extensions correctly. For example my haml.js
|
||||
library is installed from npm as "hamljs" so instead
|
||||
of layout.hamljs, we can register the engine as ".haml":</p>
|
||||
|
||||
<pre><code> app.register('.haml', require('haml-js'));
|
||||
</code></pre>
|
||||
|
||||
<p>For engines that do not comply with the Express
|
||||
specification, we can also wrap their api this way.</p>
|
||||
|
||||
<pre><code> app.register('.foo', {
|
||||
render: function(str, options) {
|
||||
// perhaps their api is
|
||||
// foo.toHTML(str, options);
|
||||
}
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h3 id="app-listen-port-host-">app.listen([port[, host]])</h3>
|
||||
|
||||
<p>Bind the app server to the given <em>port</em>, which defaults to 3000. When <em>host</em> is omitted all
|
||||
|
||||
+287
-31
@@ -42,18 +42,18 @@ Note the use of _app.router_, which can (optionally) be used to mount the applic
|
||||
otherwise the first call to _app.{get,put,del,post}()_ will mount the routes.
|
||||
|
||||
app.configure(function(){
|
||||
app.use(connect.methodOverride());
|
||||
app.use(connect.bodyDecoder());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.bodyDecoder());
|
||||
app.use(app.router);
|
||||
app.use(connect.staticProvider(__dirname + '/public'));
|
||||
app.use(express.staticProvider(__dirname + '/public'));
|
||||
});
|
||||
|
||||
app.configure('development', function(){
|
||||
app.use(connect.errorHandler({ dumpExceptions: true, showStack: true }));
|
||||
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
|
||||
});
|
||||
|
||||
app.configure('production', function(){
|
||||
app.use(connect.errorHandler());
|
||||
app.use(express.errorHandler());
|
||||
});
|
||||
|
||||
For internal and arbitrary settings Express provides the _set(key[, val])_, _enable(key)_, _disable(key)_ methods:
|
||||
@@ -70,10 +70,9 @@ For internal and arbitrary settings Express provides the _set(key[, val])_, _ena
|
||||
// same as app.set('some feature', false);
|
||||
});
|
||||
|
||||
To alter the environment we can set the _CONNECT_ENV_ environment variable,
|
||||
or more specifically _EXPRESS_ENV_, for example:
|
||||
To alter the environment we can set the _NODE_ENV_ environment variable, for example:
|
||||
|
||||
$ EXPRESS_ENV=production node app.js
|
||||
$ NODE_ENV=production node app.js
|
||||
|
||||
### Settings
|
||||
|
||||
@@ -83,6 +82,9 @@ Express supports the following settings out of the box:
|
||||
* _home_ Application base path used for _res.redirect()_ and transparently handling mounted apps.
|
||||
* _views_ Root views directory defaulting to **CWD/views**
|
||||
* _view engine_ Default view engine name for views rendered without extensions
|
||||
* _view options_ An object specifying global view options
|
||||
* _partials_ Root view partials directory defaulting to _views_/partials.
|
||||
* _stream threshold_ Bytesize indicating when a file should be streamed for _res.sendfile()_ using _fs.ReadStream()_ and _sys.pump()_.
|
||||
|
||||
### Routing
|
||||
|
||||
@@ -153,9 +155,9 @@ may consume:
|
||||
|
||||
We may pass control to the next _matching_ route, by calling the _third_ argument,
|
||||
the _next()_ function. When a match cannot be made, control is passed back to Connect,
|
||||
and middleware continue to be invoked.
|
||||
and middleware continue to be invoked. The same is true for several routes which have the same path defined, they will simply be executed in order until one does _not_ call _next()_.
|
||||
|
||||
app.get('/users/:id?', function(req, res){
|
||||
app.get('/users/:id?', function(req, res, next){
|
||||
var id = req.params.id;
|
||||
if (id) {
|
||||
// do something
|
||||
@@ -170,20 +172,141 @@ and middleware continue to be invoked.
|
||||
|
||||
### Middleware
|
||||
|
||||
The Express _Plugin_ is no more! middleware via [Connect](http://github.com/extjs/Connect) can be
|
||||
Middleware via [Connect](http://github.com/senchalabs/connect) can be
|
||||
passed to _express.createServer()_ as you would with a regular Connect server. For example:
|
||||
|
||||
var connect = require('connect'),
|
||||
express = require('express');
|
||||
var express = require('express');
|
||||
|
||||
var app = express.createServer(
|
||||
connect.logger(),
|
||||
connect.bodyDecoder()
|
||||
);
|
||||
express.logger(),
|
||||
express.bodyDecoder()
|
||||
);
|
||||
|
||||
Alternatively we can _use()_ them which is useful when adding middleware within _configure()_ blocks:
|
||||
|
||||
app.use(connect.logger({ format: ':method :uri' }));
|
||||
app.use(express.logger({ format: ':method :uri' }));
|
||||
|
||||
Typically with connect middleware you would _require('connect')_ like so:
|
||||
|
||||
var connect = require('connect');
|
||||
app.use(connect.logger());
|
||||
|
||||
This is somewhat annoying, so express re-exports these middleware properties, however they are _identical_:
|
||||
|
||||
app.use(express.logger());
|
||||
|
||||
### 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.
|
||||
|
||||
Typically async data retrieval might look similar to below, where we take the _:id_ parameter, and attempt loading a user.
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
loadUser(req.params.id, function(err, user){
|
||||
if (err) return next(err);
|
||||
res.send('Viewing user ' + user.name);
|
||||
});
|
||||
});
|
||||
|
||||
To keep things DRY and to increase readability we can apply this logic within a middleware. As you can see below, abstracting this logic into middleware allows us to reuse it, and clean up our route at the same time.
|
||||
|
||||
function loadUser(req, res, next) {
|
||||
// You would fetch your user from the db
|
||||
var user = users[req.params.id];
|
||||
if (user) {
|
||||
req.user = user;
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Failed to load user ' + req.params.id));
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/user/:id', loadUser, function(req, res){
|
||||
res.send('Viewing user ' + req.user.name);
|
||||
});
|
||||
|
||||
Multiple route middleware can be applied, and will be executed sequentially to apply further logic such as restricting access to a user account. In the example below only the authenticated user may edit his/her account.
|
||||
|
||||
function andRestrictToSelf(req, res, next) {
|
||||
req.authenticatedUser.id == req.user.id
|
||||
? next()
|
||||
: next(new Error('Unauthorized'));
|
||||
}
|
||||
|
||||
app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
|
||||
res.send('Editing user ' + req.user.name);
|
||||
});
|
||||
|
||||
Keeping in mind that middleware are simply functions, we can define function that _returns_ the middleware in order to create a more expressive and flexible solution as shown below.
|
||||
|
||||
function andRestrictTo(role) {
|
||||
return function(req, res, next) {
|
||||
req.authenticatedUser.role == role
|
||||
? next()
|
||||
: next(new Error('Unauthorized'));
|
||||
}
|
||||
}
|
||||
|
||||
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
|
||||
res.send('Deleted user ' + req.user.name);
|
||||
});
|
||||
|
||||
Commonly used "stacks" of middleware can be passed as an array (_applied recursively_), which can be mixed and matched to any degree.
|
||||
|
||||
var a = [middleware1, middleware2]
|
||||
, b = [middleware3, middleware4]
|
||||
, all = [a, b];
|
||||
|
||||
app.get('/foo', a, function(){});
|
||||
app.get('/bar', a, function(){});
|
||||
|
||||
app.get('/', a, middleware3, middleware4, function(){});
|
||||
app.get('/', a, b, function(){});
|
||||
app.get('/', all, function(){});
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
A common example for _POST_ usage, is when "submitting" a form. Below we simply set our form method to "post" in our html, and control will be given to the route we have defined below it.
|
||||
|
||||
<form method="post" action="/">
|
||||
<input type="text" name="user[name]" />
|
||||
<input type="text" name="user[email]" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
|
||||
By default Express does not know what to do with this request body, so we should add the _bodyDecoder_ middleware, which will parse _application/x-www-form-urlencoded_ request bodies and place the variables in _req.body_. We can do this by "using" the middleware as shown below:
|
||||
|
||||
app.use(express.bodyDecoder());
|
||||
|
||||
Our route below will now have access to the _req.body.user_ object which will contain the _name_ and _email_ properties when defined.
|
||||
|
||||
app.post('/', function(req, res){
|
||||
console.log(req.body.user);
|
||||
res.redirect('back');
|
||||
});
|
||||
|
||||
When using methods such as _PUT_ with a form, we can utilize a hidden input named _\_method_, which can be used to alter the HTTP method. To do so we first need the _methodOverride_ middleware, which should be placed below _bodyDecoder_ so that it can utilize it's _req.body_ containing the form values.
|
||||
|
||||
app.use(express.bodyDecoder());
|
||||
app.use(express.methodOverride());
|
||||
|
||||
The reason that these are not always defaults, is simply because these are not required for Express to be fully functional. Depending on the needs of your application, you may not need these at all, your methods such as _PUT_ and _DELETE_ can still be accessed by clients which can use them directly, although _methodOverride_ provides a great solution for forms. Below shows what the usage of _PUT_ might look like:
|
||||
|
||||
<form method="post" action="/">
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<input type="text" name="user[name]" />
|
||||
<input type="text" name="user[email]" />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
|
||||
app.put('/', function(){
|
||||
console.log(req.body.user);
|
||||
res.redirect('back');
|
||||
});
|
||||
|
||||
### Error Handling
|
||||
|
||||
@@ -211,6 +334,11 @@ We can call _app.error()_ several times as shown below.
|
||||
Here we check for an instanceof _NotFound_ and show the
|
||||
404 page, or we pass on to the next error handler.
|
||||
|
||||
Note that these handlers can be defined anywhere, as they
|
||||
will be placed below the route handlers on _listen()_. This
|
||||
allows for definition within _configure()_ blocks so we can
|
||||
handle exceptions in different ways based on the environment.
|
||||
|
||||
app.error(function(err, req, res, next){
|
||||
if (err instanceof NotFound) {
|
||||
res.render('404.jade');
|
||||
@@ -234,12 +362,12 @@ Our apps could also utilize the Connect _errorHandler_ middleware
|
||||
to report on exceptions. For example if we wish to output exceptions
|
||||
in "development" mode to _stderr_ we can use:
|
||||
|
||||
app.use(connect.errorHandler({ dumpExceptions: true }));
|
||||
app.use(express.errorHandler({ dumpExceptions: true }));
|
||||
|
||||
Also during development we may want fancy html pages to show exceptions
|
||||
that are passed or thrown, so we can set _showStack_ to true:
|
||||
|
||||
app.use(connect.errorHandler({ showStack: true, dumpExceptions: true }));
|
||||
app.use(express.errorHandler({ showStack: true, dumpExceptions: true }));
|
||||
|
||||
The _errorHandler_ middleware also responds with _json_ if _Accept: application/json_
|
||||
is present, which is useful for developing apps that rely heavily on client-side JavaScript.
|
||||
@@ -248,9 +376,8 @@ is present, which is useful for developing apps that rely heavily on client-side
|
||||
|
||||
View filenames take the form _NAME_._ENGINE_, where _ENGINE_ is the name
|
||||
of the module that will be required. For example the view _layout.ejs_ will
|
||||
tell the view system to _require('ejs')_, the module being loaded must (currently)
|
||||
export the method _exports.render(str, options)_ to comply with Express, however
|
||||
with will likely be extensible in the future.
|
||||
tell the view system to _require('ejs')_, the module being loaded must export the method _exports.render(str, options)_ to comply with Express, however
|
||||
_app.register()_ can be used to map engines to file extensions, so that for example "foo.html" can be rendered by jade.
|
||||
|
||||
Below is an example using [Haml.js](http://github.com/visionmedia/haml.js) to render _index.html_,
|
||||
and since we do not use _layout: false_ the rendered contents of _index.html_ will be passed as
|
||||
@@ -280,6 +407,35 @@ mix and match template engines:
|
||||
|
||||
res.render('another-page.ejs');
|
||||
|
||||
Express also provides the _view options_ setting, which is applied each time a view is rendered, so for example if you rarely use layouts you may set:
|
||||
|
||||
app.set('view options', {
|
||||
layout: false
|
||||
});
|
||||
|
||||
Which can then be overridden within the `res.render()` call if need be:
|
||||
|
||||
res.render('myview.ejs', { layout: true });
|
||||
|
||||
When an alternate layout is required, we may also specify a path. For example if we have _view engine_ set to _jade_ and a file named _./views/mylayout.jade_ we can simply pass:
|
||||
|
||||
res.render('page', { layout: 'mylayout' });
|
||||
|
||||
Otherwise we must specify the extension:
|
||||
|
||||
res.render('page', { layout: 'mylayout.jade' });
|
||||
|
||||
These paths may also be absolute:
|
||||
|
||||
res.render('page', { layout: __dirname + '/../../mylayout.jade' });
|
||||
|
||||
A good example of this is specifying custom _ejs_ opening and closing tags:
|
||||
|
||||
app.set('view options', {
|
||||
open: '{{',
|
||||
close: '}}'
|
||||
});
|
||||
|
||||
### View Partials
|
||||
|
||||
The Express view system has built-in support for partials and collections, which are
|
||||
@@ -303,16 +459,56 @@ for free:
|
||||
|
||||
* _firstInCollection_ True if this is the first object
|
||||
* _indexInCollection_ Index of the object in the collection
|
||||
* _lastInCollection _ True if this is the last object
|
||||
* _lastInCollection_ True if this is the last object
|
||||
|
||||
### Template Engines
|
||||
|
||||
Below are a few template engines commonly used with Express:
|
||||
|
||||
* [Jade](http://github.com/visionmedia/jade) haml.js successor
|
||||
* [Haml](http://github.com/visionmedia/haml.js) indented templates
|
||||
* [Jade](http://jade-lang.com) haml.js successor
|
||||
* [Haml](http://github.com/visionmedia/haml.js) pythonic indented templates
|
||||
* [EJS](http://github.com/visionmedia/ejs) Embedded JavaScript
|
||||
|
||||
### Session Support
|
||||
|
||||
Sessions support can be added by using Connect's _session_ middleware. To do so we also need the _cookieDecoder_ middleware place above it, which will parse and populate cookie data to _req.cookies_.
|
||||
|
||||
app.use(express.cookieDecoder());
|
||||
app.use(express.session());
|
||||
|
||||
By default the _session_ middleware uses the memory store bundled with Connect, however many implementations exist. For example [connect-redis](http://github.com/visionmedia/connect-redis) supplies a [Redis](http://code.google.com/p/redis/) session store and can be used as shown below:
|
||||
|
||||
var RedisStore = require('connect-redis');
|
||||
app.use(express.cookieDecoder());
|
||||
app.use(express.session({ store: new RedisStore }));
|
||||
|
||||
Now the _req.session_ and _req.sessionStore_ properties will be accessible to all routes and subsequent middleware. Properties on _req.session_ are automatically saved on a response, so for example if we wish to shopping cart data:
|
||||
|
||||
var RedisStore = require('connect-redis');
|
||||
app.use(express.cookieDecoder());
|
||||
app.use(express.session({ store: new RedisStore }));
|
||||
app.use(express.bodyDecoder());
|
||||
|
||||
app.post('/add-to-cart', function(req, res){
|
||||
// Perhaps we posted several items with a form
|
||||
// (use the bodyDecoder() middleware for this)
|
||||
var items = req.body.items;
|
||||
req.session.items = items;
|
||||
res.redirect('back');
|
||||
});
|
||||
|
||||
app.get('/add-to-cart', function(req, res){
|
||||
// When redirected back to GET /add-to-cart
|
||||
// we could check req.session.items && req.session.items.length
|
||||
// to print out a message
|
||||
if (req.session.items && req.session.items.length) {
|
||||
req.flash('info', 'You have %s items in your cart', req.session.items.length);
|
||||
}
|
||||
res.render('shopping-cart');
|
||||
});
|
||||
|
||||
The _req.session_ object also has methods such as _Session#touch()_, _Session#destroy()_, _Session#regenerate()_ among others to maintain and manipulate sessions. For more information view the [Connect Session](http://senchalabs.github.com/connect/session.html) documentation.
|
||||
|
||||
### req.header(key[, defaultValue])
|
||||
|
||||
Get the case-insensitive request header _key_, with optional _defaultValue_:
|
||||
@@ -355,7 +551,7 @@ Return the value of param _name_ when present.
|
||||
|
||||
To utilize urlencoded request bodies, _req.body_
|
||||
should be an object. This can be done by using
|
||||
the _connect.bodyDecoder_ middleware.
|
||||
the _express.bodyDecoder_ middleware.
|
||||
|
||||
### req.flash(type[, msg])
|
||||
|
||||
@@ -375,6 +571,10 @@ Queue flash _msg_ of the given _type_.
|
||||
req.flash();
|
||||
// => { error: ['email delivery failed'], info: [] }
|
||||
|
||||
Flash notification message may also utilize formatters, by default only the %s string formatter is available:
|
||||
|
||||
req.flash('info', 'email delivery to _%s_ from _%s_ failed.', toUser, fromUser);
|
||||
|
||||
### req.isXMLHttpRequest
|
||||
|
||||
Also aliased as _req.xhr_, this getter checks the _X-Requested-With_ header
|
||||
@@ -416,10 +616,17 @@ Used by `res.download()` to transfer an arbitrary file.
|
||||
|
||||
res.sendfile('path/to/my.file');
|
||||
|
||||
This is _not_ a substitution for Connect's _staticProvider_ middleware, it does not
|
||||
support HTTP caching, and does not perform any security checks. This method is utilized
|
||||
by _res.download()_ to transfer static files, and allows you do to so from outside of
|
||||
the public directory, so suitable security checks should be applied.
|
||||
This method accepts a callback which when given will be called on an exception, as well as when the transfer has completed. When a callback is not given, and the file has __not__ been streamed, _next(err)_ will be called on an exception.
|
||||
|
||||
res.sendfile(path, function(err, path){
|
||||
if (err) {
|
||||
// handle the error
|
||||
} else {
|
||||
console.log('transferred %s', path);
|
||||
}
|
||||
});
|
||||
|
||||
When the filesize exceeds the _stream threshold_ (defaulting to 32k), the file will be streamed using _fs.ReadStream_ and _sys.pump()_.
|
||||
|
||||
### res.download(file[, filename])
|
||||
|
||||
@@ -439,6 +646,7 @@ The `res.send()` method is a high level response utility allowing you to pass
|
||||
objects to respond with json, strings for html, arbitrary _Buffer_s or numbers for status
|
||||
code based responses. The following are all valid uses:
|
||||
|
||||
res.send(); // 204
|
||||
res.send(new Buffer('wahoo'));
|
||||
res.send({ some: 'json' });
|
||||
res.send('<p>some html</p>');
|
||||
@@ -464,6 +672,27 @@ Express supports "redirect mapping", which by default provides _home_, and _back
|
||||
The _back_ map checks the _Referrer_ and _Referer_ headers, while _home_ utilizes
|
||||
the "home" setting and defaults to "/".
|
||||
|
||||
### res.cookie(name, val[, options])
|
||||
|
||||
Sets the given cookie _name_ to _val_, with _options_ such as "httpOnly: true", "expires", "secure" etc.
|
||||
|
||||
// "Remember me" for 15 minutes
|
||||
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
|
||||
|
||||
To parse incoming _Cookie_ headers, use the _cookieDecoder_ middleware, which provides the _req.cookies_ object:
|
||||
|
||||
app.use(express.cookieDecoder());
|
||||
|
||||
app.get('/', function(req, res){
|
||||
// use req.cookies.rememberme
|
||||
});
|
||||
|
||||
### res.clearCookie(name)
|
||||
|
||||
Clear cookie _name_ by setting "expires" far in the past.
|
||||
|
||||
res.clearCookie('rememberme');
|
||||
|
||||
### res.render(view[, options[, fn]])
|
||||
|
||||
Render _view_ with the given _options_ and optional callback _fn_.
|
||||
@@ -473,9 +702,11 @@ automatically, however otherwise a response of _200_ and _text/html_ is given.
|
||||
Most engines accept one or more of the following options,
|
||||
both [haml](http://github.com/visionmedia/haml.js) and [jade](http://github.com/visionmedia/jade) accept all:
|
||||
|
||||
- _context|scope_ Template evaluation context (_this_)
|
||||
- _scope_ Template evaluation context (value of _this_)
|
||||
- _locals_ Object containing local variables
|
||||
- _debug_ Output debugging information
|
||||
- _status_ Response status code, defaults to 200
|
||||
- _headers_ Response headers object
|
||||
|
||||
### res.partial(view[, options])
|
||||
|
||||
@@ -632,6 +863,31 @@ Assign a callback _fn_ which is called when this _Server_ is passed to _Server#u
|
||||
|
||||
app.use(blog);
|
||||
|
||||
### app.register(ext, exports)
|
||||
|
||||
Register the given template engine _exports_
|
||||
as _ext_. For example we may wish to map ".html"
|
||||
files to jade:
|
||||
|
||||
app.register('.html', require('jade'));
|
||||
|
||||
This is also useful for libraries that may not
|
||||
match extensions correctly. For example my haml.js
|
||||
library is installed from npm as "hamljs" so instead
|
||||
of layout.hamljs, we can register the engine as ".haml":
|
||||
|
||||
app.register('.haml', require('haml-js'));
|
||||
|
||||
For engines that do not comply with the Express
|
||||
specification, we can also wrap their api this way.
|
||||
|
||||
app.register('.foo', {
|
||||
render: function(str, options) {
|
||||
// perhaps their api is
|
||||
// return foo.toHTML(str, options);
|
||||
}
|
||||
});
|
||||
|
||||
### app.listen([port[, host]])
|
||||
|
||||
Bind the app server to the given _port_, which defaults to 3000. When _host_ is omitted all
|
||||
|
||||
+39
-3
@@ -1,7 +1,10 @@
|
||||
.\" generated with Ronn/v0.6.6
|
||||
.\" http://github.com/rtomayko/ronn/
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "INDEX" "" "July 2010" "" ""
|
||||
.TH "INDEX" "" "October 2010" "" ""
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBindex\fR
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
@@ -31,6 +34,9 @@ Redirection helpers
|
||||
Dynamic view helpers
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Application level view options
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Content negotiation
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
@@ -51,6 +57,9 @@ Built on Connect \fIhttp://github\.com/senchalabs/connect\fR
|
||||
.IP "\(bu" 4
|
||||
Executable \fIexecutable\.html\fR for generating applications quickly
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
High test coverage
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "Contributors"
|
||||
@@ -98,10 +107,37 @@ View the source \fIhttp://github\.com/visionmedia/express\fR
|
||||
.SH "Apps Using Express"
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
LearnBoost \fIhttp://learnboost\.com\fR \- Online gradebook software
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Markup\.IO \fIhttp://markup\.io/\fR \- Draw on any website to share ideas
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
wtfjs \fIhttp://wtfjs\.com/\fR \- JavaScript WTFs :)
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Node Knockout \fIhttp://nodeknockout\.com/\fR \- node knockout competition site
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Node News \fIhttp://nodejs\.se/\fR \- node news aggregator
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Code Shelver \fIhttp://codeshelver\.com/\fR \- GitHub watch list app
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Clickdummy \fIhttp://clickdummy\.net/\fR \- Fast prototyping for designers
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
E\-Resistible \fIhttp://e\-resistible\.co\.uk\fR \- Online takeaway ordering app
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Storify \fIhttp://storify\.com\fR \- The future of publishing
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Ogre \fIhttp://ogre\.adc4gis\.com/\fR \- Translates spatial files into GeoJSON
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
Widescript \fIhttp://widescript\.com/\fR \- A digital reading app targeted to people who study or make use of reference material
|
||||
.
|
||||
.IP "" 0
|
||||
|
||||
|
||||
+42
-31
@@ -2,32 +2,7 @@
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
var done;
|
||||
$(document).scroll(function(event){
|
||||
if (!done) {
|
||||
$('#toc').animate({
|
||||
top: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#header {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
padding: 12px 0;
|
||||
text-indent: 40px;
|
||||
width: 100%;
|
||||
border-top: 1px solid rgba(0,0,0,0.7);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.7);
|
||||
background: rgba(255,255,255,0.1) url(http://www.sencha.com/favicon.ico) no-repeat 15px 50%;
|
||||
text-align: left;
|
||||
color: #fff;
|
||||
}
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
margin-bottom: 30px;
|
||||
@@ -41,8 +16,7 @@
|
||||
font: 14px/1.4 "Helvetica Neue", "Lucida Grande", "Arial";
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-moz-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: url(images/bg.jpg) 50% 0 no-repeat;
|
||||
color: #8b8b8b; }
|
||||
|
||||
@@ -65,13 +39,17 @@
|
||||
a:hover {
|
||||
opacity: 0.8; }
|
||||
|
||||
h1, h2, h3 {
|
||||
h1, h2, h3, h4 {
|
||||
margin: 45px 0 0 0;
|
||||
color: white;
|
||||
text-shadow: 1px 2px 2px rgba(0,0,0,0.6); }
|
||||
|
||||
h3 {
|
||||
font-size: 18px; }
|
||||
h4 {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 20px 10px;
|
||||
@@ -93,6 +71,12 @@
|
||||
margin: 0;
|
||||
padding: 2px 0;
|
||||
list-style: square; }
|
||||
ul li ul {
|
||||
margin: 0;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.man-name, #Express { display:none; }
|
||||
|
||||
.sect {
|
||||
margin-left: 40px; }
|
||||
@@ -123,17 +107,18 @@
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
padding-top: 110px;
|
||||
padding-top: 80px;
|
||||
width: 550px; }
|
||||
|
||||
#toc {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 0 0 15px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.2);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
#toc li {
|
||||
@@ -171,12 +156,23 @@
|
||||
-webkit-box-shadow: 1px 1px 1px rgba(0,0,0,0.4);
|
||||
-moz-box-shadow: 1px 1px 1px rgba(0,0,0,0.4); }
|
||||
</style>
|
||||
<script>
|
||||
$(function(){
|
||||
$('.section').hide();
|
||||
$('.toggle, a.section-title').toggle(function(){
|
||||
$(this).siblings('ul').fadeIn(300);
|
||||
return false;
|
||||
}, function(){
|
||||
$(this).siblings('ul').fadeOut(300);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a href='http://github.com/visionmedia/express'>
|
||||
<img alt='Fork me on GitHub' id='ribbon' src='http://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png' />
|
||||
</a>
|
||||
<div id="header"><strong>Sencha</strong> labs</div>
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<a href='http://github.com/visionmedia/express' id='logo'>Express</a>
|
||||
@@ -191,6 +187,10 @@
|
||||
<li><a href="migrate.html">1.x Migration</a></li>
|
||||
</ul>
|
||||
<div class='mp'>
|
||||
<h2 id="Express">Express</h2>
|
||||
<p class="man-name">
|
||||
<code>index</code>
|
||||
</p>
|
||||
<pre><code>var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
@@ -206,6 +206,7 @@ app.listen(3000);
|
||||
<li>Robust routing</li>
|
||||
<li>Redirection helpers</li>
|
||||
<li>Dynamic view helpers</li>
|
||||
<li>Application level view options</li>
|
||||
<li>Content negotiation</li>
|
||||
<li>Focus on high performance</li>
|
||||
<li>View rendering and partials support</li>
|
||||
@@ -213,6 +214,7 @@ app.listen(3000);
|
||||
<li>Session based flash notifications</li>
|
||||
<li>Built on <a href="http://github.com/senchalabs/connect">Connect</a></li>
|
||||
<li><a href="executable.html">Executable</a> for generating applications quickly</li>
|
||||
<li>High test coverage</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -244,8 +246,17 @@ app.listen(3000);
|
||||
<h2 id="Apps-Using-Express">Apps Using Express</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://learnboost.com">LearnBoost</a> - Online gradebook software</li>
|
||||
<li><a href="http://markup.io/">Markup.IO</a> - Draw on any website to share ideas</li>
|
||||
<li><a href="http://wtfjs.com/">wtfjs</a> - JavaScript WTFs :)</li>
|
||||
<li><a href="http://nodeknockout.com/">Node Knockout</a> - node knockout competition site</li>
|
||||
<li><a href="http://nodejs.se/">Node News</a> - node news aggregator</li>
|
||||
<li><a href="http://codeshelver.com/">Code Shelver</a> - GitHub watch list app</li>
|
||||
<li><a href="http://clickdummy.net/">Clickdummy</a> - Fast prototyping for designers</li>
|
||||
<li><a href="http://e-resistible.co.uk">E-Resistible</a> - Online takeaway ordering app</li>
|
||||
<li><a href="http://storify.com">Storify</a> - The future of publishing</li>
|
||||
<li><a href="http://ogre.adc4gis.com/">Ogre</a> - Translates spatial files into GeoJSON</li>
|
||||
<li><a href="http://widescript.com/">Widescript</a> - A digital reading app targeted to people who study or make use of reference material</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
+8
-1
@@ -12,6 +12,7 @@
|
||||
* Robust routing
|
||||
* Redirection helpers
|
||||
* Dynamic view helpers
|
||||
* Application level view options
|
||||
* Content negotiation
|
||||
* Focus on high performance
|
||||
* View rendering and partials support
|
||||
@@ -19,6 +20,7 @@
|
||||
* Session based flash notifications
|
||||
* Built on [Connect](http://github.com/senchalabs/connect)
|
||||
* [Executable](executable.html) for generating applications quickly
|
||||
* High test coverage
|
||||
|
||||
## Contributors
|
||||
|
||||
@@ -41,9 +43,14 @@ The following are the major contributors of Express (in no specific order).
|
||||
|
||||
## Apps Using Express
|
||||
|
||||
* [LearnBoost](http://learnboost.com) - Online gradebook software
|
||||
* [Markup.IO](http://markup.io/) - Draw on any website to share ideas
|
||||
* [wtfjs](http://wtfjs.com/) - JavaScript WTFs :)
|
||||
* [Node Knockout](http://nodeknockout.com/) - node knockout competition site
|
||||
* [Node News](http://nodejs.se/) - node news aggregator
|
||||
* [Code Shelver](http://codeshelver.com/) - GitHub watch list app
|
||||
* [Clickdummy](http://clickdummy.net/) - Fast prototyping for designers
|
||||
* [E-Resistible](e-resistible.co.uk) - Online takeaway ordering app
|
||||
* [E-Resistible](http://e-resistible.co.uk) - Online takeaway ordering app
|
||||
* [Storify](http://storify.com) - The future of publishing
|
||||
* [Ogre](http://ogre.adc4gis.com/) - Translates spatial files into GeoJSON
|
||||
* [Widescript](http://widescript.com/) - A digital reading app targeted to people who study or make use of reference material
|
||||
|
||||
+27
-31
@@ -2,32 +2,7 @@
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
var done;
|
||||
$(document).scroll(function(event){
|
||||
if (!done) {
|
||||
$('#toc').animate({
|
||||
top: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#header {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
padding: 12px 0;
|
||||
text-indent: 40px;
|
||||
width: 100%;
|
||||
border-top: 1px solid rgba(0,0,0,0.7);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.7);
|
||||
background: rgba(255,255,255,0.1) url(http://www.sencha.com/favicon.ico) no-repeat 15px 50%;
|
||||
text-align: left;
|
||||
color: #fff;
|
||||
}
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
margin-bottom: 30px;
|
||||
@@ -41,8 +16,7 @@
|
||||
font: 14px/1.4 "Helvetica Neue", "Lucida Grande", "Arial";
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-moz-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: url(images/bg.jpg) 50% 0 no-repeat;
|
||||
color: #8b8b8b; }
|
||||
|
||||
@@ -65,13 +39,17 @@
|
||||
a:hover {
|
||||
opacity: 0.8; }
|
||||
|
||||
h1, h2, h3 {
|
||||
h1, h2, h3, h4 {
|
||||
margin: 45px 0 0 0;
|
||||
color: white;
|
||||
text-shadow: 1px 2px 2px rgba(0,0,0,0.6); }
|
||||
|
||||
h3 {
|
||||
font-size: 18px; }
|
||||
h4 {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 20px 10px;
|
||||
@@ -93,6 +71,12 @@
|
||||
margin: 0;
|
||||
padding: 2px 0;
|
||||
list-style: square; }
|
||||
ul li ul {
|
||||
margin: 0;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.man-name, #Express { display:none; }
|
||||
|
||||
.sect {
|
||||
margin-left: 40px; }
|
||||
@@ -123,17 +107,18 @@
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
padding-top: 110px;
|
||||
padding-top: 80px;
|
||||
width: 550px; }
|
||||
|
||||
#toc {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 0 0 15px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.2);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
#toc li {
|
||||
@@ -171,12 +156,23 @@
|
||||
-webkit-box-shadow: 1px 1px 1px rgba(0,0,0,0.4);
|
||||
-moz-box-shadow: 1px 1px 1px rgba(0,0,0,0.4); }
|
||||
</style>
|
||||
<script>
|
||||
$(function(){
|
||||
$('.section').hide();
|
||||
$('.toggle, a.section-title').toggle(function(){
|
||||
$(this).siblings('ul').fadeIn(300);
|
||||
return false;
|
||||
}, function(){
|
||||
$(this).siblings('ul').fadeOut(300);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a href='http://github.com/visionmedia/express'>
|
||||
<img alt='Fork me on GitHub' id='ribbon' src='http://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png' />
|
||||
</a>
|
||||
<div id="header"><strong>Sencha</strong> labs</div>
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<a href='http://github.com/visionmedia/express' id='logo'>Express</a>
|
||||
|
||||
+12
-12
@@ -1,7 +1,10 @@
|
||||
.\" generated with Ronn/v0.6.6
|
||||
.\" http://github.com/rtomayko/ronn/
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "MIGRATE" "" "July 2010" "" ""
|
||||
.TH "MIGRATE" "" "October 2010" "" ""
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBmigrate\fR
|
||||
.
|
||||
.SS "Built On Connect"
|
||||
Express 1\.x is written to run on\-top of the Connect \fIhttp://extjs\.github\.com/Connect\fR middlware framework, thus the \fIPlugin\fR has been replaced by Connect\'s middleware\. By abstracting our middleware to Connect we allow additional community frameworks to develop robust, high\-level frameworks using the same technologies as Express\.
|
||||
@@ -74,12 +77,10 @@ Which we can now \fIuse()\fR within our app, or pass to the \fIexpress\.createSe
|
||||
.
|
||||
.nf
|
||||
|
||||
var connect = require(\'connect\');
|
||||
|
||||
var app = express\.createServer(
|
||||
connect\.logger(),
|
||||
connect\.methodOverride(),
|
||||
connect\.cookieDecoder()
|
||||
express\.logger(),
|
||||
express\.methodOverride(),
|
||||
express\.cookieDecoder()
|
||||
);
|
||||
.
|
||||
.fi
|
||||
@@ -93,12 +94,11 @@ or:
|
||||
.
|
||||
.nf
|
||||
|
||||
var connect = require(\'connect\');
|
||||
var app = express\.createServer();
|
||||
|
||||
app\.use(connect\.logger());
|
||||
app\.use(connect\.methodOverride());
|
||||
app\.use(connect\.cookieDecoder());
|
||||
app\.use(express\.logger());
|
||||
app\.use(express\.methodOverride());
|
||||
app\.use(express\.cookieDecoder());
|
||||
.
|
||||
.fi
|
||||
.
|
||||
|
||||
+39
-42
@@ -2,32 +2,7 @@
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
var done;
|
||||
$(document).scroll(function(event){
|
||||
if (!done) {
|
||||
$('#toc').animate({
|
||||
top: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
#header {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
padding: 12px 0;
|
||||
text-indent: 40px;
|
||||
width: 100%;
|
||||
border-top: 1px solid rgba(0,0,0,0.7);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.7);
|
||||
background: rgba(255,255,255,0.1) url(http://www.sencha.com/favicon.ico) no-repeat 15px 50%;
|
||||
text-align: left;
|
||||
color: #fff;
|
||||
}
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
margin-bottom: 30px;
|
||||
@@ -41,8 +16,7 @@
|
||||
font: 14px/1.4 "Helvetica Neue", "Lucida Grande", "Arial";
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
-webkit-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-moz-text-stroke: 1px rgba(0, 0, 0, 0.1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: url(images/bg.jpg) 50% 0 no-repeat;
|
||||
color: #8b8b8b; }
|
||||
|
||||
@@ -65,13 +39,17 @@
|
||||
a:hover {
|
||||
opacity: 0.8; }
|
||||
|
||||
h1, h2, h3 {
|
||||
h1, h2, h3, h4 {
|
||||
margin: 45px 0 0 0;
|
||||
color: white;
|
||||
text-shadow: 1px 2px 2px rgba(0,0,0,0.6); }
|
||||
|
||||
h3 {
|
||||
font-size: 18px; }
|
||||
h4 {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 20px 10px;
|
||||
@@ -93,6 +71,12 @@
|
||||
margin: 0;
|
||||
padding: 2px 0;
|
||||
list-style: square; }
|
||||
ul li ul {
|
||||
margin: 0;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.man-name, #Express { display:none; }
|
||||
|
||||
.sect {
|
||||
margin-left: 40px; }
|
||||
@@ -123,17 +107,18 @@
|
||||
|
||||
#container {
|
||||
margin: 0 auto;
|
||||
padding-top: 110px;
|
||||
padding-top: 80px;
|
||||
width: 550px; }
|
||||
|
||||
#toc {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 0 0 15px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.2);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
#toc li {
|
||||
@@ -171,12 +156,23 @@
|
||||
-webkit-box-shadow: 1px 1px 1px rgba(0,0,0,0.4);
|
||||
-moz-box-shadow: 1px 1px 1px rgba(0,0,0,0.4); }
|
||||
</style>
|
||||
<script>
|
||||
$(function(){
|
||||
$('.section').hide();
|
||||
$('.toggle, a.section-title').toggle(function(){
|
||||
$(this).siblings('ul').fadeIn(300);
|
||||
return false;
|
||||
}, function(){
|
||||
$(this).siblings('ul').fadeOut(300);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a href='http://github.com/visionmedia/express'>
|
||||
<img alt='Fork me on GitHub' id='ribbon' src='http://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png' />
|
||||
</a>
|
||||
<div id="header"><strong>Sencha</strong> labs</div>
|
||||
<div id="wrapper">
|
||||
<div id="container">
|
||||
<a href='http://github.com/visionmedia/express' id='logo'>Express</a>
|
||||
@@ -191,6 +187,10 @@
|
||||
<li><a href="migrate.html">1.x Migration</a></li>
|
||||
</ul>
|
||||
<div class='mp'>
|
||||
<h2 id="Express">Express</h2>
|
||||
<p class="man-name">
|
||||
<code>migrate</code>
|
||||
</p>
|
||||
<h3 id="Built-On-Connect">Built On Connect</h3>
|
||||
|
||||
<p>Express 1.x is written to run on-top of the <a href="http://extjs.github.com/Connect">Connect</a> middlware
|
||||
@@ -246,23 +246,20 @@ use(Cookie);
|
||||
|
||||
<p>Which we can now <em>use()</em> within our app, or pass to the <em>express.createServer()</em> method:</p>
|
||||
|
||||
<pre><code>var connect = require('connect');
|
||||
|
||||
var app = express.createServer(
|
||||
connect.logger(),
|
||||
connect.methodOverride(),
|
||||
connect.cookieDecoder()
|
||||
<pre><code>var app = express.createServer(
|
||||
express.logger(),
|
||||
express.methodOverride(),
|
||||
express.cookieDecoder()
|
||||
);
|
||||
</code></pre>
|
||||
|
||||
<p>or:</p>
|
||||
|
||||
<pre><code>var connect = require('connect');
|
||||
var app = express.createServer();
|
||||
<pre><code>var app = express.createServer();
|
||||
|
||||
app.use(connect.logger());
|
||||
app.use(connect.methodOverride());
|
||||
app.use(connect.cookieDecoder());
|
||||
app.use(express.logger());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.cookieDecoder());
|
||||
</code></pre>
|
||||
|
||||
<p>For documentation on creating Connect middleware visit <a href="http://extjs.github.com/Connect/#Middleware-Authoring">Middleware Authoring</a>.</p>
|
||||
|
||||
+6
-9
@@ -51,22 +51,19 @@ similar to below:
|
||||
|
||||
Which we can now _use()_ within our app, or pass to the _express.createServer()_ method:
|
||||
|
||||
var connect = require('connect');
|
||||
|
||||
var app = express.createServer(
|
||||
connect.logger(),
|
||||
connect.methodOverride(),
|
||||
connect.cookieDecoder()
|
||||
express.logger(),
|
||||
express.methodOverride(),
|
||||
express.cookieDecoder()
|
||||
);
|
||||
|
||||
or:
|
||||
|
||||
var connect = require('connect');
|
||||
var app = express.createServer();
|
||||
|
||||
app.use(connect.logger());
|
||||
app.use(connect.methodOverride());
|
||||
app.use(connect.cookieDecoder());
|
||||
app.use(express.logger());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.cookieDecoder());
|
||||
|
||||
For documentation on creating Connect middleware visit [Middleware Authoring](http://extjs.github.com/Connect/#Middleware-Authoring).
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express'),
|
||||
crypto = require('crypto');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(express.bodyDecoder());
|
||||
app.use(express.cookieDecoder());
|
||||
app.use(express.session());
|
||||
|
||||
// Message helper, ideally we would use req.flash()
|
||||
// however this is more light-weight for an example
|
||||
|
||||
app.dynamicHelpers({
|
||||
message: function(req){
|
||||
var err = req.session.error,
|
||||
msg = req.session.success;
|
||||
delete req.session.error;
|
||||
delete req.session.success;
|
||||
if (err) return '<p class="msg error">' + err + '</p>';
|
||||
if (msg) return '<p class="msg success">' + msg + '</p>';
|
||||
}
|
||||
});
|
||||
|
||||
// Generate a salt for the user to prevent rainbow table attacks
|
||||
|
||||
var users = {
|
||||
tj: {
|
||||
name: 'tj',
|
||||
salt: 'randomly-generated-salt',
|
||||
pass: md5('foobar' + 'randomly-generated-salt')
|
||||
}
|
||||
};
|
||||
|
||||
// Used to generate a hash of the plain-text password + salt
|
||||
|
||||
function md5(str) {
|
||||
return crypto.createHash('md5').update(str).digest('hex');
|
||||
}
|
||||
|
||||
// Authenticate using our plain-object database of doom!
|
||||
|
||||
function authenticate(name, pass, fn) {
|
||||
var user = users[name];
|
||||
// query the db for the given username
|
||||
if (!user) return fn(new Error('cannot find user'));
|
||||
// apply the same algorithm to the POSTed password, applying
|
||||
// the md5 against the pass / salt, if there is a match we
|
||||
// found the user
|
||||
if (user.pass == md5(pass + user.salt)) return fn(null, user);
|
||||
// Otherwise password is invalid
|
||||
fn(new Error('invalid password'));
|
||||
}
|
||||
|
||||
function restrict(req, res, next) {
|
||||
if (req.session.user) {
|
||||
next();
|
||||
} else {
|
||||
req.session.error = 'Access denied!';
|
||||
res.redirect('/login');
|
||||
}
|
||||
}
|
||||
|
||||
function accessLogger(req, res, next) {
|
||||
console.log('/restricted accessed by %s', req.session.user.name);
|
||||
next();
|
||||
}
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.redirect('/login');
|
||||
});
|
||||
|
||||
app.get('/restricted', restrict, accessLogger, function(req, res){
|
||||
res.send('Wahoo! restricted area');
|
||||
});
|
||||
|
||||
app.get('/logout', function(req, res){
|
||||
// destroy the user's session to log them out
|
||||
// will be re-created next request
|
||||
req.session.destroy(function(){
|
||||
res.redirect('home');
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/login', function(req, res){
|
||||
if (req.session.user) {
|
||||
req.session.success = 'Authenticated as ' + req.session.user.name
|
||||
+ ' click to <a href="/logout">logout</a>. '
|
||||
+ ' You may now access <a href="/restricted">/restricted</a>.';
|
||||
}
|
||||
res.render('login');
|
||||
});
|
||||
|
||||
app.post('/login', function(req, res){
|
||||
authenticate(req.body.username, req.body.password, function(err, user){
|
||||
if (user) {
|
||||
// Regenerate session when signing in
|
||||
// to prevent fixation
|
||||
req.session.regenerate(function(){
|
||||
// Store the user's primary key
|
||||
// in the session store to be retrieved,
|
||||
// or in this case the entire user object
|
||||
req.session.user = user;
|
||||
res.redirect('back');
|
||||
});
|
||||
} else {
|
||||
req.session.error = 'Authentication failed, please check your '
|
||||
+ ' username and password.'
|
||||
+ ' (use "tj" and "foobar")';
|
||||
res.redirect('back');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Authentication Example</title>
|
||||
<style>
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 13px Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.error {
|
||||
color: red
|
||||
}
|
||||
.success {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<h1>Login</h1>
|
||||
<%- message %>
|
||||
Try accessing <a href="/restricted">/restricted</a>.
|
||||
<form method="post" action="/login">
|
||||
<p>
|
||||
<label>Username:</label>
|
||||
<input type="text" name="username">
|
||||
</p>
|
||||
<p>
|
||||
<label>Password:</label>
|
||||
<input type="text" name="password">
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Login">
|
||||
</p>
|
||||
</form>
|
||||
@@ -1,4 +1,7 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -18,4 +21,5 @@ require('./contact');
|
||||
// be "mounted" to another at the given route.
|
||||
app.use('/blog', require('./blog'));
|
||||
|
||||
app.listen(3000);
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('./../../lib/express');
|
||||
|
||||
var app = express.createServer(
|
||||
// Place default Connect favicon above logger so it is not in
|
||||
// the logging output
|
||||
express.favicon(),
|
||||
|
||||
// Custom logger format
|
||||
express.logger({ format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time' }),
|
||||
|
||||
// Provides req.cookies
|
||||
express.cookieDecoder(),
|
||||
|
||||
// Parses x-www-form-urlencoded request bodies (and json)
|
||||
express.bodyDecoder()
|
||||
);
|
||||
|
||||
app.get('/', function(req, res){
|
||||
if (req.cookies.remember) {
|
||||
res.send('Remembered :). Click to <a href="/forget">forget</a>!.');
|
||||
} else {
|
||||
res.send('<form method="post"><p>Check to <label>'
|
||||
+ '<input type="checkbox" name="remember"/> remember me</label> '
|
||||
+ '<input type="submit" value="Submit"/>.</p></form>');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/forget', function(req, res){
|
||||
res.clearCookie('remember');
|
||||
res.redirect('back');
|
||||
});
|
||||
|
||||
app.post('/', function(req, res){
|
||||
if (req.body.remember) {
|
||||
res.cookie('remember', '1', { path: '/', expires: new Date(Date.now() + 900000), httpOnly: true });
|
||||
}
|
||||
res.redirect('back');
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('./../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.send('<ul>'
|
||||
+ '<li>Download <a href="/files/amazing.txt">amazing.txt</a>.</li>'
|
||||
+ '<li>Download <a href="/files/missing.txt">missing.txt</a>.</li>'
|
||||
+ '</ul>');
|
||||
});
|
||||
|
||||
app.get('/files/*', function(req, res){
|
||||
var file = req.params[0];
|
||||
res.download(__dirname + '/files/' + file);
|
||||
});
|
||||
|
||||
app.error(function(err, req, res, next){
|
||||
if (process.ENOENT == err.errno) {
|
||||
res.send('Cant find that file, sorry!');
|
||||
} else {
|
||||
// Not a 404
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express started on port 3000');
|
||||
@@ -0,0 +1 @@
|
||||
wow supes cool are you glad you downloaded this?
|
||||
@@ -1,4 +1,7 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('./../../lib/express'),
|
||||
connect = require('connect');
|
||||
var express = require('./../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
@@ -21,6 +20,6 @@ app.get('/next', function(req, res, next){
|
||||
// The errorHandler middleware in this case will dump exceptions to stderr
|
||||
// as well as show the stack trace in responses, currently handles text/plain,
|
||||
// text/html, and application/json responses to aid in development
|
||||
app.use('/', connect.errorHandler({ dumpExceptions: true, showStack: true }));
|
||||
app.use('/', express.errorHandler({ dumpExceptions: true, showStack: true }));
|
||||
|
||||
app.listen(3000);
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
// App with session support
|
||||
|
||||
var app = express.createServer(
|
||||
express.cookieDecoder(),
|
||||
express.session()
|
||||
);
|
||||
|
||||
// View settings
|
||||
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
// Dynamic helpers are functions which are executed
|
||||
// on each view render, unless dynamicHelpers is false.
|
||||
|
||||
// So for example we do not need to call messages() in our
|
||||
// template, "messages" will be populated with the return
|
||||
// value of this function.
|
||||
|
||||
app.dynamicHelpers({
|
||||
messages: function(req, res){
|
||||
// In the case of flash messages
|
||||
// we return a function, allowing
|
||||
// flash messages to only be flushed
|
||||
// when called, otherwise every request
|
||||
// will flush flash messages regardless.
|
||||
return function(){
|
||||
// Grab the flash messages
|
||||
var messages = req.flash();
|
||||
// We will render the "messages.ejs" partial
|
||||
return res.partial('messages', {
|
||||
// Our target object is our messages
|
||||
object: messages,
|
||||
// We want it to be named "types" in the partial
|
||||
// since they are keyed like this:
|
||||
// { info: ['foo'], error: ['bar']}
|
||||
as: 'types',
|
||||
// Pass a local named "hasMessages" so we can easily
|
||||
// check if we have any messages at all
|
||||
locals: { hasMessages: Object.keys(messages).length },
|
||||
// We dont want dynamicHelpers in this partial, as
|
||||
// it would cause infinite recursion
|
||||
dynamicHelpers: false
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.dynamicHelpers({
|
||||
// Another dynamic helper example. Since dynamic
|
||||
// helpers resolve at view rendering time, we can
|
||||
// "inject" the "page" local variable per request
|
||||
// providing us with the request url.
|
||||
page: function(req, res){
|
||||
return req.url;
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', function(req, res){
|
||||
// Not very realistic notifications but illustrates usage
|
||||
req.flash('info', 'email queued');
|
||||
req.flash('info', 'email sent');
|
||||
req.flash('error', 'delivery failed');
|
||||
res.render('index');
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -0,0 +1,3 @@
|
||||
<h1>Flash Message Example</h1>
|
||||
<p>on page <%- page %></p>
|
||||
<%- messages() %>
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<% if (hasMessages) { %>
|
||||
<div id="messages">
|
||||
<% for (var type in types) { %>
|
||||
<h2><%= type %> messages</h2>
|
||||
<ul id="<%= type %>">
|
||||
<% types[type].forEach(function(msg){ %>
|
||||
<li><%= msg %></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
var express = require('./../../lib/express'),
|
||||
connect = require('connect'),
|
||||
sys = require('sys');
|
||||
|
||||
var app = express.createServer();
|
||||
@@ -12,19 +11,19 @@ var app = express.createServer();
|
||||
// Here we use the bodyDecoder middleware
|
||||
// to parse urlencoded request bodies
|
||||
// which populates req.body
|
||||
app.use(connect.bodyDecoder());
|
||||
app.use(express.bodyDecoder());
|
||||
|
||||
// The methodOverride middleware allows us
|
||||
// to set a hidden input of _method to an arbitrary
|
||||
// HTTP method to support app.put(), app.del() etc
|
||||
app.use(connect.methodOverride());
|
||||
app.use(express.methodOverride());
|
||||
|
||||
// Required by session
|
||||
app.use(connect.cookieDecoder());
|
||||
app.use(express.cookieDecoder());
|
||||
|
||||
// Required by req.flash() for persistent
|
||||
// notifications
|
||||
app.use(connect.session());
|
||||
app.use(express.session());
|
||||
|
||||
app.get('/', function(req, res){
|
||||
// get ?name=foo
|
||||
@@ -76,4 +75,5 @@ app.put('/', function(req, res){
|
||||
res.redirect('/?name=' + req.body.name);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
// Fake items
|
||||
|
||||
var items = [
|
||||
{ name: 'foo' },
|
||||
{ name: 'bar' },
|
||||
{ name: 'baz' }
|
||||
];
|
||||
|
||||
// Routes
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.send('Visit /item/2');
|
||||
});
|
||||
|
||||
app.get('/item/:id.:format?', function(req, res, next){
|
||||
var id = req.params.id,
|
||||
format = req.params.format,
|
||||
item = items[id];
|
||||
// Ensure item exists
|
||||
if (item) {
|
||||
// Serve the format
|
||||
switch (format) {
|
||||
case 'json':
|
||||
// Detects json
|
||||
res.send(item);
|
||||
break;
|
||||
case 'xml':
|
||||
// Set contentType as xml then sends
|
||||
// the string
|
||||
var xml = ''
|
||||
+ '<items>'
|
||||
+ '<item>' + item.name + '</item>'
|
||||
+ '</items>';
|
||||
res.contentType('.xml');
|
||||
res.send(xml);
|
||||
break;
|
||||
case 'html':
|
||||
default:
|
||||
// By default send some hmtl
|
||||
res.send('<h1>' + item.name + '</h1>');
|
||||
}
|
||||
} else {
|
||||
// We could simply pass route control and potentially 404
|
||||
// by calling next(), or pass an exception like below.
|
||||
next(new Error('Item ' + id + ' does not exist'));
|
||||
}
|
||||
});
|
||||
|
||||
// Middleware
|
||||
|
||||
app.use(express.errorHandler({ showStack: true }));
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -1,10 +1,12 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('./../../lib/express'),
|
||||
connect = require('connect'),
|
||||
http = require('http');
|
||||
|
||||
var app = express.createServer();
|
||||
@@ -85,7 +87,7 @@ app.get('/repos/*', function(req, res, next){
|
||||
(function fetchData(name){
|
||||
// We have a user name
|
||||
if (name) {
|
||||
console.log('... fetching %s', name);
|
||||
console.log('... fetching \x1b[33m%s\x1b[0m', name);
|
||||
request('/repos/show/' + name, function(err, user){
|
||||
if (err) {
|
||||
next(err)
|
||||
@@ -110,7 +112,8 @@ app.get('/repos/*', function(req, res, next){
|
||||
});
|
||||
|
||||
// Serve statics from ./public
|
||||
app.use(connect.staticProvider(__dirname + '/public'));
|
||||
app.use(express.staticProvider(__dirname + '/public'));
|
||||
|
||||
// Listen on port 3000
|
||||
app.listen(3000);
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -1,10 +1,12 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('./../../lib/express'),
|
||||
connect = require('connect');
|
||||
var express = require('./../../lib/express');
|
||||
|
||||
// Path to our public directory
|
||||
|
||||
@@ -14,8 +16,8 @@ var pub = __dirname + '/public';
|
||||
// and then serve with connect's staticProvider
|
||||
|
||||
var app = express.createServer(
|
||||
connect.compiler({ src: pub, enable: ['sass'] }),
|
||||
connect.staticProvider(pub)
|
||||
express.compiler({ src: pub, enable: ['sass'] }),
|
||||
express.staticProvider(pub)
|
||||
);
|
||||
|
||||
// Optional since express defaults to CWD/views
|
||||
@@ -41,4 +43,5 @@ app.get('/', function(req, res){
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -1,28 +1,30 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('./../../lib/express'),
|
||||
form = require('./../../support/connect-form'),
|
||||
connect = require('connect'),
|
||||
var express = require('../../lib/express'),
|
||||
form = require('connect-form'),
|
||||
sys = require('sys');
|
||||
|
||||
var app = express.createServer(
|
||||
// connect-form (http://github.com/visionmedia/connect-form)
|
||||
// middleware uses the formidable middleware to parse urlencoded
|
||||
// and multipart form data
|
||||
form()
|
||||
form({ keepExtensions: true })
|
||||
);
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.send('<form method="post" enctype="form-data/multipart">'
|
||||
res.send('<form method="post" enctype="multipart/form-data">'
|
||||
+ '<p>Image: <input type="file" name="image" /></p>'
|
||||
+ '<p><input type="submit" value="Upload" /></p>'
|
||||
+ '</form>');
|
||||
});
|
||||
|
||||
app.post('/', function(req, res, params, next){
|
||||
app.post('/', function(req, res, next){
|
||||
|
||||
// connect-form adds the req.form object
|
||||
// we can (optionally) define onComplete, passing
|
||||
@@ -31,7 +33,9 @@ app.post('/', function(req, res, params, next){
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
sys.puts('\nuploaded ' + files.image.filename);
|
||||
console.log('\nuploaded %s to %s',
|
||||
files.image.filename,
|
||||
files.image.path);
|
||||
res.redirect('back');
|
||||
}
|
||||
});
|
||||
@@ -44,4 +48,5 @@ app.post('/', function(req, res, params, next){
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
require('./mvc').boot(app);
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
module.exports = {
|
||||
|
||||
// /
|
||||
|
||||
index: function(req, res){
|
||||
res.render();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
// Fake user database for example
|
||||
|
||||
var users = [
|
||||
{ id: 0, name: 'TJ', email: 'tj@vision-media.ca' },
|
||||
{ id: 1, name: 'Simon', email: 'simon@vision-media.ca' }
|
||||
];
|
||||
|
||||
function get(id, fn) {
|
||||
if (users[id]) {
|
||||
fn(null, users[id]);
|
||||
} else {
|
||||
fn(new Error('User ' + id + ' does not exist'));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
// /users
|
||||
|
||||
index: function(req, res){
|
||||
res.render(users);
|
||||
},
|
||||
|
||||
// /users/:id
|
||||
|
||||
show: function(req, res, next){
|
||||
get(req.params.id, function(err, user){
|
||||
if (err) return next(err);
|
||||
res.render(user);
|
||||
});
|
||||
},
|
||||
|
||||
// /users/:id/edit
|
||||
|
||||
edit: function(req, res, next){
|
||||
get(req.params.id, function(err, user){
|
||||
if (err) return next(err);
|
||||
res.render(user);
|
||||
});
|
||||
},
|
||||
|
||||
// PUT /users/:id
|
||||
|
||||
update: function(req, res, next){
|
||||
var id = req.params.id;
|
||||
get(id, function(err){
|
||||
if (err) return next(err);
|
||||
var user = users[id] = req.body.user;
|
||||
user.id = id;
|
||||
req.flash('info', 'Successfully updated _' + user.name + '_.');
|
||||
res.redirect('back');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var fs = require('fs'),
|
||||
express = require('../../lib/express');
|
||||
|
||||
exports.boot = function(app){
|
||||
bootApplication(app);
|
||||
bootControllers(app);
|
||||
};
|
||||
|
||||
// App settings and middleware
|
||||
|
||||
function bootApplication(app) {
|
||||
app.use(express.logger({ format: ':method :url :status' }));
|
||||
app.use(express.bodyDecoder());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.cookieDecoder());
|
||||
app.use(express.session());
|
||||
app.use(app.router);
|
||||
app.use(express.staticProvider(__dirname + '/public'));
|
||||
|
||||
// Example 500 page
|
||||
app.error(function(err, req, res){
|
||||
console.dir(err)
|
||||
res.render('500');
|
||||
});
|
||||
|
||||
// Example 404 page via simple Connect middleware
|
||||
app.use(function(req, res){
|
||||
res.render('404');
|
||||
});
|
||||
|
||||
// Setup ejs views as default, with .html as the extension
|
||||
app.set('views', __dirname + '/views');
|
||||
app.register('.html', require('ejs'));
|
||||
app.set('view engine', 'html');
|
||||
|
||||
// Some dynamic view helpers
|
||||
app.dynamicHelpers({
|
||||
request: function(req){
|
||||
return req;
|
||||
},
|
||||
|
||||
hasMessages: function(req){
|
||||
return Object.keys(req.session.flash || {}).length;
|
||||
},
|
||||
|
||||
messages: function(req){
|
||||
return function(){
|
||||
var msgs = req.flash();
|
||||
return Object.keys(msgs).reduce(function(arr, type){
|
||||
return arr.concat(msgs[type]);
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Bootstrap controllers
|
||||
|
||||
function bootControllers(app) {
|
||||
fs.readdir(__dirname + '/controllers', function(err, files){
|
||||
if (err) throw err;
|
||||
files.forEach(function(file){
|
||||
bootController(app, file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Example (simplistic) controller support
|
||||
|
||||
function bootController(app, file) {
|
||||
var name = file.replace('.js', ''),
|
||||
actions = require('./controllers/' + name),
|
||||
plural = name + 's', // realistically we would use an inflection lib
|
||||
prefix = '/' + plural;
|
||||
|
||||
// Special case for "app"
|
||||
if (name == 'app') {
|
||||
prefix = '/';
|
||||
}
|
||||
|
||||
Object.keys(actions).map(function(action){
|
||||
var fn = controllerAction(name, plural, action, actions[action]);
|
||||
switch(action) {
|
||||
case 'index':
|
||||
app.get(prefix, fn);
|
||||
break;
|
||||
case 'show':
|
||||
app.get(prefix + '/:id.:format?', fn);
|
||||
break;
|
||||
case 'add':
|
||||
app.get(prefix + '/:id/add', fn);
|
||||
break;
|
||||
case 'create':
|
||||
app.post(prefix + '/:id', fn);
|
||||
break;
|
||||
case 'edit':
|
||||
app.get(prefix + '/:id/edit', fn);
|
||||
break;
|
||||
case 'update':
|
||||
app.put(prefix + '/:id', fn);
|
||||
break;
|
||||
case 'destroy':
|
||||
app.del(prefix + '/:id', fn);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Proxy res.render() to add some magic
|
||||
|
||||
function controllerAction(name, plural, action, fn) {
|
||||
return function(req, res, next){
|
||||
var render = res.render,
|
||||
format = req.params.format,
|
||||
path = __dirname + '/views/' + name + '/' + action + '.html';
|
||||
res.render = function(obj, options, fn){
|
||||
res.render = render;
|
||||
// Template path
|
||||
if (typeof obj === 'string') {
|
||||
return res.render(obj, options, fn);
|
||||
}
|
||||
|
||||
// Format support
|
||||
if (action == 'show' && format) {
|
||||
if (format === 'json') {
|
||||
return res.send(obj);
|
||||
} else {
|
||||
throw new Error('unsupported format "' + format + '"');
|
||||
}
|
||||
}
|
||||
|
||||
// Render template
|
||||
res.render = render;
|
||||
options = options || {};
|
||||
options.locals = options.locals || {};
|
||||
// Expose obj as the "users" or "user" local
|
||||
if (action == 'index') {
|
||||
options.locals[plural] = obj;
|
||||
} else {
|
||||
options.locals[name] = obj;
|
||||
}
|
||||
return res.render(path, options, fn);
|
||||
};
|
||||
fn.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
body {
|
||||
padding: 30px;
|
||||
font: 12px/1.4 "Helvetica Nueue", "Lucida Grande", Arial, sans-serif;
|
||||
}
|
||||
a {
|
||||
color: #00aaff;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#messages {
|
||||
width: 95%;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
border: 1px solid #00DD00;
|
||||
-webkit-box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
#messages li {
|
||||
list-style: none;
|
||||
}
|
||||
#messages em {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<h1>Cannot find <%= request.url %></h1>
|
||||
<p>Sorry we failed to locate this page or resource.</p>
|
||||
@@ -0,0 +1,2 @@
|
||||
<h1>Internal Server Error</h1>
|
||||
<p>Sorry an error has occurred, please try refreshing the page or navigate back to <a href="/">home</a>.</p>
|
||||
@@ -0,0 +1,10 @@
|
||||
<h1>Application Index</h1>
|
||||
<p>Try visiting one of the following urls:</p>
|
||||
<ul>
|
||||
<li><a href="/users">/users</a></li>
|
||||
<li><a href="/users/1">/users/1</a></li>
|
||||
<li><a href="/users/1.json">/users/1.json</a></li>
|
||||
<li><a href="/users/1/edit">/users/1/edit</a></li>
|
||||
<li><a href="/accounts">/accounts (404, non existent)</a></li>
|
||||
<li><a href="/users/1.foo">/users/1.foo (500, unsupported format)</a></li>
|
||||
</ul>
|
||||
@@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - MVC Example</title>
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
<% if (hasMessages) { %>
|
||||
<ul id="messages">
|
||||
<% messages().forEach(function(msg){ %>
|
||||
<li><%- msg %></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% } %>
|
||||
@@ -0,0 +1,8 @@
|
||||
<h1>Editing user <%= user.name %></h1>
|
||||
<%- partial('messages') %>
|
||||
<form method="post" action="/users/<%= user.id %>">
|
||||
<input type="hidden" name="_method" value="put" />
|
||||
<p>Name: <input type="text" name="user[name]", value="<%= user.name %>" /></p>
|
||||
<p>Email: <input type="text" name="user[email]", value="<%= user.email %>" /></p>
|
||||
<p><input type="submit" value="Update" /></p>
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
<h1>Users</h1>
|
||||
<%- partial('messages') %>
|
||||
<ul>
|
||||
<% users.forEach(function(user){ %>
|
||||
<li><a href="/users/<%= user.id %>"><%= user.name %></a></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
@@ -0,0 +1,7 @@
|
||||
<h1>Viewing user #<%= user.id %></h1>
|
||||
<%- partial('messages') %>
|
||||
<ul>
|
||||
<li><%- user.name %></li>
|
||||
<li><%- user.email %></li>
|
||||
<li><a href="<%= user.id %>/edit">Edit</a> this user</li>
|
||||
</ul>
|
||||
+64
-19
@@ -1,4 +1,7 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
@@ -8,37 +11,63 @@ var express = require('./../../lib/express');
|
||||
var app = express.createServer(),
|
||||
sys = require('sys');
|
||||
|
||||
// Serve default connect favicon
|
||||
app.use(express.favicon());
|
||||
|
||||
// Logger is placed below favicon, so favicon.ico
|
||||
// requests will not be logged
|
||||
app.use(express.logger({ format: '":method :url" :status' }));
|
||||
|
||||
// "app.router" positions our routes
|
||||
// specifically above the middleware
|
||||
// assigned below
|
||||
|
||||
app.use(app.router);
|
||||
|
||||
// When no more middleware require execution, aka
|
||||
// our router is finished and did not respond, we
|
||||
// can assume that it is "not found". Instead of
|
||||
// letting Connect deal with this, we define our
|
||||
// custom middleware here to simply pass a NotFound
|
||||
// exception
|
||||
|
||||
app.use(function(req, res, next){
|
||||
next(new NotFound(req.url));
|
||||
});
|
||||
|
||||
app.set('views', __dirname + '/views');
|
||||
|
||||
// Provide our app with the notion of NotFound exceptions
|
||||
|
||||
function NotFound(msg){
|
||||
function NotFound(path){
|
||||
this.name = 'NotFound';
|
||||
Error.call(this, msg);
|
||||
if (path) {
|
||||
Error.call(this, 'Cannot find ' + path);
|
||||
this.path = path;
|
||||
} else {
|
||||
Error.call(this, 'Not Found');
|
||||
}
|
||||
Error.captureStackTrace(this, arguments.callee);
|
||||
}
|
||||
|
||||
sys.inherits(NotFound, Error);
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('index.jade');
|
||||
});
|
||||
|
||||
app.get('/404', function(req, res){
|
||||
throw new NotFound;
|
||||
});
|
||||
|
||||
app.get('/500', function(req, res){
|
||||
throw new Error('keyboard cat!');
|
||||
});
|
||||
|
||||
// We can call app.error() several times as shown below.
|
||||
// Here we check for an instanceof NotFound and show the
|
||||
// 404 page, or we pass on to the next error handler.
|
||||
|
||||
// These handlers could potentially be defined within
|
||||
// configure() blocks to provide introspection when
|
||||
// in the development environment.
|
||||
|
||||
app.error(function(err, req, res, next){
|
||||
if (err instanceof NotFound) {
|
||||
res.render('404.jade');
|
||||
res.render('404.jade', {
|
||||
status: 404,
|
||||
locals: {
|
||||
error: err
|
||||
}
|
||||
});
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
@@ -49,10 +78,26 @@ app.error(function(err, req, res, next){
|
||||
|
||||
app.error(function(err, req, res){
|
||||
res.render('500.jade', {
|
||||
locals: {
|
||||
error: err
|
||||
}
|
||||
status: 500,
|
||||
locals: {
|
||||
error: err
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
// Routes
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('index.jade');
|
||||
});
|
||||
|
||||
app.get('/404', function(req, res){
|
||||
throw new NotFound;
|
||||
});
|
||||
|
||||
app.get('/500', function(req, res, next){
|
||||
next(new Error('keyboard cat!'));
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -1 +1,4 @@
|
||||
h2 Page Not Found
|
||||
- if (error.path)
|
||||
h2 Cannot find #{error.path}
|
||||
- else
|
||||
h2 Page Not Found
|
||||
@@ -31,7 +31,7 @@ var users = [
|
||||
{ name: 'tobi' }
|
||||
];
|
||||
|
||||
// Fake model
|
||||
// Fake controller
|
||||
|
||||
var User = {
|
||||
index: function(req, res){
|
||||
@@ -47,7 +47,7 @@ var User = {
|
||||
res.send(destroyed ? 'destroyed' : 'Cannot find user');
|
||||
},
|
||||
range: function(req, res, a, b){
|
||||
res.send(users.slice(a, b));
|
||||
res.send(users.slice(a, b+1));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,4 +71,5 @@ app.get('/', function(req, res){
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
// Example requests:
|
||||
// curl http://localhost:3000/user/0
|
||||
// curl http://localhost:3000/user/0/edit
|
||||
// curl http://localhost:3000/user/1
|
||||
// curl http://localhost:3000/user/1/edit (unauthorized since this is not you)
|
||||
// curl -X DELETE http://localhost:3000/user/0 (unauthorized since you are not an admin)
|
||||
|
||||
// Dummy users
|
||||
var users = [
|
||||
{ id: 0, name: 'tj', email: 'tj@vision-media.ca', role: 'member' },
|
||||
{ id: 1, name: 'ciaran', email: 'ciaranj@gmail.com', role: 'member' },
|
||||
{ id: 2, name: 'aaron', email: 'aaron.heckmann+github@gmail.com', role: 'admin' }
|
||||
];
|
||||
|
||||
function loadUser(req, res, next) {
|
||||
// You would fetch your user from the db
|
||||
var user = users[req.params.id];
|
||||
if (user) {
|
||||
req.user = user;
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Failed to load user ' + req.params.id));
|
||||
}
|
||||
}
|
||||
|
||||
function andRestrictToSelf(req, res, next) {
|
||||
// If our authenticated user is the user we are viewing
|
||||
// then everything is fine :)
|
||||
if (req.authenticatedUser.id == req.user.id) {
|
||||
next();
|
||||
} else {
|
||||
// You may want to implement specific exceptions
|
||||
// such as UnauthorizedError or similar so that you
|
||||
// can handle these in app.error() specifically
|
||||
// (view ./examples/pages for this)
|
||||
next(new Error('Unauthorized'));
|
||||
}
|
||||
}
|
||||
|
||||
function andRestrictTo(role) {
|
||||
return function(req, res, next) {
|
||||
if (req.authenticatedUser.role == role) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('Unauthorized'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware for faux authentication
|
||||
// you would of course implement something real,
|
||||
// but this illustrates how an authenticated user
|
||||
// may interacte with middleware
|
||||
|
||||
app.use(function(req, res, next){
|
||||
req.authenticatedUser = users[0];
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.redirect('/user/0');
|
||||
});
|
||||
|
||||
app.get('/user/:id', loadUser, function(req, res){
|
||||
res.send('Viewing user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
|
||||
res.send('Editing user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
|
||||
res.send('Deleted user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
@@ -3,20 +3,19 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('./../../lib/express'),
|
||||
connect = require('connect');
|
||||
var express = require('./../../lib/express');
|
||||
|
||||
var app = express.createServer(
|
||||
connect.logger(),
|
||||
express.logger(),
|
||||
|
||||
// Required by session() middleware
|
||||
connect.cookieDecoder(),
|
||||
express.cookieDecoder(),
|
||||
|
||||
// Populates:
|
||||
// - req.session
|
||||
// - req.sessionStore
|
||||
// - req.sessionHash (the SID fingerprint)
|
||||
connect.session()
|
||||
// - req.sessionID
|
||||
express.session()
|
||||
);
|
||||
|
||||
app.get('/', function(req, res){
|
||||
|
||||
+12
-2
@@ -5,11 +5,21 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Re-export connect auto-loaders.
|
||||
*
|
||||
* This prevents the need to `require('connect')` in order
|
||||
* to access core middleware, so for example `express.logger()` instead
|
||||
* of `require('connect').logger()`.
|
||||
*/
|
||||
|
||||
var exports = module.exports = require('connect').middleware;
|
||||
|
||||
/**
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
exports.version = '1.0.0rc';
|
||||
exports.version = '1.0.0rc4';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
@@ -34,5 +44,5 @@ exports.createServer = function(){
|
||||
*/
|
||||
|
||||
require('./view');
|
||||
require('./request');
|
||||
require('./response');
|
||||
require('./request');
|
||||
|
||||
@@ -13,6 +13,18 @@ var http = require('http'),
|
||||
utils = require('./utils'),
|
||||
mime = require('connect/utils').mime;
|
||||
|
||||
/**
|
||||
* Default flash formatters.
|
||||
*
|
||||
* @type Object
|
||||
*/
|
||||
|
||||
var flashFormatters = exports.flashFormatters = {
|
||||
s: function(val){
|
||||
return String(val);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return request header or optional default.
|
||||
*
|
||||
@@ -142,6 +154,16 @@ http.IncomingMessage.prototype.param = function(name){
|
||||
* req.flash();
|
||||
* // => { error: ['email delivery failed'], info: [] }
|
||||
*
|
||||
* Formatting:
|
||||
*
|
||||
* Flash notifications also support arbitrary formatting support.
|
||||
* For example you may pass variable arguments to `req.flash()`
|
||||
* and use the %s specifier to be replaced by the associated argument:
|
||||
*
|
||||
* req.flash('info', 'email has been sent to %s.', userName);
|
||||
*
|
||||
* To add custom formatters use the `exports.flashFormatters` object.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} msg
|
||||
* @return {Array|Object|Number}
|
||||
@@ -151,7 +173,15 @@ http.IncomingMessage.prototype.param = function(name){
|
||||
http.IncomingMessage.prototype.flash = function(type, msg){
|
||||
var msgs = this.session.flash = this.session.flash || {};
|
||||
if (type && msg) {
|
||||
var i = 2,
|
||||
args = arguments,
|
||||
formatters = this.app.flashFormatters || {};
|
||||
formatters.__proto__ = flashFormatters;
|
||||
msg = utils.miniMarkdown(utils.htmlEscape(msg));
|
||||
msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
|
||||
var formatter = formatters[format];
|
||||
if (formatter) return formatter(args[i++]);
|
||||
});
|
||||
return (msgs[type] = msgs[type] || []).push(msg);
|
||||
} else if (type) {
|
||||
var arr = msgs[type];
|
||||
|
||||
+155
-27
@@ -12,15 +12,26 @@
|
||||
var fs = require('fs'),
|
||||
http = require('http'),
|
||||
path = require('path'),
|
||||
pump = require('sys').pump,
|
||||
utils = require('connect/utils'),
|
||||
mime = require('connect/utils').mime,
|
||||
parseRange = require('./utils').parseRange,
|
||||
Buffer = require('buffer').Buffer;
|
||||
|
||||
/**
|
||||
* Header fields supporting multiple values.
|
||||
*
|
||||
* @type Array
|
||||
*/
|
||||
|
||||
var multiple = ['Set-Cookie'];
|
||||
|
||||
/**
|
||||
* Send a response with the given `body` and optional `headers` and `status` code.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* res.send();
|
||||
* res.send(new Buffer('wahoo'));
|
||||
* res.send({ some: 'json' });
|
||||
* res.send('<p>some html</p>');
|
||||
@@ -44,7 +55,11 @@ http.ServerResponse.prototype.send = function(body, headers, status){
|
||||
|
||||
// Defaults
|
||||
status = status || 200;
|
||||
headers = headers || {};
|
||||
|
||||
// Allow 0 args as 204
|
||||
if (!arguments.length) {
|
||||
body = status = 204;
|
||||
}
|
||||
|
||||
// Determine content type
|
||||
switch (typeof body) {
|
||||
@@ -69,6 +84,9 @@ http.ServerResponse.prototype.send = function(body, headers, status){
|
||||
this.contentType('.json');
|
||||
}
|
||||
body = JSON.stringify(body);
|
||||
if (this.req.query.callback && this.app.settings['jsonp callback']) {
|
||||
body = this.req.query.callback + '(' + body + ');';
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -81,31 +99,91 @@ http.ServerResponse.prototype.send = function(body, headers, status){
|
||||
}
|
||||
|
||||
// Merge headers passed
|
||||
utils.merge(this.headers, headers);
|
||||
if (headers) {
|
||||
var fields = Object.keys(headers);
|
||||
for (var i = 0, len = fields.length; i < len; ++i) {
|
||||
var field = fields[i];
|
||||
this.header(field, headers[field]);
|
||||
}
|
||||
}
|
||||
|
||||
// Strip irrelevant headers
|
||||
if (204 === status) {
|
||||
delete this.headers['Content-Type'];
|
||||
delete this.headers['Content-Length'];
|
||||
}
|
||||
|
||||
// Respond
|
||||
this.writeHead(status, this.headers);
|
||||
this.end(body);
|
||||
this.end('HEAD' == this.req.method ? undefined : body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transfer the given `file`. Automatically sets the _Content-Type_
|
||||
* response header, and responds with 404 / 500 appropriately.
|
||||
* Transfer the file at the given `path`. Automatically sets
|
||||
* the _Content-Type_ response header via `res.contentType()`.
|
||||
*
|
||||
* @param {String} file
|
||||
* The given callback `fn` is invoked when an error occurs,
|
||||
* passing it as the first argument, or when the file is transferred,
|
||||
* passing the path as the second argument.
|
||||
*
|
||||
* When the filesize is >= "stream threshold" (defaulting to 32k), the
|
||||
* file will be streamed using an `fs.ReadStream` and `sys.pump()`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
http.ServerResponse.prototype.sendfile = function(file){
|
||||
var self = this;
|
||||
fs.readFile(file, function(err, buf){
|
||||
if (err) {
|
||||
self.send(err.errno === process.ENOENT
|
||||
? 404
|
||||
: 500);
|
||||
http.ServerResponse.prototype.sendfile = function(path, fn){
|
||||
var self = this,
|
||||
streamThreshold = this.app.set('stream threshold') || 32 * 1024,
|
||||
ranges = self.req.headers.range;
|
||||
|
||||
if (~path.indexOf('..')) this.send(403);
|
||||
|
||||
function error(err) {
|
||||
delete self.headers['Content-Disposition'];
|
||||
if (fn) {
|
||||
fn(err, path);
|
||||
} else {
|
||||
self.contentType(file);
|
||||
self.send(buf);
|
||||
self.req.next(err);
|
||||
}
|
||||
}
|
||||
|
||||
fs.stat(path, function(err, stat){
|
||||
if (err) return error(err);
|
||||
if (stat.size >= streamThreshold) {
|
||||
var status = 200;
|
||||
if (ranges) ranges = parseRange(stat.size, ranges);
|
||||
if (ranges) {
|
||||
var stream = fs.createReadStream(path, ranges[0]),
|
||||
start = ranges[0].start,
|
||||
end = ranges[0].end;
|
||||
status = 206;
|
||||
self.header('Content-Range', 'bytes '
|
||||
+ start
|
||||
+ '-'
|
||||
+ end
|
||||
+ '/'
|
||||
+ stat.size);
|
||||
self.header('Content-Length', end - start + 1);
|
||||
} else {
|
||||
var stream = fs.createReadStream(path);
|
||||
self.header('Content-Length', stat.size);
|
||||
}
|
||||
self.contentType(path);
|
||||
self.header('Accept-Ranges', 'bytes');
|
||||
self.writeHead(status, self.headers);
|
||||
pump(stream, self, function(err){
|
||||
fn && fn(err, path, true);
|
||||
});
|
||||
} else {
|
||||
fs.readFile(path, function(err, buf){
|
||||
if (err) return error(err);
|
||||
self.contentType(path);
|
||||
self.send(buf);
|
||||
fn && fn(null, path);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -144,21 +222,30 @@ http.ServerResponse.prototype.attachment = function(filename){
|
||||
};
|
||||
|
||||
/**
|
||||
* Transfer the given `file` with optional `filename` as an attachment.
|
||||
* Transfer the file at the given `path`, with optional
|
||||
* `filename` as an attachment. Once transferred, or if an
|
||||
* error occurs `fn` is called with the error and path.
|
||||
*
|
||||
* @param {String} file
|
||||
* @param {String} path
|
||||
* @param {String} filename
|
||||
* @param {Function} fn
|
||||
* @return {Type}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
http.ServerResponse.prototype.download = function(file, filename){
|
||||
this.attachment(filename || file).sendfile(file);
|
||||
http.ServerResponse.prototype.download = function(path, filename, fn){
|
||||
this.attachment(filename || path).sendfile(path, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set or get response header `name` with optional `val`.
|
||||
*
|
||||
* Headers that may be set multiple times (as indicated by the `multiple` array)
|
||||
* can be called with a value several times, for example:
|
||||
*
|
||||
* res.header('Set-Cookie', '...');
|
||||
* res.header('Set-Cookie', '...');
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String} val
|
||||
* @return {String}
|
||||
@@ -166,9 +253,45 @@ http.ServerResponse.prototype.download = function(file, filename){
|
||||
*/
|
||||
|
||||
http.ServerResponse.prototype.header = function(name, val){
|
||||
return val === undefined
|
||||
? this.headers[name]
|
||||
: this.headers[name] = val;
|
||||
if (val === undefined) {
|
||||
return this.headers[name];
|
||||
} else {
|
||||
if (this.headers[name] && ~multiple.indexOf(name)) {
|
||||
return this.headers[name] += '\r\n' + name + ': ' + val;
|
||||
} else {
|
||||
return this.headers[name] = val;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear cookie `name`.
|
||||
*
|
||||
* @param {String} name
|
||||
* @api public
|
||||
*/
|
||||
|
||||
http.ServerResponse.prototype.clearCookie = function(name){
|
||||
this.cookie(name, '', { expires: new Date(1) });
|
||||
};
|
||||
|
||||
/**
|
||||
* Set cookie `name` to `val`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* // "Remember Me" for 15 minutes
|
||||
* res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String} val
|
||||
* @param {Options} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
http.ServerResponse.prototype.cookie = function(name, val, options){
|
||||
var cookie = utils.serializeCookie(name, val, options);
|
||||
this.header('Set-Cookie', cookie);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -195,8 +318,8 @@ http.ServerResponse.prototype.header = function(name, val){
|
||||
*
|
||||
* We may also map dynamic redirects:
|
||||
*
|
||||
* app.redirect('comments', function(req, res, params){
|
||||
* return '/post/' + params.id + '/comments';
|
||||
* app.redirect('comments', function(req, res){
|
||||
* return '/post/' + req.params.id + '/comments';
|
||||
* });
|
||||
*
|
||||
* So now we may do the following, and the redirect will dynamically adjust to
|
||||
@@ -226,10 +349,15 @@ http.ServerResponse.prototype.redirect = function(url, status){
|
||||
|
||||
// Attempt mapped redirect
|
||||
var mapped = typeof map[url] === 'function'
|
||||
? map[url](this.req, this, this.req.params.path)
|
||||
? map[url](this.req, this)
|
||||
: map[url];
|
||||
|
||||
// Perform redirect
|
||||
this.writeHead(status || 302, { 'Location': mapped || url });
|
||||
this.end();
|
||||
url = mapped || url;
|
||||
|
||||
// Respond
|
||||
this.send('Redirecting to ' + url, {
|
||||
'Content-Type': 'text/plain',
|
||||
'Location': url
|
||||
}, status || 302);
|
||||
};
|
||||
|
||||
+99
-13
@@ -9,8 +9,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var sys = require('sys'),
|
||||
url = require('url'),
|
||||
var url = require('url'),
|
||||
view = require('./view'),
|
||||
connect = require('connect'),
|
||||
utils = require('connect/utils'),
|
||||
@@ -31,15 +30,20 @@ var Server = exports = module.exports = function Server(middleware){
|
||||
this.redirects = {};
|
||||
this.viewHelpers = {};
|
||||
this.dynamicViewHelpers = {};
|
||||
this.errorHandlers = [];
|
||||
connect.Server.call(this, middleware || []);
|
||||
|
||||
// Default "home" to /
|
||||
this.set('home', '/');
|
||||
|
||||
// Set "env" to EXPRESS_ENV or connectEnv.name
|
||||
this.set('env',
|
||||
process.env.EXPRESS_ENV ||
|
||||
process.connectEnv.name);
|
||||
// DEPRECATED: remove in 1.0
|
||||
if (process.env.EXPRESS_ENV) {
|
||||
process.env.NODE_ENV = process.env.EXPRESS_ENV;
|
||||
console.warn('\x1b[33mWarning\x1b[0m: EXPRESS_ENV is deprecated, use NODE_ENV.');
|
||||
}
|
||||
|
||||
// Set "env" to NODE_ENV, defaulting to "development"
|
||||
this.set('env', process.env.NODE_ENV || 'development');
|
||||
|
||||
// Expose objects to each other
|
||||
this.use(function(req, res, next){
|
||||
@@ -52,7 +56,7 @@ var Server = exports = module.exports = function Server(middleware){
|
||||
// Assign req.params.get
|
||||
if (req.url.indexOf('?') > 0) {
|
||||
var query = url.parse(req.url).query;
|
||||
req.query = queryString.parse(query);
|
||||
req.query = exports.parseQueryString(query);
|
||||
}
|
||||
next();
|
||||
});
|
||||
@@ -69,7 +73,48 @@ var Server = exports = module.exports = function Server(middleware){
|
||||
* Inherit from `connect.Server`.
|
||||
*/
|
||||
|
||||
sys.inherits(Server, connect.Server);
|
||||
Server.prototype.__proto__ = connect.Server.prototype;
|
||||
|
||||
/**
|
||||
* Support swappable querystring parsers.
|
||||
*/
|
||||
|
||||
exports.parseQueryString = queryString.parse;
|
||||
|
||||
/**
|
||||
* Proxy in order to register error handlers.
|
||||
*/
|
||||
|
||||
Server.prototype.listen = function(){
|
||||
this.registerErrorHandlers();
|
||||
connect.Server.prototype.listen.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Proxy in order to register error handlers.
|
||||
*/
|
||||
|
||||
Server.prototype.listenFD = function(){
|
||||
this.registerErrorHandlers();
|
||||
connect.Server.prototype.listenFD.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register error handlers. This is automatically
|
||||
* called from within `Server#listen()` and `Server#listenFD()`.
|
||||
*
|
||||
* @return {Server} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.registerErrorHandlers = function(){
|
||||
this.errorHandlers.forEach(function(fn){
|
||||
this.use(function(err, req, res, next){
|
||||
fn.apply(this, arguments);
|
||||
});
|
||||
}, this);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Proxy `connect.Server#use()` to apply settings to
|
||||
@@ -95,6 +140,7 @@ Server.prototype.use = function(route, middleware){
|
||||
home = app.set('home');
|
||||
if (home === '/') home = '';
|
||||
app.set('home', (app.route || '') + home);
|
||||
app.parent = this;
|
||||
// Mounted hook
|
||||
if (app.__mounted) app.__mounted.call(app, this);
|
||||
}
|
||||
@@ -128,6 +174,18 @@ Server.prototype.mounted = function(fn){
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* See: view.register.
|
||||
*
|
||||
* @return {Server} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.register = function(){
|
||||
view.register.apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register the given view helpers `obj`. This method
|
||||
* can be called several times to apply additional helpers.
|
||||
@@ -158,6 +216,7 @@ Server.prototype.dynamicHelpers = function(obj){
|
||||
|
||||
/**
|
||||
* Assign a custom exception handler callback `fn`.
|
||||
* These handlers are always _last_ in the middleware stack.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Server} for chaining
|
||||
@@ -165,14 +224,13 @@ Server.prototype.dynamicHelpers = function(obj){
|
||||
*/
|
||||
|
||||
Server.prototype.error = function(fn){
|
||||
this.use(function(err, req, res, next){
|
||||
fn.apply(this, arguments);
|
||||
});
|
||||
this.errorHandlers.push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Assign `setting` to `val`, or return `setting`'s value.
|
||||
* Mounted servers inherit their parent server's settings.
|
||||
*
|
||||
* @param {String} setting
|
||||
* @param {String} val
|
||||
@@ -182,7 +240,11 @@ Server.prototype.error = function(fn){
|
||||
|
||||
Server.prototype.set = function(setting, val){
|
||||
if (val === undefined) {
|
||||
return this.settings[setting];
|
||||
if (this.settings.hasOwnProperty(setting)) {
|
||||
return this.settings[setting];
|
||||
} else if (this.parent) {
|
||||
return this.parent.set(setting);
|
||||
}
|
||||
} else {
|
||||
this.settings[setting] = val;
|
||||
return this;
|
||||
@@ -250,11 +312,35 @@ Server.prototype.configure = function(env, fn){
|
||||
|
||||
(function(method){
|
||||
Server.prototype[method] = function(path, fn){
|
||||
var self = this;
|
||||
|
||||
// Ensure router is mounted
|
||||
if (!this.__usedRouter) {
|
||||
this.use(this.router);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Generate the route
|
||||
this.routes[method](path, fn);
|
||||
return this;
|
||||
};
|
||||
return arguments.callee;
|
||||
})('get')('post')('put')('del');
|
||||
})('get')('post')('put')('delete');
|
||||
|
||||
// Alias delete as "del"
|
||||
|
||||
Server.prototype.del = Server.prototype.delete;
|
||||
+35
-2
@@ -16,7 +16,7 @@
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {String}
|
||||
* @api public
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.miniMarkdown = function(str){
|
||||
@@ -31,7 +31,7 @@ exports.miniMarkdown = function(str){
|
||||
*
|
||||
* @param {String} html
|
||||
* @return {String}
|
||||
* @api public
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.htmlEscape = function(html) {
|
||||
@@ -40,4 +40,37 @@ exports.htmlEscape = function(html) {
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse "Range" header `str` relative to the given file `size`.
|
||||
*
|
||||
* @param {Number} size
|
||||
* @param {String} str
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.parseRange = function(size, str){
|
||||
var valid = true;
|
||||
var arr = str.substr(6).split(',').map(function(range){
|
||||
var range = range.split('-'),
|
||||
start = parseInt(range[0], 10),
|
||||
end = parseInt(range[1], 10);
|
||||
|
||||
// -500
|
||||
if (isNaN(start)) {
|
||||
start = size - end;
|
||||
end = size - 1;
|
||||
// 500-
|
||||
} else if (isNaN(end)) {
|
||||
end = size - 1;
|
||||
}
|
||||
|
||||
// Invalid
|
||||
if (isNaN(start) || isNaN(end) || start > end) valid = false;
|
||||
|
||||
return { start: start, end: end };
|
||||
});
|
||||
return valid ? arr : undefined;
|
||||
};
|
||||
+83
-36
@@ -13,7 +13,6 @@ var extname = require('path').extname,
|
||||
mime = require('connect/utils').mime,
|
||||
utils = require('connect/utils'),
|
||||
http = require('http'),
|
||||
sys = require('sys'),
|
||||
fs = require('fs');
|
||||
|
||||
/**
|
||||
@@ -34,16 +33,6 @@ var cache = {};
|
||||
|
||||
var viewCache = {};
|
||||
|
||||
/**
|
||||
* Clear the view cache.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.clearCache = function(){
|
||||
viewCache = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously cache view at the given `path`.
|
||||
*
|
||||
@@ -68,6 +57,39 @@ function viewRoot(app) {
|
||||
return app.set('views') || process.cwd() + '/views';
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given template engine `exports`
|
||||
* as `ext`. For example we may wish to map ".html"
|
||||
* files to jade:
|
||||
*
|
||||
* app.register('.html', require('jade'));
|
||||
*
|
||||
* This is also useful for libraries that may not
|
||||
* match extensions correctly. For example my haml.js
|
||||
* library is installed from npm as "hamljs" so instead
|
||||
* of layout.hamljs, we can register the engine as ".haml":
|
||||
*
|
||||
* app.register('.haml', require('haml-js'));
|
||||
*
|
||||
* For engines that do not comply with the Express
|
||||
* specification, we can also wrap their api this way.
|
||||
*
|
||||
* app.register('.foo', {
|
||||
* render: function(str, options) {
|
||||
* // perhaps their api is
|
||||
* // foo.toHTML(str, options);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @param {String} ext
|
||||
* @param {Object} obj
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.register = function(ext, exports) {
|
||||
cache[ext] = exports;
|
||||
};
|
||||
|
||||
/**
|
||||
* Render `view` partial with the given `options`.
|
||||
*
|
||||
@@ -95,7 +117,7 @@ http.ServerResponse.prototype.partial = function(view, options, ext){
|
||||
}
|
||||
|
||||
// Allow collection to be passed as second param
|
||||
if (Array.isArray(options)) {
|
||||
if (options && options.hasOwnProperty('length')) {
|
||||
options = { collection: options };
|
||||
}
|
||||
|
||||
@@ -109,16 +131,19 @@ http.ServerResponse.prototype.partial = function(view, options, ext){
|
||||
var collection = options.collection;
|
||||
if (collection) {
|
||||
var name = options.as || view.split('.')[0],
|
||||
len = collection.length;
|
||||
len = collection.length,
|
||||
buf = '';
|
||||
delete options.collection;
|
||||
options.locals.collectionLength = len;
|
||||
return collection.map(function(val, i){
|
||||
for (var i = 0, len = collection.length; i < len; ++i) {
|
||||
var val = collection[i];
|
||||
options.locals.firstInCollection = i === 0;
|
||||
options.locals.indexInCollection = i;
|
||||
options.locals.lastInCollection = i === len - 1;
|
||||
options.object = val;
|
||||
return this.partial(view, options);
|
||||
}, this).join('');
|
||||
buf += this.partial(view, options);
|
||||
}
|
||||
return buf;
|
||||
} else {
|
||||
if (options.object) {
|
||||
var name = options.as || view.split('.')[0];
|
||||
@@ -127,7 +152,7 @@ http.ServerResponse.prototype.partial = function(view, options, ext){
|
||||
} else if (name === global) {
|
||||
utils.merge(options.locals, options.object);
|
||||
} else {
|
||||
options.context = options.object;
|
||||
options.scope = options.object;
|
||||
}
|
||||
}
|
||||
return this.render(view, options);
|
||||
@@ -144,19 +169,30 @@ http.ServerResponse.prototype.partial = function(view, options, ext){
|
||||
* Most engines accept one or more of the following options,
|
||||
* both _haml_ and _jade_ accept all:
|
||||
*
|
||||
* - `context|scope` Template evaluation context (`this`)
|
||||
* - `scope` Template evaluation context (the value of `this`)
|
||||
* - `locals` Object containing local variables
|
||||
* - `debug` Output debugging information
|
||||
* - `status` Response status code, defaults to 200
|
||||
* - `headers` Response headers object
|
||||
*
|
||||
* @param {String} view
|
||||
* @param {Object} options
|
||||
* @param {Object|Function} options or callback function
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
http.ServerResponse.prototype.render = function(view, options, fn){
|
||||
options = options || {};
|
||||
var defaultEngine = this.app.set('view engine');
|
||||
// Support callback function as second arg
|
||||
if (typeof options === 'function') {
|
||||
fn = options, options = {};
|
||||
}
|
||||
|
||||
var options = options || {},
|
||||
viewOptions = this.app.set('view options'),
|
||||
defaultEngine = this.app.set('view engine');
|
||||
|
||||
// Mixin "view options"
|
||||
if (viewOptions) options.__proto__ = viewOptions;
|
||||
|
||||
// Support "view engine" setting
|
||||
if (view.indexOf('.') < 0 && defaultEngine) {
|
||||
@@ -180,8 +216,8 @@ http.ServerResponse.prototype.render = function(view, options, fn){
|
||||
layout += ext;
|
||||
}
|
||||
|
||||
// Normalize "context" to "scope", defaulting to this response
|
||||
options.scope = options.scope || options.context || this;
|
||||
// Default execution scope to the response
|
||||
options.scope = options.scope || this.req;
|
||||
|
||||
// Auto-cache in production
|
||||
if (this.app.set('env') === 'production') {
|
||||
@@ -190,7 +226,9 @@ http.ServerResponse.prototype.render = function(view, options, fn){
|
||||
|
||||
// Partials support
|
||||
if (options.partial) {
|
||||
root += '/partials';
|
||||
root = this.app.set('partials')
|
||||
? this.app.set('partials')
|
||||
: root + '/partials';
|
||||
}
|
||||
|
||||
// View path
|
||||
@@ -203,17 +241,25 @@ http.ServerResponse.prototype.render = function(view, options, fn){
|
||||
options.locals.__filename = options.filename = path;
|
||||
|
||||
// Dynamic helper support
|
||||
var keys = Object.keys(dynamicHelpers);
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
var key = keys[i],
|
||||
val = dynamicHelpers[key];
|
||||
if (typeof val === 'function') {
|
||||
helpers[key] = val.call(
|
||||
this.app,
|
||||
this.req,
|
||||
this,
|
||||
this.req.params.path);
|
||||
if (options.dynamicHelpers !== false) {
|
||||
// cache
|
||||
if (!this.__dynamicHelpers) {
|
||||
this.__dynamicHelpers = {};
|
||||
var keys = Object.keys(dynamicHelpers);
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
var key = keys[i],
|
||||
val = dynamicHelpers[key];
|
||||
if (typeof val === 'function') {
|
||||
this.__dynamicHelpers[key] = val.call(
|
||||
this.app,
|
||||
this.req,
|
||||
this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply
|
||||
utils.merge(helpers, this.__dynamicHelpers);
|
||||
}
|
||||
|
||||
// Always expose partial() as a local
|
||||
@@ -253,12 +299,13 @@ http.ServerResponse.prototype.render = function(view, options, fn){
|
||||
if (layout) {
|
||||
options.layout = false;
|
||||
options.locals.body = str;
|
||||
self.render(layout, options);
|
||||
options.isLayout = true;
|
||||
self.render(layout, options, fn);
|
||||
} else if (partial) {
|
||||
return str;
|
||||
} else if (fn) {
|
||||
fn(null, str);
|
||||
} else {
|
||||
self.send(str);
|
||||
this.send(str, options.headers, options.status);
|
||||
}
|
||||
};
|
||||
|
||||
+5
-4
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"name": "express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "1.0.0rc",
|
||||
"version": "1.0.0rc4",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
|
||||
{ "name": "Aaron Heckmann", "email": "aaron.heckmann+github@gmail.com" },
|
||||
{ "name": "Ciaran Jessup", "email": "ciaranj@gmail.com" }
|
||||
{ "name": "Ciaran Jessup", "email": "ciaranj@gmail.com" },
|
||||
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
|
||||
],
|
||||
"dependencies": { "connect": ">= 0.2.2" },
|
||||
"dependencies": { "connect": ">= 0.2.6" },
|
||||
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
|
||||
"directories": { "lib": "./lib/express" },
|
||||
"scripts": { "test": "make test" },
|
||||
"bin": { "express": "./bin/express" },
|
||||
"engines": { "node": ">= 0.1.98" }
|
||||
"engines": { "node": ">= 0.2.0" }
|
||||
}
|
||||
+1
-1
Submodule support/connect updated: fadf5db5d3...8db689290b
+1
-1
Submodule support/connect-form updated: ac57d4a612...6abfb690d3
+1
-1
Submodule support/ejs updated: 5a565662fe...b1d822e99c
+1
-1
Submodule support/expresso updated: b067338ae0...c0a9e3005b
+1
-1
Submodule support/jade updated: 27534d9784...897b17fa4a
+28
-3
@@ -6,16 +6,41 @@ var fs = require('fs'),
|
||||
if (file) {
|
||||
var js = fs.readFileSync(file, 'utf8'),
|
||||
headers = js.match(/<h3 id="(.*?)">(.*?)<\/h3>/g),
|
||||
toc = ['<ul id="toc">'];
|
||||
toc = ['<ul id="toc">'],
|
||||
sections = {};
|
||||
if (js.indexOf('id="toc"') < 0) {
|
||||
headers.forEach(function(header){
|
||||
var captures = header.match(/id="(.*?)">(.*?)</),
|
||||
id = captures[1],
|
||||
title = captures[2].replace(/\(.*?\)/, '()');
|
||||
toc.push('<li><a href="#' + id + '">' + title + '</a></li>');
|
||||
title = captures[2].replace(/\(.*?\)/, '()')
|
||||
if (~title.indexOf('.')) {
|
||||
var parts = title.split('.'),
|
||||
recv = parts.shift(),
|
||||
method = parts.shift();
|
||||
if (recv == 'app') recv = 'Server';
|
||||
if (recv == 'req') recv = 'Request';
|
||||
if (recv == 'res') recv = 'Response';
|
||||
sections[recv] = sections[recv] || [];
|
||||
sections[recv].push('<li><a href="#' + id + '">' + method + '</a></li>');
|
||||
} else {
|
||||
toc.push('<li><a href="#' + id + '">' + title + '</a></li>');
|
||||
}
|
||||
});
|
||||
toc.push(renderSections());
|
||||
toc.push('</ul>');
|
||||
js = js.replace('<div id="container">', '<div id="container">' + toc.join('\n'));
|
||||
fs.writeFileSync(file, js);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSections() {
|
||||
var buf = [];
|
||||
Object.keys(sections).forEach(function(section){
|
||||
var methods = sections[section],
|
||||
a = '<a href="#" class="toggle">+</a> <a class="section-title" href="#">' + section + '</a>';
|
||||
buf.push('<li>' + a + '<ul class="section" id="section-' + section + '">');
|
||||
buf.push(methods.join('\n'));
|
||||
buf.push('</ul></li>');
|
||||
});
|
||||
return buf.join('\n');
|
||||
}
|
||||
+87
-12
@@ -8,6 +8,10 @@ module.exports = {
|
||||
assert.ok(server instanceof connect.Server, 'Test serverlication inheritance');
|
||||
},
|
||||
|
||||
'test connect middleware autoloaders': function(assert){
|
||||
assert.equal(express.errorHandler, connect.errorHandler);
|
||||
},
|
||||
|
||||
'test basic server': function(assert){
|
||||
var server = express.createServer();
|
||||
|
||||
@@ -22,6 +26,14 @@ module.exports = {
|
||||
res.end('updated user ' + req.params.id)
|
||||
});
|
||||
|
||||
server.del('/something', function(req, res){
|
||||
res.send('Destroyed');
|
||||
});
|
||||
|
||||
server.delete('/something/else', function(req, res){
|
||||
res.send('Destroyed');
|
||||
});
|
||||
|
||||
assert.response(server,
|
||||
{ url: '/' },
|
||||
{ body: 'wahoo' });
|
||||
@@ -29,6 +41,14 @@ module.exports = {
|
||||
assert.response(server,
|
||||
{ url: '/user/12', method: 'PUT' },
|
||||
{ body: 'updated user 12' });
|
||||
|
||||
assert.response(server,
|
||||
{ url: '/something', method: 'DELETE' },
|
||||
{ body: 'Destroyed' });
|
||||
|
||||
assert.response(server,
|
||||
{ url: '/something/else', method: 'DELETE' },
|
||||
{ body: 'Destroyed' });
|
||||
},
|
||||
|
||||
'test constructor middleware': function(assert, beforeExit){
|
||||
@@ -73,13 +93,13 @@ module.exports = {
|
||||
// Custom handler
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
next(new Error('broken'));
|
||||
});
|
||||
|
||||
app.error(function(err, req, res){
|
||||
res.send('Shit: ' + err.message, 500);
|
||||
});
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
next(new Error('broken'));
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
@@ -88,13 +108,6 @@ module.exports = {
|
||||
// Multiple error()s
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
throw new Error('broken');
|
||||
});
|
||||
app.get('/foo', function(req, res, next){
|
||||
throw new Error('oh noes');
|
||||
});
|
||||
|
||||
app.error(function(err, req, res, next){
|
||||
if (err.message === 'broken') {
|
||||
next(err);
|
||||
@@ -106,6 +119,13 @@ module.exports = {
|
||||
app.error(function(err, req, res, next){
|
||||
res.send(err.message, 500);
|
||||
});
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
throw new Error('broken');
|
||||
});
|
||||
app.get('/foo', function(req, res, next){
|
||||
throw new Error('oh noes');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
@@ -158,7 +178,7 @@ module.exports = {
|
||||
|
||||
'test #configure()': function(assert, beforeExit){
|
||||
var calls = [];
|
||||
process.env.EXPRESS_ENV = 'development';
|
||||
process.env.NODE_ENV = 'development';
|
||||
var server = express.createServer();
|
||||
|
||||
// Config blocks
|
||||
@@ -284,6 +304,11 @@ module.exports = {
|
||||
assert.equal('/blog', blog.route);
|
||||
assert.equal('/contact', map.route);
|
||||
assert.ok(called, 'mounted() hook failed');
|
||||
|
||||
app.set("test", "parent setting");
|
||||
assert.equal(blog.set("test"), "parent setting", "settings did not inherit from parent app")
|
||||
blog.set("test", "overridden");
|
||||
assert.equal(blog.set("test"), "overridden", "mounted app setting did not override parent app")
|
||||
|
||||
app.get('/', function(req, res){
|
||||
assert.equal('/', app.set('home'), "home did not default to /");
|
||||
@@ -312,5 +337,55 @@ module.exports = {
|
||||
assert.response(blog,
|
||||
{ url: '/' },
|
||||
{ body: 'blog index' });
|
||||
},
|
||||
|
||||
'test route middleware': function(assert){
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
});
|
||||
}
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
<p><%= greetings(session, lastName) %></p>
|
||||
+1
-1
@@ -1 +1 @@
|
||||
p #{session.name} #{lastName} #{foo()}
|
||||
p #{greetings(session, lastName)}
|
||||
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
p This is actually jade :)
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
p :(
|
||||
externo
+151
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar",
|
||||
"foo":"bar"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
li.item= item
|
||||
externo
+2
@@ -0,0 +1,2 @@
|
||||
<h1>{{= name }}</h1>
|
||||
<p>{{= email }}</p>
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
<h1><?= title ?></h1>
|
||||
@@ -117,6 +117,12 @@ module.exports = {
|
||||
connect.session({ store: memoryStore })
|
||||
);
|
||||
|
||||
app.flashFormatters = {
|
||||
u: function(val){
|
||||
return String(val).toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/', function(req, res){
|
||||
assert.eql([], req.flash('info'));
|
||||
assert.eql([], req.flash('error'));
|
||||
@@ -134,6 +140,13 @@ module.exports = {
|
||||
req.flash('info', 'Email _sent_.');
|
||||
req.flash('info', '<script>');
|
||||
assert.eql(['Email <em>sent</em>.', '<script>'], req.flash('info'));
|
||||
|
||||
req.flash('info', 'Welcome _%s_ to %s', 'TJ', 'something');
|
||||
assert.eql(['Welcome <em>TJ</em> to something'], req.flash('info'));
|
||||
|
||||
req.flash('info', 'Foo %u', 'bar');
|
||||
assert.eql(['Foo BAR'], req.flash('info'));
|
||||
|
||||
res.send('ok');
|
||||
});
|
||||
|
||||
|
||||
+200
-21
@@ -19,6 +19,13 @@ module.exports = {
|
||||
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
|
||||
});
|
||||
|
||||
app.get('/jsonp', function(req, res){
|
||||
app.enable('jsonp callback');
|
||||
res.header('X-Foo', 'bar');
|
||||
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
|
||||
app.disable('jsonp callback');
|
||||
});
|
||||
|
||||
app.get('/text', function(req, res){
|
||||
res.header('X-Foo', 'bar');
|
||||
res.contentType('.txt');
|
||||
@@ -36,6 +43,10 @@ module.exports = {
|
||||
app.get('/buffer', function(req, res){
|
||||
res.send(new Buffer('wahoo!'));
|
||||
});
|
||||
|
||||
app.get('/noargs', function(req, res, next){
|
||||
res.send();
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/html' },
|
||||
@@ -43,6 +54,19 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/json' },
|
||||
{ body: '{"foo":"bar"}', status: 201, headers: { 'Content-Type': 'application/json', 'X-Foo': 'baz' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=test' },
|
||||
{ body: 'test({"foo":"bar"});', status: 201, headers: { 'Content-Type': 'application/json', 'X-Foo': 'baz' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=baz' },
|
||||
{ body: 'baz({"foo":"bar"});', status: 201, headers: { 'Content-Type': 'application/json', 'X-Foo': 'baz' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/json?callback=test' },
|
||||
{ body: '{"foo":"bar"}', status: 201, headers: { 'Content-Type': 'application/json', 'X-Foo': 'baz' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/text' },
|
||||
{ body: 'wahoo', headers: { 'Content-Type': 'text/plain; charset=utf-8', 'X-Foo': 'bar' }});
|
||||
@@ -55,9 +79,15 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/buffer' },
|
||||
{ body: 'wahoo!', headers: { 'Content-Type': 'application/octet-stream' }});
|
||||
assert.response(app,
|
||||
{ url: '/noargs' },
|
||||
{ status: 204 }, function(res){
|
||||
assert.equal(undefined, res.headers['content-type']);
|
||||
assert.equal(undefined, res.headers['content-length']);
|
||||
});
|
||||
},
|
||||
|
||||
'test #contentType': function(assert){
|
||||
'test #contentType()': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/html', function(req, res){
|
||||
@@ -71,7 +101,7 @@ module.exports = {
|
||||
{ body: '<p>yay</p>', headers: { 'Content-Type': 'text/html; charset=utf-8' }});
|
||||
},
|
||||
|
||||
'test #attachment': function(assert){
|
||||
'test #attachment()': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/style.css', function(req, res){
|
||||
@@ -135,50 +165,62 @@ module.exports = {
|
||||
});
|
||||
|
||||
app2.get('/user/:id', function(req, res){
|
||||
res.header('X-Foo', 'bar');
|
||||
res.redirect('blog');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '', status: 301, headers: { Location: 'http://google.com' }});
|
||||
{ body: 'Redirecting to http://google.com', status: 301, headers: { Location: 'http://google.com' }});
|
||||
assert.response(app,
|
||||
{ url: '/back' },
|
||||
{ body: '', status: 302, headers: { Location: '/' }});
|
||||
{ body: 'Redirecting to /', status: 302, headers: { Location: '/', 'Content-Type': 'text/plain' }});
|
||||
assert.response(app,
|
||||
{ url: '/back', headers: { Referer: '/foo' }},
|
||||
{ body: '', status: 302, headers: { Location: '/foo' }});
|
||||
{ body: 'Redirecting to /foo', status: 302, headers: { Location: '/foo' }});
|
||||
assert.response(app,
|
||||
{ url: '/back', headers: { Referrer: '/foo' }},
|
||||
{ body: '', status: 302, headers: { Location: '/foo' }});
|
||||
{ body: 'Redirecting to /foo', status: 302, headers: { Location: '/foo' }});
|
||||
assert.response(app,
|
||||
{ url: '/home' },
|
||||
{ body: '', status: 302, headers: { Location: '/' }});
|
||||
{ body: 'Redirecting to /', status: 302, headers: { Location: '/' }});
|
||||
|
||||
assert.response(app2,
|
||||
{ url: '/' },
|
||||
{ body: '', status: 301, headers: { Location: 'http://google.com' }});
|
||||
{ body: 'Redirecting to http://google.com', status: 301, headers: { Location: 'http://google.com' }});
|
||||
assert.response(app2,
|
||||
{ url: '/back' },
|
||||
{ body: '', status: 302, headers: { Location: '/blog' }});
|
||||
{ body: 'Redirecting to /blog', status: 302, headers: { Location: '/blog' }});
|
||||
assert.response(app2,
|
||||
{ url: '/home' },
|
||||
{ body: '', status: 302, headers: { Location: '/blog' }});
|
||||
{ body: 'Redirecting to /blog', status: 302, headers: { Location: '/blog' }});
|
||||
assert.response(app2,
|
||||
{ url: '/google' },
|
||||
{ body: '', headers: { Location: 'http://google.com' }});
|
||||
{ body: 'Redirecting to http://google.com', headers: { Location: 'http://google.com' }});
|
||||
assert.response(app2,
|
||||
{ url: '/user/12' },
|
||||
{ body: '', headers: { Location: '/user/12/blog' }});
|
||||
{ body: 'Redirecting to /user/12/blog', headers: { Location: '/user/12/blog', 'X-Foo': 'bar' }});
|
||||
},
|
||||
|
||||
'test #sendfile()': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/*', function(req, res){
|
||||
var file = req.params[0];
|
||||
res.sendfile(__dirname + '/fixtures/' + file);
|
||||
|
||||
app.set('stream threshold', 2 * 1024)
|
||||
|
||||
app.get('/*', function(req, res, next){
|
||||
var file = req.params[0],
|
||||
filePath = __dirname + '/fixtures/' + file;
|
||||
res.sendfile(filePath, function(err, path){
|
||||
assert.equal(path, filePath);
|
||||
if (err) next(err);
|
||||
});
|
||||
});
|
||||
|
||||
app.use(express.errorHandler());
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/../express.test.js' },
|
||||
{ body: 'Forbidden', status: 403 });
|
||||
assert.response(app,
|
||||
{ url: '/user.json' },
|
||||
{ body: '{"name":"tj"}', status: 200, headers: { 'Content-Type': 'application/json' }});
|
||||
@@ -187,23 +229,107 @@ module.exports = {
|
||||
{ body: '%p Hello World', status: 200, headers: { 'Content-Type': 'application/octet-stream' }});
|
||||
assert.response(app,
|
||||
{ url: '/doesNotExist' },
|
||||
{ body: 'Not Found', status: 404 });
|
||||
{ body: 'Internal Server Error', status: 500 });
|
||||
assert.response(app,
|
||||
{ url: '/partials' },
|
||||
{ body: 'Internal Server Error', status: 500 });
|
||||
assert.response(app,
|
||||
{ url: '/large.json' },
|
||||
{ status: 200, headers: { 'Content-Length': 2535, 'Content-Type': 'application/json' }});
|
||||
},
|
||||
|
||||
'test #sendfile() Accept-Ranges': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('stream threshold', 1024);
|
||||
|
||||
app.get('/*', function(req, res, next){
|
||||
var file = req.params[0],
|
||||
path = __dirname + '/fixtures/' + file;
|
||||
res.sendfile(path);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/large.json' },
|
||||
{ headers: { 'Accept-Ranges': 'bytes' }});
|
||||
},
|
||||
|
||||
'test #sendfile() Range': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('stream threshold', 1024);
|
||||
|
||||
app.get('/*', function(req, res, next){
|
||||
var file = req.params[0],
|
||||
path = __dirname + '/fixtures/' + file;
|
||||
res.sendfile(path);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/large.json', headers: { 'Range': 'bytes=0-499' }},
|
||||
{ headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': 500,
|
||||
'Content-Range': 'bytes 0-499/2535'
|
||||
}, status: 206 });
|
||||
},
|
||||
|
||||
'test #sendfile() Range invalid syntax': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('stream threshold', 1024);
|
||||
|
||||
app.get('/*', function(req, res, next){
|
||||
var file = req.params[0],
|
||||
path = __dirname + '/fixtures/' + file;
|
||||
res.sendfile(path);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/large.json', headers: { 'Range': 'basdytes=asdf' }},
|
||||
{ headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': 2535
|
||||
}, status: 200 });
|
||||
},
|
||||
|
||||
'test #sendfile() Range invalid range': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('stream threshold', 1024);
|
||||
|
||||
app.get('/*', function(req, res, next){
|
||||
var file = req.params[0],
|
||||
path = __dirname + '/fixtures/' + file;
|
||||
res.sendfile(path);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/large.json', headers: { 'Range': 'bytes=500-10' }},
|
||||
{ headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': 2535
|
||||
}, status: 200 });
|
||||
},
|
||||
|
||||
'test #download()': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/json', function(req, res, next){
|
||||
res.download(__dirname + '/fixtures/user.json', 'account.json');
|
||||
var filePath = __dirname + '/fixtures/user.json';
|
||||
res.download(filePath, 'account.json', function(err, path){
|
||||
assert.equal(filePath, path);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/:file', function(req, res, next){
|
||||
res.download(__dirname + '/fixtures/' + req.params.file);
|
||||
app.get('/*', function(req, res, next){
|
||||
res.download(__dirname + '/fixtures/' + req.params[0]);
|
||||
});
|
||||
|
||||
app.error(function(err, req, res){
|
||||
res.send(404);
|
||||
})
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user.json' },
|
||||
{ body: '{"name":"tj"}', status: 200, headers: {
|
||||
@@ -215,7 +341,60 @@ module.exports = {
|
||||
{ url: '/json' },
|
||||
{ body: '{"name":"tj"}', status: 200, headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': '13',
|
||||
'Content-Disposition': 'attachment; filename="account.json"'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/missing' },
|
||||
function(res){
|
||||
assert.equal(null, res.headers['content-disposition']);
|
||||
});
|
||||
},
|
||||
|
||||
'test #cookie()': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.cookie('rememberme', 'yes', { expires: new Date(1), httpOnly: true });
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
function(res){
|
||||
assert.equal(
|
||||
'rememberme=yes; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly',
|
||||
res.headers['set-cookie']);
|
||||
});
|
||||
},
|
||||
|
||||
'test #clearCookie()': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.clearCookie('rememberme');
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
function(res){
|
||||
assert.equal(
|
||||
'rememberme=; expires=Thu, 01 Jan 1970 00:00:00 GMT',
|
||||
res.headers['set-cookie']);
|
||||
});
|
||||
},
|
||||
|
||||
'test HEAD': function(assert){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.send('Hello World');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', method: 'HEAD' },
|
||||
{ body: '', headers: { 'Content-Length': 11 }});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
+10
-1
@@ -6,12 +6,21 @@
|
||||
var utils = require('express/utils');
|
||||
|
||||
module.exports = {
|
||||
'test miniMarkdown': function(assert){
|
||||
'test .miniMarkdown()': function(assert){
|
||||
assert.equal('<em>foo</em>', utils.miniMarkdown('_foo_'));
|
||||
assert.equal('<em>foo</em>', utils.miniMarkdown('*foo*'));
|
||||
assert.equal('<strong>foo</strong>', utils.miniMarkdown('**foo**'));
|
||||
assert.equal('<strong>foo</strong>', utils.miniMarkdown('__foo__'));
|
||||
assert.equal('<em>foo</em>_', utils.miniMarkdown('_foo__'));
|
||||
assert.equal('<a href="/login">User Login</a>', utils.miniMarkdown('[User Login](/login)'));
|
||||
},
|
||||
|
||||
'test .parseRange()': function(assert){
|
||||
assert.eql([{start: 0, end: 499 }], utils.parseRange(1000, 'bytes=0-499'));
|
||||
assert.eql([{start: 40, end: 80 }], utils.parseRange(1000, 'bytes=40-80'));
|
||||
assert.eql([{start: 500, end: 999 }], utils.parseRange(1000, 'bytes=-500'));
|
||||
assert.eql([{start: 500, end: 999 }], utils.parseRange(1000, 'bytes=500-'));
|
||||
assert.eql([{start: 0, end: 0 }], utils.parseRange(1000, 'bytes=0-0'));
|
||||
assert.eql([{start: 999, end: 999 }], utils.parseRange(1000, 'bytes=-1'));
|
||||
}
|
||||
};
|
||||
+207
-10
@@ -15,7 +15,7 @@ var create = function(){
|
||||
|
||||
module.exports = {
|
||||
'test #render()': function(assert){
|
||||
var app = create(connect.errorHandler({ showMessage: true }));
|
||||
var app = create();
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
@@ -27,6 +27,18 @@ module.exports = {
|
||||
app.get('/haml', function(req, res){
|
||||
res.render('hello.haml', { layout: false });
|
||||
});
|
||||
app.get('/callback/layout/no-options', function(req, res){
|
||||
res.render('hello.jade', function(err, str){
|
||||
assert.ok(!err);
|
||||
res.send(str.replace(':(', ':)'));
|
||||
});
|
||||
});
|
||||
app.get('/callback/layout', function(req, res){
|
||||
res.render('hello.jade', {}, function(err, str){
|
||||
assert.ok(!err);
|
||||
res.send(str.replace(':(', ':)'));
|
||||
});
|
||||
});
|
||||
app.get('/callback', function(req, res){
|
||||
res.render('hello.haml', { layout: false }, function(err, str){
|
||||
assert.ok(!err);
|
||||
@@ -65,6 +77,12 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/callback' },
|
||||
{ body: '\n<p>:)</p>' });
|
||||
assert.response(app,
|
||||
{ url: '/callback/layout' },
|
||||
{ body: '<html><body><p>:)</p></body></html>' });
|
||||
assert.response(app,
|
||||
{ url: '/callback/layout/no-options' },
|
||||
{ body: '<html><body><p>:)</p></body></html>' });
|
||||
assert.response(app,
|
||||
{ url: '/error' },
|
||||
{ body: 'doesNotExist' });
|
||||
@@ -99,11 +117,15 @@ module.exports = {
|
||||
{ body: '<html><body><p>Welcome</p></body></html>' });
|
||||
},
|
||||
|
||||
'test #render() specific layout': function(assert){
|
||||
var app = create();
|
||||
'test #render() specific layout': function(assert, beforeExit){
|
||||
var app = create(),
|
||||
called;
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('index.jade', { layout: 'cool-layout.jade' });
|
||||
res.render('index.jade', { layout: 'cool-layout.jade' }, function(err, html){
|
||||
called = true;
|
||||
res.send(html);
|
||||
});
|
||||
});
|
||||
app.get('/no-ext', function(req, res){
|
||||
res.render('index.jade', { layout: 'cool-layout' });
|
||||
@@ -114,6 +136,9 @@ module.exports = {
|
||||
app.get('/absolute', function(req, res){
|
||||
res.render('index.jade', { layout: __dirname + '/fixtures/layouts/foo.jade' });
|
||||
});
|
||||
app.get('/nope', function(req, res){
|
||||
res.render('index.jade', { layout: 'nope.jade' });
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
@@ -127,6 +152,16 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/absolute' },
|
||||
{ body: '<foo></foo>' });
|
||||
assert.response(app,
|
||||
{ url: '/nope' },
|
||||
function(res){
|
||||
assert.ok(res.body.indexOf('Error: ENOENT') >= 0);
|
||||
assert.ok(res.body.indexOf('nope.jade') >= 0);
|
||||
});
|
||||
|
||||
beforeExit(function(){
|
||||
assert.ok(called, 'Layout callback never called');
|
||||
});
|
||||
},
|
||||
|
||||
'test #render() specific layout "view engine"': function(assert){
|
||||
@@ -147,8 +182,8 @@ module.exports = {
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.internal = '1';
|
||||
res.method = function(){
|
||||
req.internal = '1';
|
||||
req.method = function(){
|
||||
return this.internal;
|
||||
};
|
||||
res.render('scope.jade', { layout: false });
|
||||
@@ -173,18 +208,56 @@ module.exports = {
|
||||
{ body: '<p>2</p>'});
|
||||
},
|
||||
|
||||
'test #render() view helpers': function(assert){
|
||||
'test #render() status': function(assert){
|
||||
var app = create();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('hello.jade', {
|
||||
layout: false,
|
||||
status: 404
|
||||
});
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<p>:(</p>', status: 404 });
|
||||
},
|
||||
|
||||
'test #render() headers': function(assert){
|
||||
var app = create();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('hello.jade', {
|
||||
layout: false,
|
||||
status: 500,
|
||||
headers: {
|
||||
'X-Foo': 'bar'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<p>:(</p>', status: 500, headers: {
|
||||
'X-Foo': 'bar',
|
||||
'Content-Type': 'text/html; charset=utf-8'
|
||||
}});
|
||||
},
|
||||
|
||||
'test #render() view helpers': function(assert, beforeExit){
|
||||
var app = create(),
|
||||
calls = 0;
|
||||
|
||||
app.helpers({
|
||||
lastName: 'holowaychuk',
|
||||
foo: function(){
|
||||
return 'bar';
|
||||
greetings: function(sess, lastName){
|
||||
return 'Hello ' + sess.name + ' ' + lastName;
|
||||
}
|
||||
});
|
||||
|
||||
var ret = app.dynamicHelpers({
|
||||
session: function(req, res){
|
||||
++calls;
|
||||
assert.equal('object', typeof req, 'Test dynamic helper req');
|
||||
assert.equal('object', typeof res, 'Test dynamic helper res');
|
||||
assert.ok(this instanceof express.Server, 'Test dynamic helper app scope');
|
||||
@@ -198,10 +271,32 @@ module.exports = {
|
||||
req.session = { name: 'tj' };
|
||||
res.render('dynamic-helpers.jade', { layout: false });
|
||||
});
|
||||
app.get('/ejs', function(req, res){
|
||||
req.session = { name: 'tj' };
|
||||
res.render('dynamic-helpers.ejs', { layout: false });
|
||||
});
|
||||
app.get('/precedence', function(req, res){
|
||||
req.session = { name: 'tj' };
|
||||
res.render('dynamic-helpers.jade', {
|
||||
locals: {
|
||||
lastName: 'foobar'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<p>tj holowaychuk bar</p>' });
|
||||
{ body: '<p>Hello tj holowaychuk</p>' });
|
||||
assert.response(app,
|
||||
{ url: '/ejs' },
|
||||
{ body: '<p>Hello tj holowaychuk</p>' });
|
||||
assert.response(app,
|
||||
{ url: '/precedence' },
|
||||
{ body: '<html><body><p>Hello tj foobar</p></body></html>' });
|
||||
|
||||
beforeExit(function(){
|
||||
assert.equal(3, calls);
|
||||
});
|
||||
},
|
||||
|
||||
'test #partial()': function(assert){
|
||||
@@ -339,5 +434,107 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/nothing' },
|
||||
{ body: 'Hello' });
|
||||
},
|
||||
|
||||
'test #partial() with array-like collection': function(assert){
|
||||
var app = create();
|
||||
|
||||
var movies = {
|
||||
0: { title: 'Nightmare Before Christmas', director: 'Tim Burton' },
|
||||
1: { title: 'Avatar', director: 'James Cameron' },
|
||||
length: 2
|
||||
};
|
||||
app.get('/movies', function(req, res){
|
||||
res.render('movies.jade', { locals: { movies: movies }});
|
||||
});
|
||||
|
||||
var html = [
|
||||
'<html>',
|
||||
'<body>',
|
||||
'<ul>',
|
||||
'<li>',
|
||||
'<div class="title">Nightmare Before Christmas</div>',
|
||||
'<div class="director">Tim Burton</div>',
|
||||
'</li>',
|
||||
'<li>',
|
||||
'<div class="title">Avatar</div>',
|
||||
'<div class="director">James Cameron</div>',
|
||||
'</li>',
|
||||
'</ul>',
|
||||
'</body>',
|
||||
'</html>'
|
||||
].join('');
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/movies' },
|
||||
{ body: html });
|
||||
},
|
||||
|
||||
'test "partials" setting': function(assert){
|
||||
var app = create();
|
||||
app.set('partials', __dirname + '/fixtures/sub-templates');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('items.jade', {
|
||||
layout: false,
|
||||
locals: {
|
||||
items: ['foo', 'bar']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<ul><li class="item">foo</li><li class="item">bar</li></ul>' });
|
||||
},
|
||||
|
||||
'test "view options"': function(assert){
|
||||
var app = create();
|
||||
|
||||
app.set('view options', {
|
||||
layout: false,
|
||||
open: '{{',
|
||||
close: '}}'
|
||||
});
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.render('user.ejs', {
|
||||
locals: {
|
||||
name: 'tj',
|
||||
email: 'tj@vision-media.ca'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/video', function(req, res, next){
|
||||
res.render('video.ejs', {
|
||||
open: '<?',
|
||||
close: '?>',
|
||||
locals: {
|
||||
title: 'keyboard cat'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<h1>tj</h1>\n<p>tj@vision-media.ca</p>' });
|
||||
assert.response(app,
|
||||
{ url: '/video' },
|
||||
{ body: '<h1>keyboard cat</h1>' });
|
||||
},
|
||||
|
||||
'test .register()': function(assert){
|
||||
var app = create();
|
||||
|
||||
app.register('.bar', require('jade'));
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.render('foo.bar', { layout: false });
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<p>This is actually jade :)</p>' });
|
||||
}
|
||||
};
|
||||
Referência em uma Nova Issue
Bloquear um usuário