mirror of
https://github.com/expressjs/express.git
synced 2026-02-26 18:57:43 +00:00
Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c04f85f93 | ||
|
|
77f885d4a0 | ||
|
|
c2fc9a83e8 | ||
|
|
d8b20bd2d5 | ||
|
|
d3c4fd91c9 | ||
|
|
00e44f6fc9 | ||
|
|
6692f911ab | ||
|
|
4f0b3f4684 | ||
|
|
cbf330c3db | ||
|
|
13010987b0 | ||
|
|
b078481cdb | ||
|
|
3888468a96 | ||
|
|
a6b70ceca4 | ||
|
|
45757a0f1a | ||
|
|
24a4a80ccd | ||
|
|
2fdd906a41 | ||
|
|
71cc6bac22 | ||
|
|
3a46660932 | ||
|
|
adca07a858 | ||
|
|
6b924bf1df | ||
|
|
eb88f160b6 | ||
|
|
01e6df759e | ||
|
|
1dc4e6fcb4 | ||
|
|
fff6951d94 | ||
|
|
1ebd49af75 | ||
|
|
8c2107e337 | ||
|
|
cddc5442f1 | ||
|
|
ac8cb270fe | ||
|
|
d54ee58f93 | ||
|
|
ce0fa0a3b2 | ||
|
|
e2f41147ed | ||
|
|
af3f7f0a65 | ||
|
|
a37003945b | ||
|
|
75361fb177 | ||
|
|
21f9b386db | ||
|
|
271cb16ecd | ||
|
|
803ec213d7 | ||
|
|
9e9042f1eb | ||
|
|
ec4c86f46d | ||
|
|
45f22ec602 | ||
|
|
0e1eb72058 | ||
|
|
4690b6cdf2 | ||
|
|
22204a5ce1 | ||
|
|
1ec16c0450 | ||
|
|
c72abc5293 | ||
|
|
93189ad0b6 | ||
|
|
c0aab36187 | ||
|
|
5ae994ee8f | ||
|
|
60d16eab77 | ||
|
|
fc60dfc1a6 | ||
|
|
45d149c146 | ||
|
|
4dfc1a69c3 | ||
|
|
0ae18bca60 | ||
|
|
2aaf0defe7 | ||
|
|
73ea5cd7ee | ||
|
|
e7f2d229ec | ||
|
|
87bc265817 | ||
|
|
796aaff295 | ||
|
|
8f87c50320 | ||
|
|
ee4471b345 | ||
|
|
6815feb8cf | ||
|
|
dbbe7be891 | ||
|
|
09c9452e5c | ||
|
|
b0e669ba00 | ||
|
|
f46ae9f3b2 | ||
|
|
9f2b344be8 | ||
|
|
6b47271679 | ||
|
|
6f7075be74 | ||
|
|
4b9cc3d698 | ||
|
|
3faa790b53 | ||
|
|
9477c9b516 | ||
|
|
b04be51848 | ||
|
|
9e4020efd3 | ||
|
|
6db19db665 | ||
|
|
1386f80ae5 | ||
|
|
e4342a7097 | ||
|
|
fda31b75f9 | ||
|
|
8ca0a45b33 | ||
|
|
ce2bcaef68 | ||
|
|
0db7f26ad3 | ||
|
|
35370da458 | ||
|
|
fe6c5832c2 | ||
|
|
e8c32df79c | ||
|
|
652e166462 | ||
|
|
af6385f8e4 | ||
|
|
f0277d3777 | ||
|
|
6bb100d7fa | ||
|
|
f13ea34de3 | ||
|
|
48a14a443a | ||
|
|
1820ea6f59 | ||
|
|
4d9647923e | ||
|
|
943e9b3a28 | ||
|
|
6b2ec50a0b | ||
|
|
7b813b95b6 | ||
|
|
cdaa2e78d7 | ||
|
|
add53d3222 | ||
|
|
f4f79d2217 | ||
|
|
aa36bc4516 | ||
|
|
9028cacfd1 | ||
|
|
40ccb595cd | ||
|
|
5606d08ecb | ||
|
|
1888d6fca1 | ||
|
|
5d16e6b302 | ||
|
|
96f7574bc1 | ||
|
|
490584c8bc | ||
|
|
0cbb1f661c | ||
|
|
3dc53e105a | ||
|
|
e2cdd760d8 | ||
|
|
4169202a41 | ||
|
|
835982c561 | ||
|
|
b67bacea18 | ||
|
|
3205ee7d75 | ||
|
|
ff7d5ff4e5 | ||
|
|
723774af27 | ||
|
|
c3fbd3fe10 | ||
|
|
d1d3871550 | ||
|
|
5462c8c7ec | ||
|
|
9536341e30 | ||
|
|
1bb798d963 | ||
|
|
91997e9c53 | ||
|
|
1393187040 | ||
|
|
6e69c880d9 | ||
|
|
59dcd03972 | ||
|
|
11482546a2 | ||
|
|
1ce43dd347 | ||
|
|
d1bfe137d4 | ||
|
|
9d7452cdc2 | ||
|
|
d9cee90efc | ||
|
|
175aa08500 | ||
|
|
c9ff6198d3 | ||
|
|
f026218c82 | ||
|
|
5bc86b9e29 | ||
|
|
5830ac9936 | ||
|
|
d7c6c9a9f9 | ||
|
|
9c87eed60e | ||
|
|
f15eb6d5ef | ||
|
|
5b33788359 | ||
|
|
11ec3ccd48 | ||
|
|
9d498ba3f1 | ||
|
|
15d4047180 | ||
|
|
44eae73843 | ||
|
|
d5b1c70731 | ||
|
|
e78dc18cfd | ||
|
|
4d122923e9 | ||
|
|
b1a7310263 | ||
|
|
d6ef90d98d | ||
|
|
85df59ac31 | ||
|
|
b789a28581 | ||
|
|
4068e7f444 | ||
|
|
80e9ffbf5d | ||
|
|
610fc92ca3 | ||
|
|
0f5dc9bdb2 | ||
|
|
c24b2faec5 | ||
|
|
c407f58dc2 | ||
|
|
380da0e202 | ||
|
|
9e5e7a1526 | ||
|
|
9016b5aaae |
112
History.md
112
History.md
@@ -1,4 +1,116 @@
|
||||
|
||||
2.4.7 / 2011-10-05
|
||||
==================
|
||||
|
||||
* Added mkdirp to express(1). Closes #795
|
||||
* Added simple _json-config_ example
|
||||
* Added shorthand for the parsed request's pathname via `req.path`
|
||||
* Changed connect dep to 1.7.x to fix npm issue...
|
||||
* Fixed `res.redirect()` __HEAD__ support. [reported by xerox]
|
||||
* Fixed `req.flash()`, only escape args
|
||||
* Fixed absolute path checking on windows. Closes #829 [reported by andrewpmckenzie]
|
||||
|
||||
2.4.6 / 2011-08-22
|
||||
==================
|
||||
|
||||
* Fixed multiple param callback regression. Closes #824 [reported by TroyGoode]
|
||||
|
||||
2.4.5 / 2011-08-19
|
||||
==================
|
||||
|
||||
* Added support for routes to handle errors. Closes #809
|
||||
* Added `app.routes.all()`. Closes #803
|
||||
* Added "basepath" setting to work in conjunction with reverse proxies etc.
|
||||
* Refactored `Route` to use a single array of callbacks
|
||||
* Added support for multiple callbacks for `app.param()`. Closes #801
|
||||
Closes #805
|
||||
* Changed: removed .call(self) for route callbacks
|
||||
* Dependency: `qs >= 0.3.1`
|
||||
* Fixed `res.redirect()` on windows due to `join()` usage. Closes #808
|
||||
|
||||
2.4.4 / 2011-08-05
|
||||
==================
|
||||
|
||||
* Fixed `res.header()` intention of a set, even when `undefined`
|
||||
* Fixed `*`, value no longer required
|
||||
* Fixed `res.send(204)` support. Closes #771
|
||||
|
||||
2.4.3 / 2011-07-14
|
||||
==================
|
||||
|
||||
* Added docs for `status` option special-case. Closes #739
|
||||
* Fixed `options.filename`, exposing the view path to template engines
|
||||
|
||||
2.4.2. / 2011-07-06
|
||||
==================
|
||||
|
||||
* Revert "removed jsonp stripping" for XSS
|
||||
|
||||
2.4.1 / 2011-07-06
|
||||
==================
|
||||
|
||||
* Added `res.json()` JSONP support. Closes #737
|
||||
* Added _extending-templates_ example. Closes #730
|
||||
* Added "strict routing" setting for trailing slashes
|
||||
* Added support for multiple envs in `app.configure()` calls. Closes #735
|
||||
* Changed: `res.send()` using `res.json()`
|
||||
* Changed: when cookie `path === null` don't default it
|
||||
* Changed; default cookie path to "home" setting. Closes #731
|
||||
* Removed _pids/logs_ creation from express(1)
|
||||
|
||||
2.4.0 / 2011-06-28
|
||||
==================
|
||||
|
||||
* Added chainable `res.status(code)`
|
||||
* Added `res.json()`, an explicit version of `res.send(obj)`
|
||||
* Added simple web-service example
|
||||
|
||||
2.3.12 / 2011-06-22
|
||||
==================
|
||||
|
||||
* \#express is now on freenode! come join!
|
||||
* Added `req.get(field, param)`
|
||||
* Added links to Japanese documentation, thanks @hideyukisaito!
|
||||
* Added; the `express(1)` generated app outputs the env
|
||||
* Added `content-negotiation` example
|
||||
* Dependency: connect >= 1.5.1 < 2.0.0
|
||||
* Fixed view layout bug. Closes #720
|
||||
* Fixed; ignore body on 304. Closes #701
|
||||
|
||||
2.3.11 / 2011-06-04
|
||||
==================
|
||||
|
||||
* Added `npm test`
|
||||
* Removed generation of dummy test file from `express(1)`
|
||||
* Fixed; `express(1)` adds express as a dep
|
||||
* Fixed; prune on `prepublish`
|
||||
|
||||
2.3.10 / 2011-05-27
|
||||
==================
|
||||
|
||||
* Added `req.route`, exposing the current route
|
||||
* Added _package.json_ generation support to `express(1)`
|
||||
* Fixed call to `app.param()` function for optional params. Closes #682
|
||||
|
||||
2.3.9 / 2011-05-25
|
||||
==================
|
||||
|
||||
* Fixed bug-ish with `../' in `res.partial()` calls
|
||||
|
||||
2.3.8 / 2011-05-24
|
||||
==================
|
||||
|
||||
* Fixed `app.options()`
|
||||
|
||||
2.3.7 / 2011-05-23
|
||||
==================
|
||||
|
||||
* Added route `Collection`, ex: `app.get('/user/:id').remove();`
|
||||
* Added support for `app.param(fn)` to define param logic
|
||||
* Removed `app.param()` support for callback with return value
|
||||
* Removed module.parent check from express(1) generated app. Closes #670
|
||||
* Refactored router. Closes #639
|
||||
|
||||
2.3.6 / 2011-05-20
|
||||
==================
|
||||
|
||||
|
||||
3
Makefile
3
Makefile
@@ -1,12 +1,13 @@
|
||||
|
||||
DOCS = $(shell find docs/*.md)
|
||||
HTMLDOCS =$(DOCS:.md=.html)
|
||||
TESTS = $(shell find test/*.test.js)
|
||||
|
||||
test:
|
||||
@NODE_ENV=test ./node_modules/.bin/expresso \
|
||||
-I lib \
|
||||
$(TESTFLAGS) \
|
||||
test/*.test.js
|
||||
$(TESTS)
|
||||
|
||||
test-cov:
|
||||
@TESTFLAGS=--cov $(MAKE) test
|
||||
|
||||
21
Readme.md
21
Readme.md
@@ -20,6 +20,23 @@ or to access the `express(1)` executable install globally:
|
||||
|
||||
$ npm install -g express
|
||||
|
||||
## Quick Start
|
||||
|
||||
The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
|
||||
|
||||
Create the app:
|
||||
|
||||
$ npm install -g express
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
|
||||
Install dependencies:
|
||||
|
||||
$ npm install -d
|
||||
|
||||
Start the server:
|
||||
|
||||
$ node app.js
|
||||
|
||||
## Features
|
||||
|
||||
* Robust routing
|
||||
@@ -58,13 +75,17 @@ The following are the major contributors of Express (in no specific order).
|
||||
|
||||
## More Information
|
||||
|
||||
* #express on freenode
|
||||
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js with ease
|
||||
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
|
||||
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
|
||||
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
|
||||
* [express-params](https://github.com/visionmedia/express-params) param pre-condition functions
|
||||
* [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results
|
||||
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
|
||||
* [Google Group](http://groups.google.com/group/express-js) for discussion
|
||||
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
|
||||
* [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito)
|
||||
* Screencast - [Introduction](http://bit.ly/eRYu0O)
|
||||
* Screencast - [View Partials](http://bit.ly/dU13Fx)
|
||||
* Screencast - [Route Specific Middleware](http://bit.ly/hX4IaH)
|
||||
|
||||
81
bin/express
81
bin/express
@@ -5,13 +5,14 @@
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, exec = require('child_process').exec;
|
||||
, exec = require('child_process').exec
|
||||
, mkdirp = require('mkdirp');
|
||||
|
||||
/**
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
var version = '2.3.6';
|
||||
var version = '2.4.6';
|
||||
|
||||
/**
|
||||
* Add session support.
|
||||
@@ -143,37 +144,10 @@ var sass = [
|
||||
|
||||
var stylus = [
|
||||
'body'
|
||||
, ' padding 50px'
|
||||
, ' font 14px "Lucida Grande", Helvetica, Arial, sans-serif'
|
||||
, ' padding: 50px'
|
||||
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif'
|
||||
, 'a'
|
||||
, ' color #00B7FF'
|
||||
].join('\n');
|
||||
|
||||
/**
|
||||
* App test template.
|
||||
*/
|
||||
|
||||
var appTest = [
|
||||
""
|
||||
, "// Run $ expresso"
|
||||
, ""
|
||||
, "/**"
|
||||
, " * Module dependencies."
|
||||
, " */"
|
||||
, ""
|
||||
, "var app = require('../app')"
|
||||
, " , assert = require('assert');"
|
||||
, "",
|
||||
, "module.exports = {"
|
||||
, " 'GET /': function(){"
|
||||
, " assert.response(app,"
|
||||
, " { url: '/' },"
|
||||
, " { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }},"
|
||||
, " function(res){"
|
||||
, " assert.includes(res.body, '<title>Express</title>');"
|
||||
, " });"
|
||||
, " }"
|
||||
, "};"
|
||||
, ' color: #00B7FF'
|
||||
].join('\n');
|
||||
|
||||
/**
|
||||
@@ -217,12 +191,8 @@ var app = [
|
||||
, ' });'
|
||||
, '});'
|
||||
, ''
|
||||
, '// Only listen on $ node app.js'
|
||||
, ''
|
||||
, 'if (!module.parent) {'
|
||||
, ' app.listen(3000);'
|
||||
, ' console.log("Express server listening on port %d", app.address().port);'
|
||||
, '}'
|
||||
, 'app.listen(3000);'
|
||||
, 'console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);'
|
||||
, ''
|
||||
].join('\n');
|
||||
|
||||
@@ -291,8 +261,6 @@ while (args.length) {
|
||||
|
||||
function createApplicationAt(path) {
|
||||
mkdir(path, function(){
|
||||
mkdir(path + '/pids');
|
||||
mkdir(path + '/logs');
|
||||
mkdir(path + '/public/javascripts');
|
||||
mkdir(path + '/public/images');
|
||||
mkdir(path + '/public/stylesheets', function(){
|
||||
@@ -322,9 +290,6 @@ function createApplicationAt(path) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
mkdir(path + '/test', function(){
|
||||
write(path + '/test/app.test.js', appTest);
|
||||
});
|
||||
|
||||
// CSS Engine support
|
||||
switch (cssEngine) {
|
||||
@@ -347,19 +312,21 @@ function createApplicationAt(path) {
|
||||
// Template support
|
||||
app = app.replace(':TEMPLATE', templateEngine);
|
||||
|
||||
write(path + '/app.js', app);
|
||||
// package.json
|
||||
var json = '{\n';
|
||||
json += ' "name": "application-name"\n';
|
||||
json += ' , "version": "0.0.1"\n';
|
||||
json += ' , "private": true\n';
|
||||
json += ' , "dependencies": {\n';
|
||||
json += ' "express": "' + version + '"\n';
|
||||
if (cssEngine) json += ' , "' + cssEngine + '": ">= 0.0.1"\n';
|
||||
if (templateEngine) json += ' , "' + templateEngine + '": ">= 0.0.1"\n';
|
||||
json += ' }\n';
|
||||
json += '}';
|
||||
|
||||
// Suggestions
|
||||
process.on('exit', function(){
|
||||
if (cssEngine) {
|
||||
console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
|
||||
, cssEngine
|
||||
, cssEngine);
|
||||
}
|
||||
console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
|
||||
, templateEngine
|
||||
, templateEngine);
|
||||
});
|
||||
|
||||
write(path + '/package.json', json);
|
||||
write(path + '/app.js', app);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -432,9 +399,9 @@ function prompt(msg, fn) {
|
||||
*/
|
||||
|
||||
function mkdir(path, fn) {
|
||||
exec('mkdir -p ' + path, function(err){
|
||||
mkdirp(path, 0755, function(err){
|
||||
if (err) throw err;
|
||||
console.log(' \x1b[36mcreate\x1b[0m : ' + path);
|
||||
console.log(' \033[36mcreate\033[0m : ' + path);
|
||||
fn && fn();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-25235225-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-25235225-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
@@ -192,10 +206,9 @@
|
||||
</ul>
|
||||
<h3>Development Dependencies</h3>
|
||||
|
||||
<p>Express development dependencies are stored within the <em>./support</em> directory. To
|
||||
update them execute:</p>
|
||||
<p>First install the dev dependencies by executing the following command in the repo’s directory:</p>
|
||||
|
||||
<pre><code>$ git submodule update --init
|
||||
<pre><code>$ npm install
|
||||
</code></pre>
|
||||
|
||||
<h3>Running Tests</h3>
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-25235225-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
|
||||
112
docs/guide.html
112
docs/guide.html
@@ -1,7 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-25235225-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
@@ -202,6 +216,7 @@
|
||||
<li><a href="#req.accepts()">accepts()</a></li>
|
||||
<li><a href="#req.is()">is()</a></li>
|
||||
<li><a href="#req.param()">param()</a></li>
|
||||
<li><a href="#req.get()">get()</a></li>
|
||||
<li><a href="#req.flash()">flash()</a></li>
|
||||
<li><a href="#req.isxmlhttprequest">isXMLHttpRequest</a></li>
|
||||
</ul></li>
|
||||
@@ -213,6 +228,7 @@
|
||||
<li><a href="#res.sendfile()">sendfile()</a></li>
|
||||
<li><a href="#res.download()">download()</a></li>
|
||||
<li><a href="#res.send()">send()</a></li>
|
||||
<li><a href="#res.json()">json()</a></li>
|
||||
<li><a href="#res.redirect()">redirect()</a></li>
|
||||
<li><a href="#res.cookie()">cookie()</a></li>
|
||||
<li><a href="#res.clearcookie()">clearCookie()</a></li>
|
||||
@@ -255,6 +271,31 @@
|
||||
<pre><code>$ npm install express
|
||||
</code></pre>
|
||||
|
||||
<p>or to access the <code>express(1)</code> executable install globally:</p>
|
||||
|
||||
<pre><code>$ npm install -g express
|
||||
</code></pre>
|
||||
|
||||
<h2>Quick Start</h2>
|
||||
|
||||
<p> The quickest way to get started with express is to utilize the executable <code>express(1)</code> to generate an application as shown below:</p>
|
||||
|
||||
<p> Create the app:</p>
|
||||
|
||||
<pre><code>$ npm install -g express
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
</code></pre>
|
||||
|
||||
<p> Install dependencies:</p>
|
||||
|
||||
<pre><code>$ npm install -d
|
||||
</code></pre>
|
||||
|
||||
<p> Start the server:</p>
|
||||
|
||||
<pre><code>$ node app.js
|
||||
</code></pre>
|
||||
|
||||
<h3 id="creating-a server">Creating A Server</h3>
|
||||
|
||||
<p> To create an instance of the <em>express.HTTPServer</em>, simply invoke the <em>createServer()</em> method. With our instance <em>app</em> we can then define routes based on the HTTP verbs, in this example <em>app.get()</em>.</p>
|
||||
@@ -306,6 +347,13 @@ app.configure('production', function(){
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>For similar environments you may also pass several env strings:</p>
|
||||
|
||||
<pre><code>app.configure('stage', 'prod', function(){
|
||||
// config
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>For internal and arbitrary settings Express provides the <em>set(key[, val])</em>, <em>enable(key)</em>, <em>disable(key)</em> methods:</p>
|
||||
|
||||
<pre><code> app.configure(function(){
|
||||
@@ -336,12 +384,14 @@ app.configure('production', function(){
|
||||
<p>Express supports the following settings out of the box:</p>
|
||||
|
||||
<ul>
|
||||
<li><em>home</em> Application base path used for <em>res.redirect()</em> and transparently handling mounted apps.</li>
|
||||
<li><em>basepath</em> Application base path used for <em>res.redirect()</em> and transparently handling mounted apps.</li>
|
||||
<li><em>views</em> Root views directory defaulting to <strong>CWD/views</strong></li>
|
||||
<li><em>view engine</em> Default view engine name for views rendered without extensions</li>
|
||||
<li><em>view options</em> An object specifying global view options</li>
|
||||
<li><em>view cache</em> Enable view caching (enabled in production)</li>
|
||||
<li><em>case sensitive routes</em> Enable case-sensitive routing</li>
|
||||
<li><em>strict routing</em> When enabled trailing slashes are no longer ignored</li>
|
||||
<li><em>jsonp callback</em> Enable <em>res.send()</em> / <em>res.json()</em> transparent jsonp support</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -505,7 +555,7 @@ var app = express.createServer(
|
||||
|
||||
<p>Alternatively we can <em>use()</em> them which is useful when adding middleware within <em>configure()</em> blocks, in a progressive manor.</p>
|
||||
|
||||
<pre><code>app.use(express.logger({ format: ':method :uri' }));
|
||||
<pre><code>app.use(express.logger({ format: ':method :url' }));
|
||||
</code></pre>
|
||||
|
||||
<p>Typically with connect middleware you would <em>require(‘connect’)</em> like so:</p>
|
||||
@@ -772,16 +822,6 @@ is present, which is useful for developing apps that rely heavily on client-side
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to <em>next(err)</em>.</p>
|
||||
|
||||
<pre><code>app.param('number', function(n){ return parseInt(n, 10); });
|
||||
</code></pre>
|
||||
|
||||
<p>We may also apply the same callback to several placeholders, for example a route GET <em>/commits/:from-:to</em> are both numbers, so we may define them as an array:</p>
|
||||
|
||||
<pre><code>app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
|
||||
</code></pre>
|
||||
|
||||
<h3 id="view-rendering">View Rendering</h3>
|
||||
|
||||
<p>View filenames take the form “<name>.<engine>”, where <engine> is the name
|
||||
@@ -916,14 +956,14 @@ app.use(express.session({ secret: "keyboard cat" }));
|
||||
|
||||
<p>By default the <em>session</em> middleware uses the memory store bundled with Connect, however many implementations exist. For example <a href="http://github.com/visionmedia/connect-redis">connect-redis</a> supplies a <a href="http://code.google.com/p/redis/">Redis</a> session store and can be used as shown below:</p>
|
||||
|
||||
<pre><code>var RedisStore = require('connect-redis');
|
||||
<pre><code>var RedisStore = require('connect-redis')(express);
|
||||
app.use(express.cookieParser());
|
||||
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
|
||||
</code></pre>
|
||||
|
||||
<p>Now the <em>req.session</em> and <em>req.sessionStore</em> properties will be accessible to all routes and subsequent middleware. Properties on <em>req.session</em> are automatically saved on a response, so for example if we wish to shopping cart data:</p>
|
||||
|
||||
<pre><code>var RedisStore = require('connect-redis');
|
||||
<pre><code>var RedisStore = require('connect-redis')(express);
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.cookieParser());
|
||||
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
|
||||
@@ -1067,6 +1107,18 @@ can perform any request assertion you wish.</p>
|
||||
should be an object. This can be done by using
|
||||
the _express.bodyParser middleware.</p>
|
||||
|
||||
<h3 id="req.get()">req.get(field, param)</h3>
|
||||
|
||||
<p> Get <em>field</em>’s <em>param</em> value, defaulting to ‘’ when the <em>param</em>
|
||||
or <em>field</em> is not present.</p>
|
||||
|
||||
<pre><code> req.get('content-disposition', 'filename');
|
||||
// => "something.png"
|
||||
|
||||
req.get('Content-Type', 'boundary');
|
||||
// => "--foo-bar-baz"
|
||||
</code></pre>
|
||||
|
||||
<h3 id="req.flash()">req.flash(type[, msg])</h3>
|
||||
|
||||
<p>Queue flash <em>msg</em> of the given <em>type</em>.</p>
|
||||
@@ -1237,6 +1289,19 @@ it will not be set again.</p>
|
||||
|
||||
<p>Note that this method <em>end()</em>s the response, so you will want to use node’s <em>res.write()</em> for multiple writes or streaming.</p>
|
||||
|
||||
<h3 id="res.json()">res.json(obj[, headers|status[, status]])</h3>
|
||||
|
||||
<p> Send a JSON response with optional <em>headers</em> and <em>status</em>. This method
|
||||
is ideal for JSON-only APIs, however <em>res.send(obj)</em> will send JSON as
|
||||
well, though not ideal for cases when you want to send for example a string
|
||||
as JSON, since the default for <em>res.send(string)</em> is text/html.</p>
|
||||
|
||||
<pre><code>res.json(null);
|
||||
res.json({ user: 'tj' });
|
||||
res.json('oh noes!', 500);
|
||||
res.json('I dont have that', 404);
|
||||
</code></pre>
|
||||
|
||||
<h3 id="res.redirect()">res.redirect(url[, status])</h3>
|
||||
|
||||
<p>Redirect to the given <em>url</em> with a default response <em>status</em> of 302.</p>
|
||||
@@ -1250,11 +1315,12 @@ res.redirect('back');
|
||||
|
||||
<p>Express supports “redirect mapping”, which by default provides <em>home</em>, and <em>back</em>.
|
||||
The <em>back</em> map checks the <em>Referrer</em> and <em>Referer</em> headers, while <em>home</em> utilizes
|
||||
the “home” setting and defaults to “/”.</p>
|
||||
the “basepath” setting and defaults to “/”.</p>
|
||||
|
||||
<h3 id="res.cookie()">res.cookie(name, val[, options])</h3>
|
||||
|
||||
<p>Sets the given cookie <em>name</em> to <em>val</em>, with options <em>httpOnly</em>, <em>secure</em>, <em>expires</em> etc.</p>
|
||||
<p>Sets the given cookie <em>name</em> to <em>val</em>, with options <em>httpOnly</em>, <em>secure</em>, <em>expires</em> etc. The <em>path</em> option defaults to the app’s “basepath” setting, which
|
||||
is typically “/”.</p>
|
||||
|
||||
<pre><code>// "Remember me" for 15 minutes
|
||||
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
|
||||
@@ -1276,7 +1342,8 @@ app.get('/', function(req, res){
|
||||
|
||||
<h3 id="res.clearcookie()">res.clearCookie(name[, options])</h3>
|
||||
|
||||
<p>Clear cookie <em>name</em> by setting “expires” far in the past.</p>
|
||||
<p>Clear cookie <em>name</em> by setting “expires” far in the past. Much like
|
||||
<em>res.cookie()</em> the <em>path</em> option also defaults to the “basepath” setting.</p>
|
||||
|
||||
<pre><code>res.clearCookie('rememberme');
|
||||
</code></pre>
|
||||
@@ -1293,6 +1360,16 @@ automatically, however otherwise a response of <em>200</em> and <em>text/html</e
|
||||
res.render('index', { layout: false, user: user });
|
||||
</code></pre>
|
||||
|
||||
<p>This <em>options</em> object is also considered an “options” object. For example
|
||||
when you pass the <em>status</em> local, it’s not only available to the view, it
|
||||
sets the response status to this number. This is also useful if a template
|
||||
engine accepts specific options, such as <em>debug</em>, or <em>compress</em>. Below
|
||||
is an example of how one might render an error page, passing the <em>status</em> for
|
||||
display, as well as it setting <em>res.statusCode</em>.</p>
|
||||
|
||||
<pre><code> res.render('error', { status: 500, message: 'Internal Server Error' });
|
||||
</code></pre>
|
||||
|
||||
<h3 id="res.partial()">res.partial(view[, options])</h3>
|
||||
|
||||
<p>Render <em>view</em> partial with the given <em>options</em>. This method is always available
|
||||
@@ -1565,7 +1642,6 @@ as well as the <em>name()</em> function exposed.</p>
|
||||
<p>Express also provides a few locals by default:</p>
|
||||
|
||||
<pre><code>- `settings` the app's settings object
|
||||
- `filename` the view's filename
|
||||
- `layout(path)` specify the layout from within a view
|
||||
</code></pre>
|
||||
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
|
||||
### Installation
|
||||
|
||||
$ npm install express
|
||||
|
||||
or to access the `express(1)` executable install globally:
|
||||
|
||||
$ npm install -g express
|
||||
|
||||
## Quick Start
|
||||
|
||||
The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
|
||||
|
||||
Create the app:
|
||||
|
||||
$ npm install -g express
|
||||
$ express /tmp/foo && cd /tmp/foo
|
||||
|
||||
Install dependencies:
|
||||
|
||||
$ npm install -d
|
||||
|
||||
Start the server:
|
||||
|
||||
$ node app.js
|
||||
|
||||
### Creating A Server
|
||||
|
||||
To create an instance of the _express.HTTPServer_, simply invoke the _createServer()_ method. With our instance _app_ we can then define routes based on the HTTP verbs, in this example _app.get()_.
|
||||
@@ -51,6 +71,12 @@ otherwise the first call to _app.get()_, _app.post()_, etc will mount the routes
|
||||
app.use(express.errorHandler());
|
||||
});
|
||||
|
||||
For similar environments you may also pass several env strings:
|
||||
|
||||
app.configure('stage', 'prod', function(){
|
||||
// config
|
||||
});
|
||||
|
||||
For internal and arbitrary settings Express provides the _set(key[, val])_, _enable(key)_, _disable(key)_ methods:
|
||||
|
||||
app.configure(function(){
|
||||
@@ -78,12 +104,14 @@ This is _very_ important, as many caching mechanisms are _only enabled_ when in
|
||||
|
||||
Express supports the following settings out of the box:
|
||||
|
||||
* _home_ Application base path used for _res.redirect()_ and transparently handling mounted apps.
|
||||
* _basepath_ Application base path used for _res.redirect()_ and transparently handling mounted apps.
|
||||
* _views_ Root views directory defaulting to **CWD/views**
|
||||
* _view engine_ Default view engine name for views rendered without extensions
|
||||
* _view options_ An object specifying global view options
|
||||
* _view cache_ Enable view caching (enabled in production)
|
||||
* _case sensitive routes_ Enable case-sensitive routing
|
||||
* _strict routing_ When enabled trailing slashes are no longer ignored
|
||||
* _jsonp callback_ Enable _res.send()_ / _res.json()_ transparent jsonp support
|
||||
|
||||
### Routing
|
||||
|
||||
@@ -236,7 +264,7 @@ passed to _express.createServer()_ as you would with a regular Connect server. F
|
||||
|
||||
Alternatively we can _use()_ them which is useful when adding middleware within _configure()_ blocks, in a progressive manor.
|
||||
|
||||
app.use(express.logger({ format: ':method :uri' }));
|
||||
app.use(express.logger({ format: ':method :url' }));
|
||||
|
||||
Typically with connect middleware you would _require('connect')_ like so:
|
||||
|
||||
@@ -481,14 +509,6 @@ Doing so, as mentioned drastically improves our route readability, and allows us
|
||||
res.send('user ' + req.user.name);
|
||||
});
|
||||
|
||||
For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to _next(err)_.
|
||||
|
||||
app.param('number', function(n){ return parseInt(n, 10); });
|
||||
|
||||
We may also apply the same callback to several placeholders, for example a route GET _/commits/:from-:to_ are both numbers, so we may define them as an array:
|
||||
|
||||
app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
|
||||
|
||||
### View Rendering
|
||||
|
||||
View filenames take the form "<name>.<engine>", where <engine> is the name
|
||||
@@ -603,13 +623,13 @@ Sessions support can be added by using Connect's _session_ middleware. To do so
|
||||
|
||||
By default the _session_ middleware uses the memory store bundled with Connect, however many implementations exist. For example [connect-redis](http://github.com/visionmedia/connect-redis) supplies a [Redis](http://code.google.com/p/redis/) session store and can be used as shown below:
|
||||
|
||||
var RedisStore = require('connect-redis');
|
||||
var RedisStore = require('connect-redis')(express);
|
||||
app.use(express.cookieParser());
|
||||
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
|
||||
|
||||
Now the _req.session_ and _req.sessionStore_ properties will be accessible to all routes and subsequent middleware. Properties on _req.session_ are automatically saved on a response, so for example if we wish to shopping cart data:
|
||||
|
||||
var RedisStore = require('connect-redis');
|
||||
var RedisStore = require('connect-redis')(express);
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.cookieParser());
|
||||
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
|
||||
@@ -741,6 +761,17 @@ To utilize urlencoded request bodies, _req.body_
|
||||
should be an object. This can be done by using
|
||||
the _express.bodyParser middleware.
|
||||
|
||||
### req.get(field, param)
|
||||
|
||||
Get _field_'s _param_ value, defaulting to '' when the _param_
|
||||
or _field_ is not present.
|
||||
|
||||
req.get('content-disposition', 'filename');
|
||||
// => "something.png"
|
||||
|
||||
req.get('Content-Type', 'boundary');
|
||||
// => "--foo-bar-baz"
|
||||
|
||||
### req.flash(type[, msg])
|
||||
|
||||
Queue flash _msg_ of the given _type_.
|
||||
@@ -892,6 +923,18 @@ it will not be set again.
|
||||
|
||||
Note that this method _end()_s the response, so you will want to use node's _res.write()_ for multiple writes or streaming.
|
||||
|
||||
### res.json(obj[, headers|status[, status]])
|
||||
|
||||
Send a JSON response with optional _headers_ and _status_. This method
|
||||
is ideal for JSON-only APIs, however _res.send(obj)_ will send JSON as
|
||||
well, though not ideal for cases when you want to send for example a string
|
||||
as JSON, since the default for _res.send(string)_ is text/html.
|
||||
|
||||
res.json(null);
|
||||
res.json({ user: 'tj' });
|
||||
res.json('oh noes!', 500);
|
||||
res.json('I dont have that', 404);
|
||||
|
||||
### res.redirect(url[, status])
|
||||
|
||||
Redirect to the given _url_ with a default response _status_ of 302.
|
||||
@@ -904,11 +947,12 @@ Redirect to the given _url_ with a default response _status_ of 302.
|
||||
|
||||
Express supports "redirect mapping", which by default provides _home_, and _back_.
|
||||
The _back_ map checks the _Referrer_ and _Referer_ headers, while _home_ utilizes
|
||||
the "home" setting and defaults to "/".
|
||||
the "basepath" setting and defaults to "/".
|
||||
|
||||
### res.cookie(name, val[, options])
|
||||
|
||||
Sets the given cookie _name_ to _val_, with options _httpOnly_, _secure_, _expires_ etc.
|
||||
Sets the given cookie _name_ to _val_, with options _httpOnly_, _secure_, _expires_ etc. The _path_ option defaults to the app's "basepath" setting, which
|
||||
is typically "/".
|
||||
|
||||
// "Remember me" for 15 minutes
|
||||
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
|
||||
@@ -927,7 +971,8 @@ To parse incoming _Cookie_ headers, use the _cookieParser_ middleware, which pro
|
||||
|
||||
### res.clearCookie(name[, options])
|
||||
|
||||
Clear cookie _name_ by setting "expires" far in the past.
|
||||
Clear cookie _name_ by setting "expires" far in the past. Much like
|
||||
_res.cookie()_ the _path_ option also defaults to the "basepath" setting.
|
||||
|
||||
res.clearCookie('rememberme');
|
||||
|
||||
@@ -942,6 +987,15 @@ The _options_ passed are the local variables as well, for example if we want to
|
||||
var user = { name: 'tj' };
|
||||
res.render('index', { layout: false, user: user });
|
||||
|
||||
This _options_ object is also considered an "options" object. For example
|
||||
when you pass the _status_ local, it's not only available to the view, it
|
||||
sets the response status to this number. This is also useful if a template
|
||||
engine accepts specific options, such as _debug_, or _compress_. Below
|
||||
is an example of how one might render an error page, passing the _status_ for
|
||||
display, as well as it setting _res.statusCode_.
|
||||
|
||||
res.render('error', { status: 500, message: 'Internal Server Error' });
|
||||
|
||||
### res.partial(view[, options])
|
||||
|
||||
Render _view_ partial with the given _options_. This method is always available
|
||||
@@ -1183,7 +1237,6 @@ as well as the _name()_ function exposed.
|
||||
Express also provides a few locals by default:
|
||||
|
||||
- `settings` the app's settings object
|
||||
- `filename` the view's filename
|
||||
- `layout(path)` specify the layout from within a view
|
||||
|
||||
This method is aliased as _app.locals()_.
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-25235225-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
@@ -232,26 +246,27 @@ app.listen(3000);
|
||||
|
||||
<h2>Third-Party Modules</h2>
|
||||
|
||||
<p>The following modules compliment or extend Express directly:</p>
|
||||
<p>The following modules complement or extend Express directly:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://github.com/visionmedia/express-resource">express-resource</a> provides resourceful routing</li>
|
||||
<li><a href="http://github.com/visionmedia/express-messages">express-messages</a> flash message notification rendering</li>
|
||||
<li><a href="http://github.com/visionmedia/express-configuration">express-configure</a> async configuration support (load settings from redis etc)</li>
|
||||
<li><a href="http://github.com/visionmedia/express-namespace">express-namespace</a> namespaced routing support</li>
|
||||
<li><a href="http://github.com/visionmedia/express-expose">express-expose</a> expose objects, functions, modules and more to client-side js</li>
|
||||
<li><a href="https://github.com/visionmedia/express-params">express-params</a> app.param() extensions</li>
|
||||
<li><a href="https://github.com/LearnBoost/express-mongoose">express-mongoose</a> plugin for easy rendering of Mongoose async Query results</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h2>More Information</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://groups.google.com/group/express-js">Google Group</a> for discussion</li>
|
||||
<li>#express on freenode</li>
|
||||
<li>Follow <a href="http://twitter.com/tjholowaychuk">tjholowaychuk</a> on twitter for updates</li>
|
||||
<li>View the <a href="http://senchalabs.github.com/connect">Connect</a> documentation</li>
|
||||
<li>View the <a href="http://wiki.github.com/senchalabs/connect/">Connect Wiki</a> for contrib middleware</li>
|
||||
<li>View the <a href="http://github.com/visionmedia/express/tree/master/examples/">examples</a></li>
|
||||
<li>View the <a href="http://github.com/visionmedia/express">source</a></li>
|
||||
<li>View the <a href="contrib.html">contrib guide</a></li>
|
||||
<li><a href="http://groups.google.com/group/express-js">Google Group</a> for discussion</li>
|
||||
<li>Visit the <a href="http://github.com/visionmedia/express/wiki">Wiki</a></li>
|
||||
<li><a href="http://hideyukisaito.com/doc/expressjs/">日本語ドキュメンテーション</a> by <a href="https://github.com/hideyukisaito">hideyukisaito</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -34,19 +34,20 @@ The following are the major contributors of Express (in no specific order).
|
||||
|
||||
## Third-Party Modules
|
||||
|
||||
The following modules compliment or extend Express directly:
|
||||
The following modules complement or extend Express directly:
|
||||
|
||||
* [express-resource](http://github.com/visionmedia/express-resource) provides resourceful routing
|
||||
* [express-messages](http://github.com/visionmedia/express-messages) flash message notification rendering
|
||||
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support (load settings from redis etc)
|
||||
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced routing support
|
||||
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js
|
||||
* [express-params](https://github.com/visionmedia/express-params) app.param() extensions
|
||||
* [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results
|
||||
|
||||
## More Information
|
||||
|
||||
* [Google Group](http://groups.google.com/group/express-js) for discussion
|
||||
* \#express on freenode
|
||||
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
|
||||
* View the [Connect](http://senchalabs.github.com/connect) documentation
|
||||
* View the [Connect Wiki](http://wiki.github.com/senchalabs/connect/) for contrib middleware
|
||||
* View the [examples](http://github.com/visionmedia/express/tree/master/examples/)
|
||||
* View the [source](http://github.com/visionmedia/express)
|
||||
* View the [contrib guide](contrib.html)
|
||||
* [Google Group](http://groups.google.com/group/express-js) for discussion
|
||||
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
|
||||
* [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito)
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-25235225-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-25235225-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
@@ -326,7 +340,7 @@
|
||||
|
||||
<h3>partial() locals</h3>
|
||||
|
||||
<p> Both <em>res.partial()</em> and the <em>partial()</em> functions accept an single object consisting of both the options and the locals. Previously with Express 1.x you may pass <em>user</em> to a partial, along with <em>date</em> like so:</p>
|
||||
<p> Both <em>res.partial()</em> and the <em>partial()</em> functions accept a single object consisting of both the options and the locals. Previously with Express 1.x you may pass <em>user</em> to a partial, along with <em>date</em> like so:</p>
|
||||
|
||||
<pre><code> partial('user', { object: user, locals: { date: new Date }})
|
||||
</code></pre>
|
||||
@@ -367,7 +381,7 @@
|
||||
|
||||
<h3>View Partial Lookup</h3>
|
||||
|
||||
<p> Previously partials were loaded relative to the now removed <em>view partials</em> directory setting, or by default <em>views/partials</em>, now they are relative to the view calling them, read more on <a href="guide.html#View-Lookup">view lookup</a>.</p>
|
||||
<p> Previously partials were loaded relative to the now removed <em>view partials</em> directory setting, or by default <em>views/partials</em>, now they are relative to the view calling them, read more on <a href="guide.html#view-lookup">view lookup</a>.</p>
|
||||
|
||||
<h3>Mime Types</h3>
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ However now we have the alternative _maxAge_ property which may be used to set _
|
||||
|
||||
### partial() locals
|
||||
|
||||
Both _res.partial()_ and the _partial()_ functions accept an single object consisting of both the options and the locals. Previously with Express 1.x you may pass _user_ to a partial, along with _date_ like so:
|
||||
Both _res.partial()_ and the _partial()_ functions accept a single object consisting of both the options and the locals. Previously with Express 1.x you may pass _user_ to a partial, along with _date_ like so:
|
||||
|
||||
partial('user', { object: user, locals: { date: new Date }})
|
||||
|
||||
@@ -157,7 +157,7 @@ or perhaps if you preferred not to use the inferred name _user_ you may used a l
|
||||
|
||||
### View Partial Lookup
|
||||
|
||||
Previously partials were loaded relative to the now removed _view partials_ directory setting, or by default _views/partials_, now they are relative to the view calling them, read more on [view lookup](guide.html#View-Lookup).
|
||||
Previously partials were loaded relative to the now removed _view partials_ directory setting, or by default _views/partials_, now they are relative to the view calling them, read more on [view lookup](guide.html#view-lookup).
|
||||
|
||||
### Mime Types
|
||||
|
||||
@@ -174,4 +174,4 @@ or perhaps if you preferred not to use the inferred name _user_ you may used a l
|
||||
Previously when using options the `root` option would be used for this:
|
||||
|
||||
app.use(express.staticProvider({ root: __dirname + '/public', maxAge: oneYear }));
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Express - node web framework</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-25235225-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#tagline {
|
||||
margin-left: 75px;
|
||||
|
||||
@@ -35,16 +35,15 @@ var users = {
|
||||
tj: {
|
||||
name: 'tj'
|
||||
, salt: 'randomly-generated-salt'
|
||||
, pass: md5('foobar' + 'randomly-generated-salt')
|
||||
, pass: hash('foobar', 'randomly-generated-salt')
|
||||
}
|
||||
};
|
||||
|
||||
// Used to generate a hash of the plain-text password + salt
|
||||
|
||||
function md5(str) {
|
||||
return crypto.createHash('md5').update(str).digest('hex');
|
||||
function hash(msg, key) {
|
||||
return crypto.createHmac('sha256', key).update(msg).digest('hex');
|
||||
}
|
||||
|
||||
// Authenticate using our plain-object database of doom!
|
||||
|
||||
function authenticate(name, pass, fn) {
|
||||
@@ -52,9 +51,9 @@ function authenticate(name, pass, fn) {
|
||||
// query the db for the given username
|
||||
if (!user) return fn(new Error('cannot find user'));
|
||||
// apply the same algorithm to the POSTed password, applying
|
||||
// the md5 against the pass / salt, if there is a match we
|
||||
// the hash against the pass / salt, if there is a match we
|
||||
// found the user
|
||||
if (user.pass == md5(pass + user.salt)) return fn(null, user);
|
||||
if (user.pass == hash(pass, user.salt)) return fn(null, user);
|
||||
// Otherwise password is invalid
|
||||
fn(new Error('invalid password'));
|
||||
}
|
||||
|
||||
47
examples/content-negotiation/app.js
Normal file
47
examples/content-negotiation/app.js
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
var users = [
|
||||
{ name: 'tobi' }
|
||||
, { name: 'loki' }
|
||||
, { name: 'jane' }
|
||||
];
|
||||
|
||||
function provides(type) {
|
||||
return function(req, res, next){
|
||||
if (req.accepts(type)) return next();
|
||||
next('route');
|
||||
}
|
||||
}
|
||||
|
||||
// curl http://localhost:3000/users -H "Accept: application/json"
|
||||
|
||||
app.get('/users', provides('json'), function(req, res){
|
||||
res.send(users);
|
||||
});
|
||||
|
||||
// curl http://localhost:3000/users -H "Accept: text/html"
|
||||
|
||||
app.get('/users', provides('html'), function(req, res){
|
||||
res.send('<ul>' + users.map(function(user){
|
||||
return '<li>' + user.name + '</li>';
|
||||
}).join('\n') + '</ul>');
|
||||
});
|
||||
|
||||
// curl http://localhost:3000/users -H "Accept: text/plain"
|
||||
|
||||
app.get('/users', function(req, res, next){
|
||||
res.contentType('txt');
|
||||
res.send(users.map(function(user){
|
||||
return user.name;
|
||||
}).join(', '));
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express server listening on port 3000');
|
||||
@@ -11,7 +11,7 @@ var app = express.createServer(
|
||||
express.favicon(),
|
||||
|
||||
// Custom logger format
|
||||
express.logger({ format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time' }),
|
||||
express.logger({ format: '\x1b[36m:method\x1b[0m \x1b[90m:url\x1b[0m :response-time' }),
|
||||
|
||||
// Provides req.cookies
|
||||
express.cookieParser(),
|
||||
@@ -36,9 +36,8 @@ app.get('/forget', function(req, res){
|
||||
});
|
||||
|
||||
app.post('/', function(req, res){
|
||||
if (req.body.remember) {
|
||||
res.cookie('remember', '1', { path: '/', expires: new Date(Date.now() + 900000), httpOnly: true });
|
||||
}
|
||||
var minute = 60000;
|
||||
if (req.body.remember) res.cookie('remember', 1, { maxAge: minute });
|
||||
res.redirect('back');
|
||||
});
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ require.paths.unshift(__dirname + '/../../support');
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
// Serve default connect favicon
|
||||
app.use(express.favicon());
|
||||
|
||||
// Logger is placed below favicon, so favicon.ico
|
||||
// requests will not be logged
|
||||
app.use(express.logger({ format: '":method :url" :status' }));
|
||||
app.use(express.logger('":method :url" :status'));
|
||||
|
||||
// "app.router" positions our routes
|
||||
// specifically above the middleware
|
||||
@@ -23,59 +25,38 @@ app.use(express.logger({ format: '":method :url" :status' }));
|
||||
|
||||
app.use(app.router);
|
||||
|
||||
// When no more middleware require execution, aka
|
||||
// our router is finished and did not respond, we
|
||||
// can assume that it is "not found". Instead of
|
||||
// letting Connect deal with this, we define our
|
||||
// custom middleware here to simply pass a NotFound
|
||||
// exception
|
||||
// Since this is the last non-error-handling
|
||||
// middleware use()d, we assume 404, as nothing else
|
||||
// responded.
|
||||
|
||||
app.use(function(req, res, next){
|
||||
next(new NotFound(req.url));
|
||||
// the status option, or res.statusCode = 404
|
||||
// are equivalent, however with the option we
|
||||
// get the "status" local available as well
|
||||
res.render('404', { status: 404, url: req.url });
|
||||
});
|
||||
|
||||
app.set('views', __dirname + '/views');
|
||||
// error-handling middleware, take the same form
|
||||
// as regular middleware, however they require an
|
||||
// arity of 4, aka the signature (err, req, res, next).
|
||||
// when connect has an error, it will invoke ONLY error-handling
|
||||
// middleware.
|
||||
|
||||
// Provide our app with the notion of NotFound exceptions
|
||||
// If we were to next() here any remaining non-error-handling
|
||||
// middleware would then be executed, or if we next(err) to
|
||||
// continue passing the error, only error-handling middleware
|
||||
// would remain being executed, however here
|
||||
// we simply respond with an error page.
|
||||
|
||||
function NotFound(path){
|
||||
this.name = 'NotFound';
|
||||
if (path) {
|
||||
Error.call(this, 'Cannot find ' + path);
|
||||
this.path = path;
|
||||
} else {
|
||||
Error.call(this, 'Not Found');
|
||||
}
|
||||
Error.captureStackTrace(this, arguments.callee);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `Error.prototype`.
|
||||
*/
|
||||
|
||||
NotFound.prototype.__proto__ = Error.prototype;
|
||||
|
||||
// We can call app.error() several times as shown below.
|
||||
// Here we check for an instanceof NotFound and show the
|
||||
// 404 page, or we pass on to the next error handler.
|
||||
|
||||
// These handlers could potentially be defined within
|
||||
// configure() blocks to provide introspection when
|
||||
// in the development environment.
|
||||
|
||||
app.error(function(err, req, res, next){
|
||||
if (err instanceof NotFound) {
|
||||
res.render('404.jade', { status: 404, error: err });
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Here we assume all errors as 500 for the simplicity of
|
||||
// this demo, however you can choose whatever you like
|
||||
|
||||
app.error(function(err, req, res){
|
||||
res.render('500.jade', { status: 500, error: err });
|
||||
app.use(function(err, req, res, next){
|
||||
// we may use properties of the error object
|
||||
// here and next(err) appropriately, or if
|
||||
// we possibly recovered from the error, simply next().
|
||||
res.render('500', {
|
||||
status: err.status || 500
|
||||
, error: err
|
||||
});
|
||||
});
|
||||
|
||||
// Routes
|
||||
@@ -84,8 +65,14 @@ app.get('/', function(req, res){
|
||||
res.render('index.jade');
|
||||
});
|
||||
|
||||
app.get('/404', function(req, res){
|
||||
throw new NotFound(req.url);
|
||||
app.get('/404', function(req, res, next){
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/403', function(req, res, next){
|
||||
var err = new Error('not allowed!');
|
||||
err.status = 403;
|
||||
next(err);
|
||||
});
|
||||
|
||||
app.get('/500', function(req, res, next){
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
- if (error.path)
|
||||
h2 Cannot find #{error.path}
|
||||
- else
|
||||
h2 Page Not Found
|
||||
h2 Cannot find #{url}
|
||||
@@ -5,4 +5,7 @@ ul
|
||||
a(href="/500") 500
|
||||
li
|
||||
| visit
|
||||
a(href="/404") 404
|
||||
a(href="/404") 404
|
||||
li
|
||||
| visit
|
||||
a(href='/403') 403
|
||||
66
examples/extending-templates/app.js
Normal file
66
examples/extending-templates/app.js
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
// Register ejs as .html
|
||||
|
||||
app.register('.html', require('ejs'));
|
||||
|
||||
// Optional since express defaults to CWD/views
|
||||
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'html');
|
||||
|
||||
// Dummy users
|
||||
var users = [
|
||||
{ name: 'tj', email: 'tj@sencha.com' }
|
||||
, { name: 'ciaran', email: 'ciaranj@gmail.com' }
|
||||
, { name: 'aaron', email: 'aaron.heckmann+github@gmail.com' }
|
||||
];
|
||||
|
||||
// dynamic helpers are simply functions that are invoked
|
||||
// per request (once), passed both the request and response
|
||||
// objects. These can be used for request-specific
|
||||
// details within a view, such telling the layout which
|
||||
// scripts to include.
|
||||
app.dynamicHelpers({
|
||||
// by simply returning an object here
|
||||
// we can set it's properties such as "page.title"
|
||||
// within a view, and it remains specific to that request,
|
||||
// so it would be valid to do:
|
||||
// page.title = user.name + "'s account"
|
||||
page: function() {
|
||||
return {};
|
||||
},
|
||||
|
||||
// the scripts array here is assigned once,
|
||||
// so by returning a closure, we can use script(path)
|
||||
// in a template, instead of something like
|
||||
// scripts.push(path).
|
||||
script: function(req){
|
||||
req._scripts = [];
|
||||
return function(path){
|
||||
req._scripts.push(path);
|
||||
}
|
||||
},
|
||||
|
||||
// to expose our scripts array for iteration within
|
||||
// our views (typically the layout), we simply return it
|
||||
// here, and since composite types are mutable, it will
|
||||
// contain all of the paths pushed with the helper above.
|
||||
scripts: function(req){
|
||||
return req._scripts;
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('users', { users: users });
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
11
examples/extending-templates/views/layout.html
Normal file
11
examples/extending-templates/views/layout.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<title><%- page.title %></title>
|
||||
<% for (var i in scripts) { %>
|
||||
<script src="<%= scripts[i] %>"></script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
8
examples/extending-templates/views/users/index.html
Normal file
8
examples/extending-templates/views/users/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<% page.title = 'Users' %>
|
||||
<% script('/javascripts/jquery.js') %>
|
||||
<% script('/javascripts/users.js') %>
|
||||
|
||||
<h1>Users</h1>
|
||||
<ul id="users">
|
||||
<%- partial('user', users) %>
|
||||
</ul>
|
||||
1
examples/extending-templates/views/users/user.html
Normal file
1
examples/extending-templates/views/users/user.html
Normal file
@@ -0,0 +1 @@
|
||||
<li><%= user.name %> <<%= user.email %>></li>
|
||||
@@ -13,6 +13,7 @@ var pub = __dirname + '/public';
|
||||
// and then serve with connect's staticProvider
|
||||
|
||||
var app = express.createServer();
|
||||
app.use(express.logger('dev'));
|
||||
app.use(express.compiler({ src: pub, enable: ['sass'] }));
|
||||
app.use(app.router);
|
||||
app.use(express.static(pub));
|
||||
|
||||
3
examples/jade/views/header.jade
Normal file
3
examples/jade/views/header.jade
Normal file
@@ -0,0 +1,3 @@
|
||||
head
|
||||
title Jade Example
|
||||
link(rel="stylesheet", href="/stylesheets/style.css")
|
||||
@@ -1,6 +1,4 @@
|
||||
!!!
|
||||
html
|
||||
head
|
||||
title Jade Example
|
||||
link(rel="stylesheet", href="/stylesheets/style.css")
|
||||
include header
|
||||
body!= body
|
||||
26
examples/json-config/app.js
Normal file
26
examples/json-config/app.js
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../');
|
||||
|
||||
app = express.createServer();
|
||||
|
||||
// load the config for this environment (NODE_ENV)
|
||||
|
||||
var config = require('./config')[app.settings.env];
|
||||
|
||||
// apply settings
|
||||
|
||||
for (var key in config) app.set(key, config[key]);
|
||||
|
||||
// apply middleware
|
||||
|
||||
config.middleware.forEach(app.use.bind(app));
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('index', { layout: false });
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
19
examples/json-config/config.js
Normal file
19
examples/json-config/config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
// ok so it's not JSON, but close enough :)
|
||||
|
||||
var express = require('../../');
|
||||
|
||||
exports.development = {
|
||||
'view engine': 'jade'
|
||||
, 'views': __dirname + '/views'
|
||||
, 'title': 'My Site'
|
||||
, 'middleware': [
|
||||
express.logger('dev')
|
||||
, app.router
|
||||
, express.static(__dirname + '/public')
|
||||
]
|
||||
};
|
||||
|
||||
exports.production = {
|
||||
|
||||
};
|
||||
4
examples/json-config/views/index.jade
Normal file
4
examples/json-config/views/index.jade
Normal file
@@ -0,0 +1,4 @@
|
||||
html
|
||||
body
|
||||
h1 #{settings.title}
|
||||
p Simple example
|
||||
@@ -5,21 +5,26 @@
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
// $ npm install connect-redis
|
||||
var RedisStore = require('connect-redis');
|
||||
// pass the express to the connect redis module
|
||||
// allowing it to inherit from express.session.Store
|
||||
var RedisStore = require('connect-redis')(express);
|
||||
|
||||
var app = express.createServer(
|
||||
express.logger(),
|
||||
var app = express.createServer();
|
||||
|
||||
// Required by session() middleware
|
||||
express.cookieParser(),
|
||||
app.use(express.favicon());
|
||||
|
||||
// Populates:
|
||||
// - req.session
|
||||
// - req.sessionStore
|
||||
// - req.sessionID (or req.session.id)
|
||||
express.session({ secret: 'keyboard cat', store: new RedisStore })
|
||||
);
|
||||
// request logging
|
||||
app.use(express.logger());
|
||||
|
||||
// required to parse the session cookie
|
||||
app.use(express.cookieParser());
|
||||
|
||||
// Populates:
|
||||
// - req.session
|
||||
// - req.sessionStore
|
||||
// - req.sessionID (or req.session.id)
|
||||
|
||||
app.use(express.session({ secret: 'keyboard cat', store: new RedisStore }));
|
||||
|
||||
app.get('/', function(req, res){
|
||||
var body = '';
|
||||
|
||||
121
examples/web-service/app.js
Normal file
121
examples/web-service/app.js
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
// configuration
|
||||
|
||||
// if we wanted to supply more than JSON, we could
|
||||
// use something similar to the content-negotiation
|
||||
// example.
|
||||
|
||||
// here we validate the API key,
|
||||
// by mounting this middleware to /api/v1
|
||||
// meaning only paths prefixed with "/api/v1"
|
||||
// will cause this middleware to be invoked
|
||||
|
||||
app.use('/api/v1', function(req, res, next){
|
||||
var key = req.query['api-key'];
|
||||
|
||||
// key isnt present
|
||||
if (!key) return next(new Error('api key required'));
|
||||
|
||||
// key is invalid
|
||||
if (!~apiKeys.indexOf(key)) return next(new Error('invalid api key'));
|
||||
|
||||
// all good, store req.key for route access
|
||||
req.key = key;
|
||||
next();
|
||||
});
|
||||
|
||||
// position our routes above the error handling middleware,
|
||||
// and below our API middleware, since we want the API validation
|
||||
// to take place BEFORE our routes
|
||||
app.use(app.router);
|
||||
|
||||
// middleware with an arity of 4 are considered
|
||||
// error handling middleware. When you next(err)
|
||||
// it will be passed through the defined middleware
|
||||
// in order, but ONLY those with an arity of 4, ignoring
|
||||
// regular middleware.
|
||||
app.use(function(err, req, res, next){
|
||||
// whatever you want here, feel free to populate
|
||||
// properties on `err` to treat it differently in here,
|
||||
// or when you next(err) set res.statusCode= etc.
|
||||
res.send({ error: err.message }, 500);
|
||||
});
|
||||
|
||||
// our custom JSON 404 middleware. Since it's placed last
|
||||
// it will be the last middleware called, if all others
|
||||
// invoke next() and do not respond.
|
||||
app.use(function(req, res){
|
||||
res.send({ error: "Lame, can't find that" }, 404);
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate our unique identifier.
|
||||
*/
|
||||
|
||||
function uid() {
|
||||
return [
|
||||
Math.random() * 0xffff | 0
|
||||
, Math.random() * 0xffff | 0
|
||||
, Math.random() * 0xffff | 0
|
||||
, Date.now()
|
||||
].join('-');
|
||||
}
|
||||
|
||||
// map of valid api keys, typically mapped to
|
||||
// account info with some sort of database like redis.
|
||||
// api keys do _not_ serve as authentication, merely to
|
||||
// track API usage or help prevent malicious behavior etc.
|
||||
|
||||
var apiKeys = [uid(), uid(), uid()];
|
||||
|
||||
console.log('valid keys:\n ', apiKeys.join('\n '));
|
||||
|
||||
// these two objects will serve as our faux database
|
||||
|
||||
var repos = [
|
||||
{ name: 'express', url: 'http://github.com/visionmedia/express' }
|
||||
, { name: 'stylus', url: 'http://github.com/learnboost/stylus' }
|
||||
, { name: 'cluster', url: 'http://github.com/learnboost/cluster' }
|
||||
];
|
||||
|
||||
var users = [
|
||||
{ name: 'tobi' }
|
||||
, { name: 'loki' }
|
||||
, { name: 'jane' }
|
||||
];
|
||||
|
||||
var userRepos = {
|
||||
tobi: [repos[0], repos[1]]
|
||||
, loki: [repos[1]]
|
||||
, jane: [repos[2]]
|
||||
};
|
||||
|
||||
// we now can assume the api key is valid,
|
||||
// and simply expose the data
|
||||
|
||||
app.get('/api/v1/users', function(req, res, next){
|
||||
res.send(users);
|
||||
});
|
||||
|
||||
app.get('/api/v1/repos', function(req, res, next){
|
||||
res.send(repos);
|
||||
});
|
||||
|
||||
app.get('/api/v1/user/:name/repos', function(req, res, next){
|
||||
var name = req.params.name
|
||||
, user = userRepos[name];
|
||||
|
||||
if (user) res.send(user);
|
||||
else next();
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express server listening on port 3000');
|
||||
@@ -28,7 +28,7 @@ var exports = module.exports = connect.middleware;
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
exports.version = '2.3.6';
|
||||
exports.version = '2.4.7';
|
||||
|
||||
/**
|
||||
* Shortcut for `new Server(...)`.
|
||||
|
||||
269
lib/http.js
269
lib/http.js
@@ -12,8 +12,10 @@
|
||||
var qs = require('qs')
|
||||
, connect = require('connect')
|
||||
, router = require('./router')
|
||||
, methods = router.methods.concat('del', 'all')
|
||||
, Router = require('./router')
|
||||
, view = require('./view')
|
||||
, toArray = require('./utils').toArray
|
||||
, methods = router.methods.concat('del', 'all')
|
||||
, url = require('url')
|
||||
, utils = connect.utils;
|
||||
|
||||
@@ -64,7 +66,6 @@ app.init = function(middleware){
|
||||
this.dynamicViewHelpers = {};
|
||||
this.errorHandlers = [];
|
||||
|
||||
this.set('home', '/');
|
||||
this.set('env', process.env.NODE_ENV || 'development');
|
||||
|
||||
// expose objects to each other
|
||||
@@ -86,11 +87,11 @@ app.init = function(middleware){
|
||||
// apply middleware
|
||||
if (middleware) middleware.forEach(self.use.bind(self));
|
||||
|
||||
// use router, expose as app.get(), etc
|
||||
var fn = router(function(app){ self.routes = app; }, this);
|
||||
// router
|
||||
this.routes = new Router(this);
|
||||
this.__defineGetter__('router', function(){
|
||||
this.__usedRouter = true;
|
||||
return fn;
|
||||
return self.routes.middleware;
|
||||
});
|
||||
|
||||
// default locals
|
||||
@@ -113,38 +114,62 @@ app.init = function(middleware){
|
||||
// so that they disregard definition position.
|
||||
this.on('listening', this.registerErrorHandlers.bind(this));
|
||||
|
||||
// route lookup methods
|
||||
this.remove = function(url){
|
||||
return self.remove.all(url);
|
||||
};
|
||||
|
||||
this.match = function(url){
|
||||
return self.match.all(url);
|
||||
};
|
||||
|
||||
this.lookup = function(url){
|
||||
return self.lookup.all(url);
|
||||
};
|
||||
|
||||
// route manipulation methods
|
||||
methods.forEach(function(method){
|
||||
self.match[method] = function(url){
|
||||
return self.router.match(url, 'all' == method
|
||||
? null
|
||||
: method);
|
||||
};
|
||||
|
||||
self.remove[method] = function(url){
|
||||
return self.router.remove(url, 'all' == method
|
||||
? null
|
||||
: method);
|
||||
};
|
||||
|
||||
self.lookup[method] = function(path){
|
||||
return self.router.lookup(path, 'all' == method
|
||||
? null
|
||||
: method);
|
||||
return self.routes.lookup(method, path);
|
||||
};
|
||||
|
||||
self.match[method] = function(path){
|
||||
return self.routes.match(method, path);
|
||||
};
|
||||
|
||||
self.remove[method] = function(path){
|
||||
return self.routes.lookup(method, path).remove();
|
||||
};
|
||||
});
|
||||
|
||||
// del -> delete
|
||||
self.lookup.del = self.lookup.delete;
|
||||
self.match.del = self.match.delete;
|
||||
self.remove.del = self.remove.delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove routes matching the given `path`.
|
||||
*
|
||||
* @param {Route} path
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.remove = function(path){
|
||||
return this.routes.lookup('all', path).remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup routes defined with a path
|
||||
* equivalent to `path`.
|
||||
*
|
||||
* @param {Stirng} path
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.lookup = function(path){
|
||||
return this.routes.lookup('all', path);
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup routes matching the given `url`.
|
||||
*
|
||||
* @param {Stirng} url
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.match = function(url){
|
||||
return this.routes.match('all', url);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -182,7 +207,7 @@ app.registerErrorHandlers = function(){
|
||||
*/
|
||||
|
||||
app.use = function(route, middleware){
|
||||
var app, home, handle;
|
||||
var app, base, handle;
|
||||
|
||||
if ('string' != typeof route) {
|
||||
middleware = route, route = '/';
|
||||
@@ -208,9 +233,10 @@ app.use = function(route, middleware){
|
||||
// mounted an app, invoke the hook
|
||||
// and adjust some settings
|
||||
if (app) {
|
||||
home = app.set('home');
|
||||
if ('/' == home) home = '';
|
||||
app.set('home', app.route + home);
|
||||
base = this.set('basepath') || this.route;
|
||||
if ('/' == base) base = '';
|
||||
base = base + (app.set('basepath') || app.route);
|
||||
app.set('basepath', base);
|
||||
app.parent = this;
|
||||
if (app.__mounted) app.__mounted.call(app, this);
|
||||
}
|
||||
@@ -286,57 +312,86 @@ app.dynamicHelpers = function(obj){
|
||||
};
|
||||
|
||||
/**
|
||||
* Map the given param placeholder `name`(s) to the given callback `fn`.
|
||||
* Map the given param placeholder `name`(s) to the given callback(s).
|
||||
*
|
||||
* Param mapping is used to provide pre-conditions to routes
|
||||
* which us normalized placeholders. For example ":user_id" may
|
||||
* attempt to load the user from the database, where as ":num" may
|
||||
* pass the value through `parseInt(num, 10)`.
|
||||
* which us normalized placeholders. This callback has the same
|
||||
* signature as regular middleware, for example below when ":userId"
|
||||
* is used this function will be invoked in an attempt to load the user.
|
||||
*
|
||||
* When the callback function accepts only a single argument, the
|
||||
* value of placeholder is passed:
|
||||
* app.param('userId', function(req, res, next, id){
|
||||
* User.find(id, function(err, user){
|
||||
* if (err) {
|
||||
* next(err);
|
||||
* } else if (user) {
|
||||
* req.user = user;
|
||||
* next();
|
||||
* } else {
|
||||
* next(new Error('failed to load user'));
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* app.param('page', function(n){ return parseInt(n, 10); });
|
||||
* Passing a single function allows you to map logic
|
||||
* to the values passed to `app.param()`, for example
|
||||
* this is useful to provide coercion support in a concise manner.
|
||||
*
|
||||
* After which "/users/:page" would automatically provide us with
|
||||
* an integer for `req.params.page`. If desired we could use the callback
|
||||
* signature shown below, and immediately `next(new Error('invalid page'))`
|
||||
* when `parseInt` fails.
|
||||
* The following example maps regular expressions to param values
|
||||
* ensuring that they match, otherwise passing control to the next
|
||||
* route:
|
||||
*
|
||||
* Alternatively the callback may accept the request, response, next, and
|
||||
* the value, acting like middlware:
|
||||
* app.param(function(name, regexp){
|
||||
* if (regexp instanceof RegExp) {
|
||||
* return function(req, res, next, val){
|
||||
* var captures;
|
||||
* if (captures = regexp.exec(String(val))) {
|
||||
* req.params[name] = captures;
|
||||
* next();
|
||||
* } else {
|
||||
* next('route');
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* app.param('userId', function(req, res, next, id){
|
||||
* User.find(id, function(err, user){
|
||||
* if (err) {
|
||||
* next(err);
|
||||
* } else if (user) {
|
||||
* req.user = user;
|
||||
* next();
|
||||
* } else {
|
||||
* next(new Error('failed to load user'));
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
* We can now use it as shown below, where "/commit/:commit" expects
|
||||
* that the value for ":commit" is at 5 or more digits. The capture
|
||||
* groups are then available as `req.params.commit` as we defined
|
||||
* in the function above.
|
||||
*
|
||||
* Now every time ":userId" is present, the associated user object
|
||||
* will be loaded and assigned before the route handler is invoked.
|
||||
* app.param('commit', /^\d{5,}$/);
|
||||
*
|
||||
* @param {String|Array} name
|
||||
* For more of this useful functionality take a look
|
||||
* at [express-params](http://github.com/visionmedia/express-params).
|
||||
*
|
||||
* @param {String|Array|Function} name
|
||||
* @param {Function} fn
|
||||
* @return {Server} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.param = function(name, fn){
|
||||
var self = this
|
||||
, fns = [].slice.call(arguments, 1);
|
||||
|
||||
// array
|
||||
if (Array.isArray(name)) {
|
||||
name.forEach(function(name){
|
||||
this.param(name, fn);
|
||||
}, this);
|
||||
fns.forEach(function(fn){
|
||||
self.param(name, fn);
|
||||
});
|
||||
});
|
||||
// param logic
|
||||
} else if ('function' == typeof name) {
|
||||
this.routes.param(name);
|
||||
// single
|
||||
} else {
|
||||
if (':' == name[0]) name = name.substr(1);
|
||||
this.routes.param(name, fn);
|
||||
fns.forEach(function(fn){
|
||||
self.routes.param(name, fn);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -455,46 +510,74 @@ app.redirect = function(key, url){
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure callback for the given `env`.
|
||||
* Configure callback for zero or more envs,
|
||||
* when no env is specified that callback will
|
||||
* be invoked for all environments. Any combination
|
||||
* can be used multiple times, in any order desired.
|
||||
*
|
||||
* @param {String} env
|
||||
* Examples:
|
||||
*
|
||||
* app.configure(function(){
|
||||
* // executed for all envs
|
||||
* });
|
||||
*
|
||||
* app.configure('stage', function(){
|
||||
* // executed staging env
|
||||
* });
|
||||
*
|
||||
* app.configure('stage', 'production', function(){
|
||||
* // executed for stage and production
|
||||
* });
|
||||
*
|
||||
* @param {String} env...
|
||||
* @param {Function} fn
|
||||
* @return {Server} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.configure = function(env, fn){
|
||||
if ('function' == typeof env) {
|
||||
fn = env, env = 'all';
|
||||
}
|
||||
if ('all' == env || env == this.settings.env) {
|
||||
fn.call(this);
|
||||
}
|
||||
var envs = 'all'
|
||||
, args = toArray(arguments);
|
||||
fn = args.pop();
|
||||
if (args.length) envs = args;
|
||||
if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
|
||||
return this;
|
||||
};
|
||||
|
||||
// Generate routing methods
|
||||
/**
|
||||
* Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
|
||||
*/
|
||||
|
||||
methods.forEach(function(method){
|
||||
app[method] = function(path){
|
||||
var self = this;
|
||||
|
||||
// Lookup
|
||||
if (1 == arguments.length) {
|
||||
return this.router.lookup(path, 'all' == method
|
||||
? null
|
||||
: method);
|
||||
}
|
||||
|
||||
// Ensure router is mounted
|
||||
if (1 == arguments.length) return this.routes.lookup(method, path);
|
||||
var args = [method].concat(toArray(arguments));
|
||||
if (!this.__usedRouter) this.use(this.router);
|
||||
|
||||
// Generate the route
|
||||
this.routes[method].apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
return this.routes._route.apply(this.routes, args);
|
||||
}
|
||||
});
|
||||
|
||||
// Alias delete as "del"
|
||||
/**
|
||||
* Special-cased "all" method, applying the given route `path`,
|
||||
* middleware, and callback to _every_ HTTP method.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Function} ...
|
||||
* @return {Server} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.all = function(path){
|
||||
var args = arguments;
|
||||
if (1 == args.length) return this.routes.lookup('all', path);
|
||||
methods.forEach(function(method){
|
||||
if ('all' == method) return;
|
||||
app[method].apply(this, args);
|
||||
}, this);
|
||||
return this;
|
||||
};
|
||||
|
||||
// del -> delete alias
|
||||
|
||||
app.del = app.delete;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
var http = require('http')
|
||||
, req = http.IncomingMessage.prototype
|
||||
, utils = require('./utils')
|
||||
, parse = require('url').parse
|
||||
, mime = require('mime');
|
||||
|
||||
/**
|
||||
@@ -65,6 +66,39 @@ req.header = function(name, defaultValue){
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get `field`'s `param` value, defaulting to ''.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* req.get('content-disposition', 'filename');
|
||||
* // => "something.png"
|
||||
*
|
||||
* @param {String} field
|
||||
* @param {String} param
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.get = function(field, param){
|
||||
var val = this.header(field);
|
||||
if (!val) return '';
|
||||
var regexp = new RegExp(param + ' *= *(?:"([^"]+)"|([^;]+))', 'i');
|
||||
if (!regexp.exec(val)) return '';
|
||||
return RegExp.$1 || RegExp.$2;
|
||||
};
|
||||
|
||||
/**
|
||||
* Short-hand for `require('url').parse(req.url).pathname`.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
req.__defineGetter__('path', function(){
|
||||
return parse(this.url).pathname;
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if the _Accept_ header is present, and includes the given `type`.
|
||||
*
|
||||
@@ -106,16 +140,14 @@ req.accepts = function(type){
|
||||
return true;
|
||||
} else if (type) {
|
||||
// allow "html" vs "text/html" etc
|
||||
if (type.indexOf('/') < 0) {
|
||||
type = mime.lookup(type);
|
||||
}
|
||||
if (!~type.indexOf('/')) type = mime.lookup(type);
|
||||
|
||||
// check if we have a direct match
|
||||
if (~accept.indexOf(type)) return true;
|
||||
|
||||
// check if we have type/*
|
||||
type = type.split('/')[0] + '/*';
|
||||
return accept.indexOf(type) >= 0;
|
||||
return !!~accept.indexOf(type);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -197,10 +229,10 @@ req.flash = function(type, msg){
|
||||
, args = arguments
|
||||
, formatters = this.app.flashFormatters || {};
|
||||
formatters.__proto__ = flashFormatters;
|
||||
msg = utils.miniMarkdown(utils.escape(msg));
|
||||
msg = utils.miniMarkdown(msg);
|
||||
msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
|
||||
var formatter = formatters[format];
|
||||
if (formatter) return formatter(args[i++]);
|
||||
if (formatter) return formatter(utils.escape(args[i++]));
|
||||
});
|
||||
return (msgs[type] = msgs[type] || []).push(msg);
|
||||
} else if (type) {
|
||||
|
||||
@@ -52,7 +52,7 @@ res.send = function(body, headers, status){
|
||||
status = status || this.statusCode;
|
||||
|
||||
// allow 0 args as 204
|
||||
if (!arguments.length || undefined === body) body = status = 204;
|
||||
if (!arguments.length || undefined === body) status = 204;
|
||||
|
||||
// determine content type
|
||||
switch (typeof body) {
|
||||
@@ -75,22 +75,13 @@ res.send = function(body, headers, status){
|
||||
this.contentType('.bin');
|
||||
}
|
||||
} else {
|
||||
if (!this.header('Content-Type')) {
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.contentType('.json');
|
||||
}
|
||||
body = JSON.stringify(body);
|
||||
if (this.req.query.callback && this.app.set('jsonp callback')) {
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.header('Content-Type', 'text/javascript');
|
||||
body = this.req.query.callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
|
||||
}
|
||||
return this.json(body, headers, status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// populate Content-Length
|
||||
if (!this.header('Content-Length')) {
|
||||
if (undefined !== body && !this.header('Content-Length')) {
|
||||
this.header('Content-Length', Buffer.isBuffer(body)
|
||||
? body.length
|
||||
: Buffer.byteLength(body));
|
||||
@@ -106,14 +97,62 @@ res.send = function(body, headers, status){
|
||||
}
|
||||
|
||||
// strip irrelevant headers
|
||||
if (204 === status) {
|
||||
if (204 == status || 304 == status) {
|
||||
this.removeHeader('Content-Type');
|
||||
this.removeHeader('Content-Length');
|
||||
body = '';
|
||||
}
|
||||
|
||||
// respond
|
||||
this.statusCode = status;
|
||||
this.end('HEAD' == this.req.method ? undefined : body);
|
||||
this.end('HEAD' == this.req.method ? null : body);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send JSON response with `obj`, optional `headers`, and optional `status`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* res.json(null);
|
||||
* res.json({ user: 'tj' });
|
||||
* res.json('oh noes!', 500);
|
||||
* res.json('I dont have that', 404);
|
||||
*
|
||||
* @param {Mixed} obj
|
||||
* @param {Object|Number} headers or status
|
||||
* @param {Number} status
|
||||
* @return {ServerResponse}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.json = function(obj, headers, status){
|
||||
var body = JSON.stringify(obj)
|
||||
, callback = this.req.query.callback
|
||||
, jsonp = this.app.enabled('jsonp callback');
|
||||
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.header('Content-Type', 'application/json');
|
||||
|
||||
if (callback && jsonp) {
|
||||
this.header('Content-Type', 'text/javascript');
|
||||
body = callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
|
||||
}
|
||||
|
||||
return this.send(body, headers, status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set status `code`.
|
||||
*
|
||||
* @param {Number} code
|
||||
* @return {ServerResponse}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.status = function(code){
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -190,7 +229,7 @@ res.attachment = function(filename){
|
||||
* Transfer the file at the given `path`, with optional
|
||||
* `filename` as an attachment and optional callback `fn(err)`,
|
||||
* and optional `fn2(err)` which is invoked when an error has
|
||||
* occurred after headers have been sent.
|
||||
* occurred after header has been sent.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {String|Function} filename or fn
|
||||
@@ -232,17 +271,14 @@ res.download = function(path, filename, fn, fn2){
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String} val
|
||||
* @return {String}
|
||||
* @return {ServerResponse} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.header = function(name, val){
|
||||
if (val === undefined) {
|
||||
return this.getHeader(name);
|
||||
} else {
|
||||
this.setHeader(name, val);
|
||||
return val;
|
||||
}
|
||||
if (1 == arguments.length) return this.getHeader(name);
|
||||
this.setHeader(name, val);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -266,6 +302,7 @@ res.clearCookie = function(name, options){
|
||||
* Options:
|
||||
*
|
||||
* - `maxAge` max-age in milliseconds, converted to `expires`
|
||||
* - `path` defaults to the "basepath" setting which is typically "/"
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
@@ -284,6 +321,7 @@ res.clearCookie = function(name, options){
|
||||
res.cookie = function(name, val, options){
|
||||
options = options || {};
|
||||
if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge);
|
||||
if (undefined === options.path) options.path = this.app.set('basepath');
|
||||
var cookie = utils.serializeCookie(name, val, options);
|
||||
this.header('Set-Cookie', cookie);
|
||||
};
|
||||
@@ -295,8 +333,8 @@ res.cookie = function(name, val, options){
|
||||
* The given `url` can also be the name of a mapped url, for
|
||||
* example by default express supports "back" which redirects
|
||||
* to the _Referrer_ or _Referer_ headers or the application's
|
||||
* "home" setting. Express also supports "home" out of the box,
|
||||
* which can be set via `app.set('home', '/blog');`, and defaults
|
||||
* "basepath" setting. Express also supports "basepath" out of the box,
|
||||
* which can be set via `app.set('basepath', '/blog');`, and defaults
|
||||
* to '/'.
|
||||
*
|
||||
* Redirect Mapping:
|
||||
@@ -336,8 +374,9 @@ res.cookie = function(name, val, options){
|
||||
res.redirect = function(url, status){
|
||||
var app = this.app
|
||||
, req = this.req
|
||||
, base = app.set('home') || '/'
|
||||
, base = app.set('basepath') || app.route
|
||||
, status = status || 302
|
||||
, head = 'HEAD' == req.method
|
||||
, body;
|
||||
|
||||
// Setup redirect map
|
||||
@@ -360,16 +399,13 @@ res.redirect = function(url, status){
|
||||
// Relative
|
||||
if (!~url.indexOf('://')) {
|
||||
// Respect mount-point
|
||||
if (app.route) {
|
||||
url = join(app.route, url);
|
||||
}
|
||||
if ('/' != base && 0 != url.indexOf(base)) url = base + url;
|
||||
|
||||
// Absolute
|
||||
var host = req.headers.host
|
||||
, tls = req.connection.encrypted;
|
||||
url = 'http' + (tls ? 's' : '') + '://' + host + url;
|
||||
}
|
||||
|
||||
|
||||
// Support text/{plain,html} by default
|
||||
if (req.accepts('html')) {
|
||||
@@ -383,7 +419,7 @@ res.redirect = function(url, status){
|
||||
// Respond
|
||||
this.statusCode = status;
|
||||
this.header('Location', url);
|
||||
this.end(body);
|
||||
this.end(head ? null : body);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
53
lib/router/collection.js
Normal file
53
lib/router/collection.js
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
/*!
|
||||
* Express - router - Collection
|
||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Expose `Collection`.
|
||||
*/
|
||||
|
||||
module.exports = Collection;
|
||||
|
||||
/**
|
||||
* Initialize a new route `Collection`
|
||||
* with the given `router`.
|
||||
*
|
||||
* @param {Router} router
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Collection(router) {
|
||||
Array.apply(this, arguments);
|
||||
this.router = router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `Array.prototype`.
|
||||
*/
|
||||
|
||||
Collection.prototype.__proto__ = Array.prototype;
|
||||
|
||||
/**
|
||||
* Remove the routes in this collection.
|
||||
*
|
||||
* @return {Collection} of routes removed
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Collection.prototype.remove = function(){
|
||||
var router = this.router
|
||||
, len = this.length
|
||||
, ret = new Collection(this.router);
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (router.remove(this[i])) {
|
||||
ret.push(this[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
/*!
|
||||
* Express - router
|
||||
* Express - Router
|
||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
@@ -9,323 +9,390 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils')
|
||||
var Route = require('./route')
|
||||
, Collection = require('./collection')
|
||||
, utils = require('../utils')
|
||||
, parse = require('url').parse
|
||||
, _methods = require('./methods')
|
||||
, Route = require('./route');
|
||||
, toArray = utils.toArray;
|
||||
|
||||
/**
|
||||
* Expose router.
|
||||
* Expose `Router` constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = router;
|
||||
exports = module.exports = Router;
|
||||
|
||||
/**
|
||||
* Expose methods.
|
||||
* Expose HTTP methods.
|
||||
*/
|
||||
|
||||
exports.methods = _methods;
|
||||
var methods = exports.methods = require('./methods');
|
||||
|
||||
/**
|
||||
* Provides Sinatra-like routing capabilities.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
* Initialize a new `Router` with the given `app`.
|
||||
*
|
||||
* @param {express.HTTPServer} app
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function router(fn, app){
|
||||
var self = this
|
||||
, methods = {}
|
||||
, routes = {}
|
||||
, params = {};
|
||||
function Router(app) {
|
||||
var self = this;
|
||||
this.app = app;
|
||||
this.routes = {};
|
||||
this.params = {};
|
||||
this._params = [];
|
||||
|
||||
if (!fn) throw new Error('router provider requires a callback function');
|
||||
|
||||
// Generate method functions
|
||||
_methods.forEach(function(method){
|
||||
methods[method] = generateMethodFunction(method.toUpperCase());
|
||||
});
|
||||
|
||||
// Alias del -> delete
|
||||
methods.del = methods.delete;
|
||||
|
||||
// Apply callback to all methods
|
||||
methods.all = function(){
|
||||
var args = arguments;
|
||||
_methods.forEach(function(name){
|
||||
methods[name].apply(this, args);
|
||||
});
|
||||
return self;
|
||||
this.middleware = function(req, res, next){
|
||||
self._dispatch(req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
// Register param callback
|
||||
methods.param = function(name, fn){
|
||||
params[name] = fn;
|
||||
};
|
||||
|
||||
fn.call(this, methods);
|
||||
/**
|
||||
* Register a param callback `fn` for the given `name`.
|
||||
*
|
||||
* @param {String|Function} name
|
||||
* @param {Function} fn
|
||||
* @return {Router} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function generateMethodFunction(name) {
|
||||
var localRoutes = routes[name] = routes[name] || [];
|
||||
return function(path, fn){
|
||||
var keys = []
|
||||
, middleware = [];
|
||||
|
||||
// slice middleware
|
||||
if (arguments.length > 2) {
|
||||
middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
|
||||
fn = middleware.pop();
|
||||
middleware = utils.flatten(middleware);
|
||||
}
|
||||
|
||||
if (!path) throw new Error(name + ' route requires a path');
|
||||
if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
|
||||
|
||||
var options = { sensitive: app.enabled('case sensitive routes') };
|
||||
var route = new Route(name, path, fn, options);
|
||||
route.middleware = middleware;
|
||||
localRoutes.push(route);
|
||||
return self;
|
||||
};
|
||||
Router.prototype.param = function(name, fn){
|
||||
// param logic
|
||||
if ('function' == typeof name) {
|
||||
this._params.push(name);
|
||||
return;
|
||||
}
|
||||
|
||||
function router(req, res, next){
|
||||
var route
|
||||
, self = this;
|
||||
// apply param functions
|
||||
var params = this._params
|
||||
, len = params.length
|
||||
, ret;
|
||||
|
||||
(function pass(i){
|
||||
if (route = match(req, routes, i)) {
|
||||
var i = 0
|
||||
, keys = route.keys;
|
||||
|
||||
req.params = route.params;
|
||||
|
||||
// Param preconditions
|
||||
(function param(err) {
|
||||
try {
|
||||
var key = keys[i++]
|
||||
, val = req.params[key]
|
||||
, fn = params[key];
|
||||
|
||||
if ('route' == err) {
|
||||
pass(req._route_index + 1);
|
||||
// Error
|
||||
} else if (err) {
|
||||
next(err);
|
||||
// Param has callback
|
||||
} else if (fn) {
|
||||
// Return style
|
||||
if (1 == fn.length) {
|
||||
req.params[key] = fn(val);
|
||||
param();
|
||||
// Middleware style
|
||||
} else {
|
||||
fn(req, res, param, val);
|
||||
}
|
||||
// Finished processing params
|
||||
} else if (!key) {
|
||||
// route middleware
|
||||
i = 0;
|
||||
(function nextMiddleware(err){
|
||||
var fn = route.middleware[i++];
|
||||
if ('route' == err) {
|
||||
pass(req._route_index + 1);
|
||||
} else if (err) {
|
||||
next(err);
|
||||
} else if (fn) {
|
||||
fn(req, res, nextMiddleware);
|
||||
} else {
|
||||
route.callback.call(self, req, res, function(err){
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
pass(req._route_index + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
// More params
|
||||
} else {
|
||||
param();
|
||||
}
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
})();
|
||||
} else if ('OPTIONS' == req.method) {
|
||||
options(req, res, routes);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
router.remove = function(path, method, ret){
|
||||
var ret = ret || []
|
||||
, route;
|
||||
|
||||
// method specific remove
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
if (routes[method]) {
|
||||
for (var i = 0; i < routes[method].length; ++i) {
|
||||
route = routes[method][i];
|
||||
if (path == route.path) {
|
||||
route.index = i;
|
||||
routes[method].splice(i, 1);
|
||||
ret.push(route);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
// global remove
|
||||
} else {
|
||||
_methods.forEach(function(method){
|
||||
router.remove(path, method, ret);
|
||||
});
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (ret = params[i](name, fn)) {
|
||||
fn = ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
// ensure we end up with a
|
||||
// middleware function
|
||||
if ('function' != typeof fn) {
|
||||
throw new Error('invalid param() call for ' + name + ', got ' + fn);
|
||||
}
|
||||
|
||||
router.lookup = function(path, method, ret){
|
||||
ret = ret || [];
|
||||
|
||||
// method specific lookup
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
if (routes[method]) {
|
||||
routes[method].forEach(function(route, i){
|
||||
if (path == route.path) {
|
||||
route.index = i;
|
||||
ret.push(route);
|
||||
}
|
||||
});
|
||||
}
|
||||
// global lookup
|
||||
} else {
|
||||
_methods.forEach(function(method){
|
||||
router.lookup(path, method, ret);
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
router.match = function(url, method, ret){
|
||||
var ret = ret || []
|
||||
, i = 0
|
||||
, route
|
||||
, req;
|
||||
|
||||
// method specific matches
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
req = { url: url, method: method };
|
||||
while (route = match(req, routes, i)) {
|
||||
i = req._route_index + 1;
|
||||
route.index = i;
|
||||
ret.push(route);
|
||||
}
|
||||
// global matches
|
||||
} else {
|
||||
_methods.forEach(function(method){
|
||||
router.match(url, method, ret);
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
router.routes = routes;
|
||||
|
||||
return router;
|
||||
}
|
||||
(this.params[name] = this.params[name] || []).push(fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to OPTIONS.
|
||||
* Return a `Collection` of all routes defined.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {ServerResponse} req
|
||||
* @param {Array} routes
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.all = function(){
|
||||
return this.find(function(){
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the given `route`, returns
|
||||
* a bool indicating if the route was present
|
||||
* or not.
|
||||
*
|
||||
* @param {Route} route
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.remove = function(route){
|
||||
var routes = this.routes[route.method]
|
||||
, len = routes.length;
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (route == routes[i]) {
|
||||
routes.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return routes with route paths matching `path`.
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.lookup = function(method, path){
|
||||
return this.find(function(route){
|
||||
return path == route.path
|
||||
&& (route.method == method
|
||||
|| method == 'all');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return routes with regexps that match the given `url`.
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} url
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.match = function(method, url){
|
||||
return this.find(function(route){
|
||||
return route.match(url)
|
||||
&& (route.method == method
|
||||
|| method == 'all');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find routes based on the return value of `fn`
|
||||
* which is invoked once per route.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.find = function(fn){
|
||||
var len = methods.length
|
||||
, ret = new Collection(this)
|
||||
, method
|
||||
, routes
|
||||
, route;
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
method = methods[i];
|
||||
routes = this.routes[method];
|
||||
if (!routes) continue;
|
||||
for (var j = 0, jlen = routes.length; j < jlen; ++j) {
|
||||
route = routes[j];
|
||||
if (fn(route)) ret.push(route);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Route dispatcher aka the route "middleware".
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @param {ServerResponse} res
|
||||
* @param {Function} next
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function options(req, res, routes) {
|
||||
var pathname = parse(req.url).pathname
|
||||
, body = optionsFor(pathname, routes).join(',');
|
||||
res.send(body, { Allow: body });
|
||||
}
|
||||
Router.prototype._dispatch = function(req, res, next){
|
||||
var params = this.params
|
||||
, self = this;
|
||||
|
||||
// route dispatch
|
||||
(function pass(i, err){
|
||||
var paramCallbacks
|
||||
, paramIndex = 0
|
||||
, paramVal
|
||||
, route
|
||||
, keys
|
||||
, key
|
||||
, ret;
|
||||
|
||||
// match next route
|
||||
function nextRoute(err) {
|
||||
pass(req._route_index + 1, err);
|
||||
}
|
||||
|
||||
// match route
|
||||
req.route = route = self._match(req, i);
|
||||
|
||||
// implied OPTIONS
|
||||
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
|
||||
|
||||
// no route
|
||||
if (!route) return next(err);
|
||||
|
||||
// we have a route
|
||||
// start at param 0
|
||||
req.params = route.params;
|
||||
keys = route.keys;
|
||||
i = 0;
|
||||
|
||||
// param callbacks
|
||||
function param(err) {
|
||||
paramIndex = 0;
|
||||
key = keys[i++];
|
||||
paramVal = key && req.params[key.name];
|
||||
paramCallbacks = key && params[key.name];
|
||||
|
||||
try {
|
||||
if ('route' == err) {
|
||||
nextRoute();
|
||||
} else if (err) {
|
||||
i = 0;
|
||||
callbacks(err);
|
||||
} else if (paramCallbacks && undefined !== paramVal) {
|
||||
paramCallback();
|
||||
} else if (key) {
|
||||
param();
|
||||
} else {
|
||||
i = 0;
|
||||
callbacks();
|
||||
}
|
||||
} catch (err) {
|
||||
param(err);
|
||||
}
|
||||
};
|
||||
|
||||
param(err);
|
||||
|
||||
// single param callbacks
|
||||
function paramCallback(err) {
|
||||
var fn = paramCallbacks[paramIndex++];
|
||||
if (err || !fn) return param(err);
|
||||
fn(req, res, paramCallback, paramVal, key.name);
|
||||
}
|
||||
|
||||
// invoke route callbacks
|
||||
function callbacks(err) {
|
||||
var fn = route.callbacks[i++];
|
||||
try {
|
||||
if ('route' == err) {
|
||||
nextRoute();
|
||||
} else if (err && fn) {
|
||||
if (fn.length < 4) return callbacks(err);
|
||||
fn(err, req, res, callbacks);
|
||||
} else if (fn) {
|
||||
fn(req, res, callbacks);
|
||||
} else {
|
||||
nextRoute(err);
|
||||
}
|
||||
} catch (err) {
|
||||
callbacks(err);
|
||||
}
|
||||
}
|
||||
})(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return OPTIONS array for the given `path`, matching `routes`.
|
||||
* Respond to __OPTIONS__ method.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @param {ServerResponse} res
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Router.prototype._options = function(req, res){
|
||||
var path = parse(req.url).pathname
|
||||
, body = this._optionsFor(path).join(',');
|
||||
res.send(body, { Allow: body });
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an array of HTTP verbs or "options" for `path`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Array} routes
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function optionsFor(path, routes) {
|
||||
return _methods.filter(function(method){
|
||||
var arr = routes[method.toUpperCase()];
|
||||
for (var i = 0, len = arr.length; i < len; ++i) {
|
||||
if (arr[i].regexp.test(path)) return true;
|
||||
Router.prototype._optionsFor = function(path){
|
||||
var self = this;
|
||||
return methods.filter(function(method){
|
||||
var routes = self.routes[method];
|
||||
if (!routes || 'options' == method) return;
|
||||
for (var i = 0, len = routes.length; i < len; ++i) {
|
||||
if (routes[i].match(path)) return true;
|
||||
}
|
||||
}).map(function(method){
|
||||
return method.toUpperCase();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt to match the given request to
|
||||
* one of the routes. When successful
|
||||
* a route function is returned.
|
||||
* Attempt to match a route for `req`
|
||||
* starting from offset `i`.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {Object} routes
|
||||
* @return {Function}
|
||||
* @param {IncomingMessage} req
|
||||
* @param {Number} i
|
||||
* @return {Route}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function match(req, routes, i) {
|
||||
var captures
|
||||
, method = req.method
|
||||
, i = i || 0;
|
||||
Router.prototype._match = function(req, i){
|
||||
var method = req.method.toLowerCase()
|
||||
, url = parse(req.url)
|
||||
, path = url.pathname
|
||||
, routes = this.routes
|
||||
, captures
|
||||
, route
|
||||
, keys;
|
||||
|
||||
// pass HEAD to GET routes
|
||||
if ('HEAD' == method) method = 'GET';
|
||||
if ('head' == method) method = 'get';
|
||||
|
||||
// routes for this method
|
||||
if (routes = routes[method]) {
|
||||
var url = parse(req.url)
|
||||
, pathname = url.pathname;
|
||||
|
||||
// matching routes
|
||||
for (var len = routes.length; i < len; ++i) {
|
||||
var route = routes[i]
|
||||
, fn = route.callback
|
||||
, path = route.regexp
|
||||
, keys = route.keys;
|
||||
|
||||
// match
|
||||
if (captures = path.exec(pathname)) {
|
||||
route = routes[i];
|
||||
if (captures = route.match(path)) {
|
||||
keys = route.keys;
|
||||
route.params = [];
|
||||
for (var j = 1, l = captures.length; j < l; ++j) {
|
||||
var key = keys[j-1],
|
||||
val = 'string' == typeof captures[j]
|
||||
|
||||
// params from capture groups
|
||||
for (var j = 1, jlen = captures.length; j < jlen; ++j) {
|
||||
var key = keys[j-1]
|
||||
, val = 'string' == typeof captures[j]
|
||||
? decodeURIComponent(captures[j])
|
||||
: captures[j];
|
||||
if (key) {
|
||||
route.params[key] = val;
|
||||
route.params[key.name] = val;
|
||||
} else {
|
||||
route.params.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
// all done
|
||||
req._route_index = i;
|
||||
return route;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Route `method`, `path`, and one or more callbacks.
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
* @param {Function} callback...
|
||||
* @return {Router} for chaining
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Router.prototype._route = function(method, path, callbacks){
|
||||
var app = this.app
|
||||
, callbacks = utils.flatten(toArray(arguments, 2));
|
||||
|
||||
// ensure path was given
|
||||
if (!path) throw new Error('app.' + method + '() requires a path');
|
||||
|
||||
// create the route
|
||||
var route = new Route(method, path, callbacks, {
|
||||
sensitive: app.enabled('case sensitive routes')
|
||||
, strict: app.enabled('strict routing')
|
||||
});
|
||||
|
||||
// add it
|
||||
(this.routes[method] = this.routes[method] || [])
|
||||
.push(route);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -13,27 +13,43 @@ module.exports = Route;
|
||||
|
||||
/**
|
||||
* Initialize `Route` with the given HTTP `method`, `path`,
|
||||
* and callback `fn` and `options`.
|
||||
* and an array of `callbacks` and `options`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `sensitive` enable case-sensitive routes
|
||||
* - `sensitive` enable case-sensitive routes
|
||||
* - `strict` enable strict matching for trailing slashes
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
* @param {Function} fn
|
||||
* @param {Array} callbacks
|
||||
* @param {Object} options.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Route(method, path, fn, options) {
|
||||
function Route(method, path, callbacks, options) {
|
||||
options = options || {};
|
||||
this.callback = fn;
|
||||
this.path = path;
|
||||
this.regexp = normalize(path, this.keys = [], options.sensitive);
|
||||
this.method = method;
|
||||
this.callbacks = callbacks;
|
||||
this.regexp = normalize(path
|
||||
, this.keys = []
|
||||
, options.sensitive
|
||||
, options.strict);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this route matches `path` and return captures made.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Route.prototype.match = function(path){
|
||||
return this.regexp.exec(path);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize the given path string,
|
||||
* returning a regular expression.
|
||||
@@ -46,26 +62,27 @@ function Route(method, path, fn, options) {
|
||||
* @param {String|RegExp} path
|
||||
* @param {Array} keys
|
||||
* @param {Boolean} sensitive
|
||||
* @param {Boolean} strict
|
||||
* @return {RegExp}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function normalize(path, keys, sensitive) {
|
||||
if (path instanceof RegExp) return path;
|
||||
function normalize(path, keys, sensitive, strict) {
|
||||
if (path instanceof RegExp) return path;
|
||||
path = path
|
||||
.concat('/?')
|
||||
.concat(strict ? '' : '/?')
|
||||
.replace(/\/\(/g, '(?:/')
|
||||
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
|
||||
keys.push(key);
|
||||
keys.push({ name: key, optional: !! optional });
|
||||
slash = slash || '';
|
||||
return ''
|
||||
+ (optional ? '' : slash)
|
||||
+ '(?:'
|
||||
+ (optional ? slash : '')
|
||||
+ (format || '') + (capture || '([^/]+?)') + ')'
|
||||
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
|
||||
+ (optional || '');
|
||||
})
|
||||
.replace(/([\/.])/g, '\\$1')
|
||||
.replace(/\*/g, '(.+)');
|
||||
.replace(/\*/g, '(.*)');
|
||||
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
|
||||
}
|
||||
}
|
||||
|
||||
30
lib/utils.js
30
lib/utils.js
@@ -5,6 +5,19 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if `path` looks absolute.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.isAbsolute = function(path){
|
||||
if ('/' == path[0]) return true;
|
||||
if (':' == path[1] && '\\' == path[2]) return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge object `b` with `a` giving precedence to
|
||||
* values in object `a`.
|
||||
@@ -120,3 +133,20 @@ exports.parseRange = function(size, str){
|
||||
});
|
||||
return valid ? arr : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fast alternative to `Array.prototype.slice.call()`.
|
||||
*
|
||||
* @param {Arguments} args
|
||||
* @param {Number} n
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.toArray = function(args, i){
|
||||
var arr = []
|
||||
, len = args.length
|
||||
, i = i || 0;
|
||||
for (; i < len; ++i) arr.push(args[i]);
|
||||
return arr;
|
||||
};
|
||||
|
||||
27
lib/view.js
27
lib/view.js
@@ -61,6 +61,7 @@ exports.compile = function(view, cache, cid, options){
|
||||
}
|
||||
|
||||
// compile
|
||||
options.filename = view.path;
|
||||
view.fn = view.templateEngine.compile(view.contents, options);
|
||||
cache[cid] = view;
|
||||
|
||||
@@ -93,20 +94,23 @@ exports.compile = function(view, cache, cid, options){
|
||||
*/
|
||||
|
||||
exports.lookup = function(view, options){
|
||||
var orig = view = new View(view, options);
|
||||
var orig = view = new View(view, options)
|
||||
, partial = options.isPartial
|
||||
, layout = options.isLayout;
|
||||
|
||||
// Try _ prefix ex: ./views/_<name>.jade
|
||||
// taking precedence over the direct path
|
||||
if (partial) {
|
||||
view = new View(orig.prefixPath, options);
|
||||
if (!view.exists) view = orig;
|
||||
}
|
||||
|
||||
// Try index ex: ./views/user/index.jade
|
||||
if (!view.exists) view = new View(orig.indexPath, options);
|
||||
if (!layout && !view.exists) view = new View(orig.indexPath, options);
|
||||
|
||||
// Try ../<name>/index ex: ../user/index.jade
|
||||
// when calling partial('user') within the same dir
|
||||
if (!view.exists && !options.isLayout) view = new View(orig.upIndexPath, options);
|
||||
if (!layout && !view.exists) view = new View(orig.upIndexPath, options);
|
||||
|
||||
// Try root ex: <root>/user.jade
|
||||
if (!view.exists) view = new View(orig.rootPath, options);
|
||||
@@ -127,11 +131,6 @@ exports.lookup = function(view, options){
|
||||
function renderPartial(res, view, options, parentLocals, parent){
|
||||
var collection, object, locals;
|
||||
|
||||
// Inherit parent view extension when not present
|
||||
if (parent && !~view.indexOf('.')) {
|
||||
view += parent.extension;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
// collection
|
||||
if (options.collection) {
|
||||
@@ -167,7 +166,7 @@ function renderPartial(res, view, options, parentLocals, parent){
|
||||
if (locals) merge(options, locals);
|
||||
|
||||
// Partials dont need layouts
|
||||
options.renderPartial = true;
|
||||
options.isPartial = true;
|
||||
options.layout = false;
|
||||
|
||||
// Deduce name from view path
|
||||
@@ -180,8 +179,6 @@ function renderPartial(res, view, options, parentLocals, parent){
|
||||
options[name] = object;
|
||||
} else if (name === global) {
|
||||
merge(options, object);
|
||||
} else {
|
||||
options.scope = object;
|
||||
}
|
||||
}
|
||||
return res.render(view, options, null, parent, true);
|
||||
@@ -269,7 +266,7 @@ res.partial = function(view, options, fn){
|
||||
parent.dirname = app.set('views') || process.cwd() + '/views';
|
||||
|
||||
// utilize "view engine" option
|
||||
if (viewEngine) parent.extension = '.' + viewEngine;
|
||||
if (viewEngine) parent.engine = viewEngine;
|
||||
|
||||
// render the partial
|
||||
try {
|
||||
@@ -320,8 +317,7 @@ res.render = function(view, opts, fn, parent, sub){
|
||||
// callback given
|
||||
if (fn) {
|
||||
fn(err);
|
||||
// unwind to root call to prevent
|
||||
// several next(err) calls
|
||||
// unwind to root call to prevent multiple callbacks
|
||||
} else if (sub) {
|
||||
throw err;
|
||||
// root template, next(err)
|
||||
@@ -365,7 +361,7 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
// capture attempts
|
||||
options.attempts = [];
|
||||
|
||||
var partial = options.renderPartial
|
||||
var partial = options.isPartial
|
||||
, layout = options.layout;
|
||||
|
||||
// Layout support
|
||||
@@ -416,7 +412,6 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
// View lookup
|
||||
options.hint = app.enabled('hints');
|
||||
view = exports.compile(view, app.cache, cid, options);
|
||||
options.filename = view.path;
|
||||
|
||||
// layout helper
|
||||
options.layout = function(path){
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
var path = require('path')
|
||||
, utils = require('../utils')
|
||||
, extname = path.extname
|
||||
, dirname = path.dirname
|
||||
, basename = path.basename
|
||||
@@ -99,7 +100,7 @@ View.prototype.resolvePath = function(){
|
||||
// Implicit engine
|
||||
if (!~this.basename.indexOf('.')) path += this.extension;
|
||||
// Absolute
|
||||
if ('/' == path[0]) return path;
|
||||
if (utils.isAbsolute(path)) return path;
|
||||
// Relative to parent
|
||||
if (this.relative && this.parent) return this.parent.dirname + '/' + path;
|
||||
// Relative to root
|
||||
|
||||
11
package.json
11
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "2.3.6",
|
||||
"version": "2.4.7",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
|
||||
@@ -10,9 +10,10 @@
|
||||
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
|
||||
],
|
||||
"dependencies": {
|
||||
"connect": ">= 1.4.1 < 2.0.0",
|
||||
"connect": "1.7.x",
|
||||
"mime": ">= 0.0.1",
|
||||
"qs": ">= 0.0.6"
|
||||
"qs": ">= 0.3.1",
|
||||
"mkdirp": "0.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"connect-form": "0.2.1",
|
||||
@@ -30,5 +31,9 @@
|
||||
"repository": "git://github.com/visionmedia/express",
|
||||
"main": "index",
|
||||
"bin": { "express": "./bin/express" },
|
||||
"scripts": {
|
||||
"test": "make test",
|
||||
"prepublish" : "npm prune"
|
||||
},
|
||||
"engines": { "node": ">= 0.4.1 < 0.5.0" }
|
||||
}
|
||||
@@ -236,7 +236,6 @@ module.exports = {
|
||||
var server = express.createServer();
|
||||
server.set('env', 'development');
|
||||
|
||||
// Config blocks
|
||||
var ret = server.configure(function(){
|
||||
assert.equal(this, server, 'Test context of configure() is the server');
|
||||
calls.push('any');
|
||||
@@ -297,7 +296,23 @@ module.exports = {
|
||||
{ url: '/' },
|
||||
{ body: 'first route last' });
|
||||
},
|
||||
|
||||
|
||||
'test #configure() multiple envs': function(){
|
||||
var app = express.createServer();
|
||||
app.set('env', 'prod');
|
||||
var calls = [];
|
||||
|
||||
app.configure('stage', 'prod', function(){
|
||||
calls.push('stage/prod');
|
||||
});
|
||||
|
||||
app.configure('prod', function(){
|
||||
calls.push('prod');
|
||||
});
|
||||
|
||||
calls.should.eql(['stage/prod', 'prod']);
|
||||
},
|
||||
|
||||
'test #set()': function(){
|
||||
var app = express.createServer();
|
||||
var ret = app.set('title', 'My App').set('something', 'else');
|
||||
@@ -352,15 +367,27 @@ module.exports = {
|
||||
{ url: '/', method: 'POST', data: 'name=tj', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }},
|
||||
{ body: '{"name":"tj"}' });
|
||||
},
|
||||
|
||||
|
||||
'test "basepath" setting': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('basepath', '/shop');
|
||||
|
||||
app.get('/redirect', function(req, res){
|
||||
res.redirect('/cart');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/redirect', headers: { Host: 'foo.com' }},
|
||||
{ headers: { Location: 'http://foo.com/shop/cart' }});
|
||||
},
|
||||
|
||||
'test mounting': function(){
|
||||
var called
|
||||
, app = express.createServer()
|
||||
, blog = express.createServer()
|
||||
, map = express.createServer()
|
||||
, reg = connect.createServer();
|
||||
|
||||
map.set('home', '/map');
|
||||
|
||||
map.mounted(function(parent){
|
||||
called = true;
|
||||
@@ -381,9 +408,8 @@ module.exports = {
|
||||
blog.set('test').should.equal('parent setting');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
app.set('home').should.equal('/');
|
||||
blog.set('home').should.equal('/blog');
|
||||
map.set('home').should.equal('/contact/map');
|
||||
blog.set('basepath').should.equal('/blog');
|
||||
map.set('basepath').should.equal('/contact');
|
||||
res.send('main app');
|
||||
});
|
||||
|
||||
@@ -461,5 +487,17 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/another' },
|
||||
{ body: 'got /another' });
|
||||
},
|
||||
|
||||
'invalid chars': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/:name', function(req, res, next){
|
||||
res.send('invalid');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/%a0' },
|
||||
{ status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
4
test/fixtures/forum/thread.jade
vendored
4
test/fixtures/forum/thread.jade
vendored
@@ -1 +1,3 @@
|
||||
h1 Forum Thread
|
||||
h1 Forum Thread
|
||||
!= partial('../hello')
|
||||
!= partial('../hello.haml')
|
||||
2
test/fixtures/magic.jade
vendored
2
test/fixtures/magic.jade
vendored
@@ -3,4 +3,4 @@
|
||||
- else if (lastInCollection)
|
||||
li.last= word
|
||||
- else
|
||||
li(class: 'word-' + indexInCollection)= word
|
||||
li(class='word-' + indexInCollection)= word
|
||||
1
test/fixtures/person.jade
vendored
1
test/fixtures/person.jade
vendored
@@ -1 +0,0 @@
|
||||
p #{label} #{this.name}
|
||||
@@ -9,6 +9,18 @@ var express = require('express')
|
||||
, should = require('should');
|
||||
|
||||
module.exports = {
|
||||
'test #path': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/search', function(req, res){
|
||||
res.send(req.path);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/search?q=tobi' },
|
||||
{ body: '/search' });
|
||||
},
|
||||
|
||||
'test #isXMLHttpRequest': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
@@ -155,12 +167,15 @@ module.exports = {
|
||||
req.flash('info').should.eql(['one']);
|
||||
|
||||
req.flash('info', 'Email _sent_.');
|
||||
req.flash('info', '<script>');
|
||||
req.flash('info').should.eql(['Email <em>sent</em>.', '<script>']);
|
||||
req.flash('info', '<em>%s</em>', 'html');
|
||||
req.flash('info').should.eql(['Email <em>sent</em>.', '<em>html</em>']);
|
||||
|
||||
req.flash('info', 'Welcome _%s_ to %s', 'TJ', 'something');
|
||||
req.flash('info').should.eql(['Welcome <em>TJ</em> to something']);
|
||||
|
||||
req.flash('info', 'Welcome %s', '<script>');
|
||||
req.flash('info').should.eql(['Welcome <script>']);
|
||||
|
||||
req.flash('error', 'Foo %u', 'bar');
|
||||
req.flash('error').should.eql(['Foo BAR']);
|
||||
|
||||
@@ -303,5 +318,36 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/incorrect', headers: { Referer: 'expressjs.com' }},
|
||||
{ body: 'expressjs.com' });
|
||||
},
|
||||
|
||||
'test #get(field, param)': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
req.get('content-disposition', 'filename')
|
||||
.should.equal('foo bar.jpg');
|
||||
|
||||
req.get('Content-Disposition', 'filename')
|
||||
.should.equal('foo bar.jpg');
|
||||
|
||||
req.get('x-content-foo', 'foo').should.equal('bar');
|
||||
req.get('x-content-foo', 'bar').should.equal('foo bar baz');
|
||||
req.get('x-content-foo', 'woot').should.equal('tobi loki jane');
|
||||
req.get('cache-control', 'max-age').should.equal('500');
|
||||
|
||||
req.get('foo').should.equal('');
|
||||
req.get('foo', 'bar').should.equal('');
|
||||
res.end();
|
||||
});
|
||||
|
||||
var fields = {
|
||||
'Content-Disposition': 'attachment; filename="foo bar.jpg"'
|
||||
, 'X-Content-Foo': 'foo=bar; bar=foo bar baz; woot=tobi loki jane;'
|
||||
, 'Cache-Control': 'max-age = 500'
|
||||
};
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', headers: fields },
|
||||
{ body: '' });
|
||||
}
|
||||
};
|
||||
@@ -9,6 +9,57 @@ var express = require('express')
|
||||
, should = require('should');
|
||||
|
||||
module.exports = {
|
||||
'test #json()': function(){
|
||||
var app = express.createServer()
|
||||
, json = 'application/json; charset=utf-8';
|
||||
|
||||
app.get('/user', function(req, res, next){
|
||||
res.json({ name: 'tj' });
|
||||
});
|
||||
|
||||
app.get('/string', function(req, res, next){
|
||||
res.json('whoop!');
|
||||
});
|
||||
|
||||
app.get('/error', function(req, res, next){
|
||||
res.json('oh noes!', 500);
|
||||
});
|
||||
|
||||
app.get('/headers', function(req, res, next){
|
||||
res.json(undefined, { 'X-Foo': 'bar' }, 302);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/error' },
|
||||
{ body: '"oh noes!"'
|
||||
, status: 500
|
||||
, headers: { 'Content-Type': json }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/string' },
|
||||
{ body: '"whoop!"'
|
||||
, headers: {
|
||||
'Content-Type': json
|
||||
, 'Content-Length': 8
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user' },
|
||||
{ body: '{"name":"tj"}', headers: { 'Content-Type': json }});
|
||||
},
|
||||
|
||||
'test #status()': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/error', function(req, res, next){
|
||||
res.status(500).send('OH NO');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/error' },
|
||||
{ body: 'OH NO', status: 500 });
|
||||
},
|
||||
|
||||
'test #send()': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
@@ -17,15 +68,8 @@ module.exports = {
|
||||
});
|
||||
|
||||
app.get('/json', function(req, res){
|
||||
res.header('X-Foo', 'bar');
|
||||
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
|
||||
});
|
||||
|
||||
app.get('/jsonp', function(req, res){
|
||||
app.enable('jsonp callback');
|
||||
res.header('X-Foo', 'bar');
|
||||
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
|
||||
app.disable('jsonp callback');
|
||||
res.header('X-Foo', 'bar')
|
||||
.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
|
||||
});
|
||||
|
||||
app.get('/text', function(req, res){
|
||||
@@ -53,7 +97,11 @@ module.exports = {
|
||||
app.get('/noargs', function(req, res, next){
|
||||
res.send();
|
||||
});
|
||||
|
||||
|
||||
app.get('/no-content', function(req, res, next){
|
||||
res.send(204);
|
||||
});
|
||||
|
||||
app.get('/undefined', function(req, res, next){
|
||||
res.send(undefined);
|
||||
});
|
||||
@@ -84,41 +132,6 @@ module.exports = {
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=test' },
|
||||
{ body: 'test({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=baz' },
|
||||
{ body: 'baz({"foo":"bar"});'
|
||||
, status: 201, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=invalid()[]' },
|
||||
{ body: 'invalid({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/json?callback=test' },
|
||||
{ body: '{"foo":"bar"}'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/text' },
|
||||
{ body: 'wahoo'
|
||||
@@ -153,6 +166,13 @@ module.exports = {
|
||||
'Content-Type': 'application/octet-stream'
|
||||
, 'Content-Length': '6'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/no-content' },
|
||||
{ status: 204 }, function(res){
|
||||
assert.equal(undefined, res.headers['content-type']);
|
||||
assert.equal(undefined, res.headers['content-length']);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/noargs' },
|
||||
@@ -167,8 +187,91 @@ module.exports = {
|
||||
assert.equal(undefined, res.headers['content-type']);
|
||||
assert.equal(undefined, res.headers['content-length']);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/json?callback=test' },
|
||||
{ body: '{"foo":"bar"}'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
},
|
||||
|
||||
'test #send() JSONP': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.enable('jsonp callback');
|
||||
|
||||
app.get('/jsonp', function(req, res){
|
||||
res.header('X-Foo', 'bar');
|
||||
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=test' },
|
||||
{ body: 'test({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=baz' },
|
||||
{ body: 'baz({"foo":"bar"});'
|
||||
, status: 201, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=invalid()[]' },
|
||||
{ body: 'invalid({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
},
|
||||
|
||||
'test #json() JSONP': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.enable('jsonp callback');
|
||||
|
||||
app.get('/jsonp', function(req, res){
|
||||
res.header('X-Foo', 'bar');
|
||||
res.json({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=test' },
|
||||
{ body: 'test({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=baz' },
|
||||
{ body: 'baz({"foo":"bar"});'
|
||||
, status: 201, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/jsonp?callback=invalid()[]' },
|
||||
{ body: 'invalid({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
},
|
||||
|
||||
'test #contentType()': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
@@ -229,13 +332,13 @@ module.exports = {
|
||||
var app = express.createServer()
|
||||
, app2 = express.createServer();
|
||||
|
||||
app2.set('home', '/blog');
|
||||
app2.set('basepath', '/blog');
|
||||
|
||||
app2.redirect('google', 'http://google.com');
|
||||
|
||||
app2.redirect('blog', function(req, res){
|
||||
return req.params.id
|
||||
? '/user/' + req.params.id + '/blog'
|
||||
? '/user/' + req.params.id + '/posts'
|
||||
: null;
|
||||
});
|
||||
|
||||
@@ -276,59 +379,63 @@ module.exports = {
|
||||
res.redirect('blog');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/home', method: 'HEAD' },
|
||||
{ body: '' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/html', headers: { Accept: 'text/html,text/plain', Host: 'foo.com' }},
|
||||
{ body: '<p>Moved Temporarily. Redirecting to <a href="http://google.com">http://google.com</a></p>' });
|
||||
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', headers: { Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Permanently. Redirecting to http://google.com'
|
||||
, status: 301, headers: { Location: 'http://google.com' }});
|
||||
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/back', headers: { Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Temporarily. Redirecting to http://foo.com/'
|
||||
, status: 302, headers: { Location: 'http://foo.com/', 'Content-Type': 'text/plain' }});
|
||||
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/back', headers: { Referer: '/foo', Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Temporarily. Redirecting to http://foo.com/foo'
|
||||
, status: 302, headers: { Location: 'http://foo.com/foo' }});
|
||||
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/back', headers: { Referrer: '/foo', Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Temporarily. Redirecting to http://foo.com/foo'
|
||||
, status: 302, headers: { Location: 'http://foo.com/foo' }});
|
||||
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/home', headers: { Accept: 'text/plain', Host: 'foo.com' } },
|
||||
{ body: 'Moved Temporarily. Redirecting to http://foo.com/'
|
||||
, status: 302, headers: { Location: 'http://foo.com/' }});
|
||||
|
||||
|
||||
assert.response(app2,
|
||||
{ url: '/', headers: { Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Permanently. Redirecting to http://google.com'
|
||||
, status: 301, headers: { Location: 'http://google.com' }});
|
||||
|
||||
|
||||
assert.response(app2,
|
||||
{ url: '/back', headers: { Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Temporarily. Redirecting to http://foo.com/blog'
|
||||
, status: 302, headers: { Location: 'http://foo.com/blog' }});
|
||||
|
||||
|
||||
assert.response(app2,
|
||||
{ url: '/home', headers: { Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Temporarily. Redirecting to http://foo.com/blog'
|
||||
, status: 302, headers: { Location: 'http://foo.com/blog' }});
|
||||
|
||||
|
||||
assert.response(app2,
|
||||
{ url: '/google', headers: { Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Temporarily. Redirecting to http://google.com'
|
||||
, status: 302, headers: { Location: 'http://google.com' }});
|
||||
|
||||
|
||||
assert.response(app2,
|
||||
{ url: '/user/12', headers: { Accept: 'text/plain', Host: 'foo.com' }},
|
||||
{ body: 'Moved Temporarily. Redirecting to http://foo.com/user/12/blog'
|
||||
, status: 302, headers: { Location: 'http://foo.com/user/12/blog', 'X-Foo': 'bar' }});
|
||||
{ body: 'Moved Temporarily. Redirecting to http://foo.com/blog/user/12/posts'
|
||||
, status: 302, headers: { Location: 'http://foo.com/blog/user/12/posts', 'X-Foo': 'bar' }});
|
||||
},
|
||||
|
||||
'test #redirect() when mounted': function(){
|
||||
@@ -403,10 +510,6 @@ module.exports = {
|
||||
});
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/forum' },
|
||||
{ body: 'got an error' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/does-not-exist' },
|
||||
{ body: 'got an error' });
|
||||
@@ -561,15 +664,55 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test #cookie()': function(){
|
||||
'test #cookie() path default': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
|
||||
app.set('basepath', '/foo');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.cookie('rememberme', 'yes', { expires: new Date(1), httpOnly: true });
|
||||
res.cookie('something', 'else');
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', headers: { Host: 'foo.com' }},
|
||||
function(res){
|
||||
res.headers['set-cookie']
|
||||
.should.eql(['rememberme=yes; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else; path=/foo']);
|
||||
});
|
||||
},
|
||||
|
||||
'test #cookie() explicit path': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('/basepath', '/foo');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.cookie('rememberme', 'yes', { path: '/', expires: new Date(1), httpOnly: true });
|
||||
res.cookie('something', 'else', { path: '/' });
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', headers: { Host: 'foo.com' }},
|
||||
function(res){
|
||||
res.headers['set-cookie']
|
||||
.should.eql(['rememberme=yes; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else; path=/']);
|
||||
});
|
||||
},
|
||||
|
||||
'test #cookie() null path': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('/basepath', '/foo');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.cookie('rememberme', 'yes', { path: null, expires: new Date(1), httpOnly: true });
|
||||
res.cookie('something', 'else', { path: null });
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', headers: { Host: 'foo.com' }},
|
||||
function(res){
|
||||
@@ -577,10 +720,30 @@ module.exports = {
|
||||
.should.eql(['rememberme=yes; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else']);
|
||||
});
|
||||
},
|
||||
|
||||
'test #clearCookie()': function(){
|
||||
|
||||
'test #clearCookie() default path': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('basepath', '/foo');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.clearCookie('rememberme');
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
function(res){
|
||||
res.headers['set-cookie']
|
||||
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
|
||||
});
|
||||
},
|
||||
|
||||
'test #clearCookie() explicit path': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('basepath', '/bar');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.clearCookie('rememberme', { path: '/foo' });
|
||||
res.redirect('/');
|
||||
@@ -593,7 +756,7 @@ module.exports = {
|
||||
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
'test HEAD': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
|
||||
@@ -74,6 +74,277 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test app.param() multiple mapping functions': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.param(function(name, fn){
|
||||
if (fn.length < 3) {
|
||||
return function(req, res, next, val){
|
||||
val = req.params[name] = fn(val);
|
||||
if (false === val) {
|
||||
next('route');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
app.param(function(name, range){
|
||||
if (!~String(range).indexOf('..')) return;
|
||||
var parts = range.split('..')
|
||||
, from = parseInt(parts.shift())
|
||||
, to = parseInt(parts.shift());
|
||||
|
||||
return function(req, res, next, val){
|
||||
if (val < from || val > to) return next('route');
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.param('user', Number);
|
||||
app.param('user', '0..5');
|
||||
|
||||
app.get('/user/:user', function(req, res){
|
||||
res.json(req.params.user);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/3' },
|
||||
{ body: '3' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/6' },
|
||||
{ status: 404 });
|
||||
},
|
||||
|
||||
'test app.param() name passing': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.param(function(name, fn){
|
||||
if (fn.length < 3) {
|
||||
return function(req, res, next, val){
|
||||
val = req.params[name] = fn(val);
|
||||
if (false === val) {
|
||||
next('route');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function within(a, b) {
|
||||
return function(req, res, next, val, name){
|
||||
if (val < a || val > b) {
|
||||
return next(new Error(name + ' should be within ' + a + '..' + b));
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
app.param('user', Number);
|
||||
app.param('user', within(0, 5));
|
||||
|
||||
app.get('/user/:user', function(req, res){
|
||||
res.json(req.params.user);
|
||||
});
|
||||
|
||||
app.use(function(err, req, res, next){
|
||||
res.json({ error: err.message });
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/0' },
|
||||
{ body: '0' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/6' },
|
||||
{ body: '{"error":"user should be within 0..5"}' });
|
||||
},
|
||||
|
||||
'test app.param() multiple callbacks and array of params': function(){
|
||||
var app = express.createServer();
|
||||
var users = [{ name: 'tj' }];
|
||||
var pets = [['tobi', 'loki', 'jane', 'manny', 'luna']];
|
||||
|
||||
function loadUser(req, res, next, id) {
|
||||
req.user = users[id];
|
||||
next();
|
||||
}
|
||||
|
||||
function loadUserPets(req, res, next, id) {
|
||||
req.user.pets = pets[id];
|
||||
next();
|
||||
}
|
||||
|
||||
app.param(['user_id', 'user'], loadUser, loadUserPets);
|
||||
|
||||
app.get('/user/:user_id', function(req, res){
|
||||
res.send(req.user);
|
||||
});
|
||||
|
||||
app.get('/account/:user', function(req, res){
|
||||
res.send(req.user);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/account/0' },
|
||||
{ body: '{"name":"tj","pets":["tobi","loki","jane","manny","luna"]}' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/0' },
|
||||
{ body: '{"name":"tj","pets":["tobi","loki","jane","manny","luna"]}' });
|
||||
},
|
||||
|
||||
'test app.param() multiple callbacks': function(){
|
||||
var app = express.createServer();
|
||||
var users = [{ name: 'tj' }];
|
||||
var pets = [['tobi', 'loki', 'jane', 'manny', 'luna']];
|
||||
|
||||
function loadUser(req, res, next, id) {
|
||||
req.user = users[id];
|
||||
next();
|
||||
}
|
||||
|
||||
function loadUserPets(req, res, next, id) {
|
||||
req.user.pets = pets[id];
|
||||
next();
|
||||
}
|
||||
|
||||
app.param('user_id', loadUser, loadUserPets);
|
||||
|
||||
app.get('/user/:user_id', function(req, res){
|
||||
res.send(req.user);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/0' },
|
||||
{ body: '{"name":"tj","pets":["tobi","loki","jane","manny","luna"]}' });
|
||||
},
|
||||
|
||||
'test app.param() multiple calls with error': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
var commits = ['foo', 'bar', 'baz'];
|
||||
|
||||
app.param('commit', function(req, res, next, id){
|
||||
req.commit = parseInt(id);
|
||||
if (isNaN(req.commit)) return next('route');
|
||||
next();
|
||||
});
|
||||
|
||||
app.param('commit', function(req, res, next, id){
|
||||
req.commit = commits[req.commit];
|
||||
next(new Error('failed'));
|
||||
});
|
||||
|
||||
app.get('/commit/:commit', function(req, res){
|
||||
res.send(req.commit);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/commit/0' },
|
||||
{ status: 500 });
|
||||
},
|
||||
|
||||
'test app.param() multiple calls': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
var commits = ['foo', 'bar', 'baz'];
|
||||
|
||||
app.param('commit', function(req, res, next, id){
|
||||
req.commit = parseInt(id);
|
||||
if (isNaN(req.commit)) return next('route');
|
||||
next();
|
||||
});
|
||||
|
||||
app.param('commit', function(req, res, next, id){
|
||||
req.commit = commits[req.commit];
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/commit/:commit', function(req, res){
|
||||
res.send(req.commit);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/commit/0' },
|
||||
{ body: 'foo' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/commit/0x01' },
|
||||
{ body: 'bar' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/commit/asdf' },
|
||||
{ status: 404 });
|
||||
},
|
||||
|
||||
'test app.param(fn)': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.param(function(name, fn){
|
||||
if (fn instanceof RegExp) {
|
||||
return function(req, res, next, val){
|
||||
var captures;
|
||||
if (captures = fn.exec(String(val))) {
|
||||
req.params[name] = captures[1];
|
||||
next();
|
||||
} else {
|
||||
next('route');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.param('commit', /^(\d+)$/);
|
||||
|
||||
app.get('/commit/:commit', function(req, res){
|
||||
res.send(req.params.commit);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/commit/12' },
|
||||
{ body: '12' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/commit/asdf' },
|
||||
{ status: 404 });
|
||||
},
|
||||
|
||||
'test precedence': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
var hits = [];
|
||||
|
||||
app.all('*', function(req, res, next){
|
||||
hits.push('all');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/foo', function(req, res, next){
|
||||
hits.push('GET /foo');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/foo', function(req, res, next){
|
||||
hits.push('GET /foo2');
|
||||
next();
|
||||
});
|
||||
|
||||
app.put('/foo', function(req, res, next){
|
||||
hits.push('PUT /foo');
|
||||
next();
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/foo' },
|
||||
function(){
|
||||
hits.should.eql(['all', 'GET /foo', 'GET /foo2']);
|
||||
});
|
||||
},
|
||||
|
||||
'test named capture groups': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
@@ -81,6 +352,22 @@ module.exports = {
|
||||
res.send('user ' + req.params.id);
|
||||
});
|
||||
|
||||
app.post('/pin/save/:lat(\\d+.\\d+)/:long(\\d+.\\d+)', function(req, res){
|
||||
res.send(req.params.lat + ' ' + req.params.long);
|
||||
});
|
||||
|
||||
app.post('/pin/save2/:lat([0-9]+.[0-9]+)/:long([0-9]+.[0-9]+)', function(req, res){
|
||||
res.send(req.params.lat + ' ' + req.params.long);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/pin/save/1.2/3.4', method: 'POST' },
|
||||
{ body: '1.2 3.4' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/pin/save2/1.2/3.4', method: 'POST' },
|
||||
{ body: '1.2 3.4' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12' },
|
||||
{ body: 'user 12' });
|
||||
@@ -90,7 +377,51 @@ module.exports = {
|
||||
{ body: 'Cannot GET /user/ab' });
|
||||
},
|
||||
|
||||
'test .param()': function(){
|
||||
'test named capture group after dot': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/user/:name.:format?', function(req, res){
|
||||
res.send(req.params.name + ' - ' + (req.params.format || ''));
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/foo' },
|
||||
{ body: 'foo - ' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/foo.json' },
|
||||
{ body: 'foo - json' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/foo.bar.json' },
|
||||
{ body: 'foo.bar - json' });
|
||||
},
|
||||
|
||||
'test optional * value': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/admin*', function(req, res){
|
||||
res.send(req.params[0]);
|
||||
});
|
||||
|
||||
app.get('/file/*.*', function(req, res){
|
||||
res.send(req.params[0] + ' - ' + req.params[1]);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/file/some.foo.bar' },
|
||||
{ body: 'some.foo - bar' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/admin', },
|
||||
{ body: '', status: 200 });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/adminify', },
|
||||
{ body: 'ify', status: 200 });
|
||||
},
|
||||
|
||||
'test app.param()': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
var users = [
|
||||
@@ -101,9 +432,6 @@ module.exports = {
|
||||
, { name: 'bandit' }
|
||||
];
|
||||
|
||||
function integer(n){ return parseInt(n, 10); };
|
||||
app.param(['to', 'from'], integer);
|
||||
|
||||
app.param('user', function(req, res, next, id){
|
||||
if (req.user = users[id]) {
|
||||
next();
|
||||
@@ -116,13 +444,6 @@ module.exports = {
|
||||
res.send('user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.get('/users/:from-:to', function(req, res, next){
|
||||
var names = users.slice(req.params.from, req.params.to).map(function(user){
|
||||
return user.name;
|
||||
});
|
||||
res.send('users ' + names.join(', '));
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/0' },
|
||||
{ body: 'user tj' });
|
||||
@@ -130,10 +451,35 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/user/1' },
|
||||
{ body: 'user tobi' });
|
||||
|
||||
},
|
||||
|
||||
'test app.param() optional execution': function(beforeExit){
|
||||
var app = express.createServer()
|
||||
, calls = 0;
|
||||
|
||||
var months = ['Jan', 'Feb', 'Mar'];
|
||||
|
||||
app.param('month', function(req, res, next, n){
|
||||
req.params.month = months[n];
|
||||
++calls;
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/calendar/:month?', function(req, res, next){
|
||||
res.send(req.params.month || months[0]);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/users/0-3' },
|
||||
{ body: 'users tj, tobi, loki' });
|
||||
{ url: '/calendar' },
|
||||
{ body: 'Jan' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/calendar/1' },
|
||||
{ body: 'Feb' });
|
||||
|
||||
beforeExit(function(){
|
||||
calls.should.equal(1);
|
||||
});
|
||||
},
|
||||
|
||||
'test OPTIONS': function(){
|
||||
@@ -162,12 +508,11 @@ module.exports = {
|
||||
|
||||
var route = app.get('/user/:id')[0]
|
||||
route.should.be.an.instanceof(Route);
|
||||
route.callback.should.be.a('function');
|
||||
route.callbacks.should.be.an.instanceof(Array);
|
||||
route.path.should.equal('/user/:id');
|
||||
route.regexp.should.be.an.instanceof(RegExp);
|
||||
route.method.should.equal('GET');
|
||||
route.index.should.equal(1);
|
||||
route.keys.should.eql(['id']);
|
||||
route.method.should.equal('get');
|
||||
route.keys.should.eql([{ name: 'id', optional: false }]);
|
||||
|
||||
app.get('/user').should.have.length(1);
|
||||
app.get('/user/:id').should.have.length(1);
|
||||
@@ -225,13 +570,11 @@ module.exports = {
|
||||
|
||||
var route = app.match.get('/user/12')[0];
|
||||
route.should.be.an.instanceof(Route);
|
||||
route.callback.should.be.a('function');
|
||||
route.path.should.equal('/user/:id');
|
||||
route.regexp.should.be.an.instanceof(RegExp);
|
||||
route.method.should.equal('GET');
|
||||
route.index.should.equal(2);
|
||||
route.keys.should.eql(['id']);
|
||||
route.params.id.should.equal('12');
|
||||
route.method.should.equal('get');
|
||||
route.keys.should.eql([{ name: 'id', optional: false }]);
|
||||
//route.params.id.should.equal('12');
|
||||
|
||||
app.match.get('/user').should.have.length(1);
|
||||
app.match.get('/user/12').should.have.length(2);
|
||||
@@ -242,7 +585,54 @@ module.exports = {
|
||||
app.match.all('/user/123').should.have.length(3);
|
||||
app.match('/user/123').should.have.length(3);
|
||||
},
|
||||
|
||||
|
||||
'test app.routes.all()': function(){
|
||||
var app = express.createServer();
|
||||
app.get('/user', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/edit', function(){});
|
||||
app.routes.all()[0].should.be.an.instanceof(Route);
|
||||
app.routes.all().length.should.equal(5);
|
||||
},
|
||||
|
||||
'test Collection': function(){
|
||||
var app = express.createServer();
|
||||
app.get('/user', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/edit', function(){});
|
||||
|
||||
var ret = app.match.all('/user/12').remove();
|
||||
ret.should.have.length(3);
|
||||
app.match.all('/user/12').should.have.length(0);
|
||||
app.get('/user/:id').should.have.length(0);
|
||||
},
|
||||
|
||||
'test "strict routing" setting': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.enable('strict routing');
|
||||
|
||||
app.get('/:path', function(req, res, next){
|
||||
res.send({ type: 'directory' });
|
||||
});
|
||||
|
||||
app.get('/:path/', function(req, res, next){
|
||||
res.send(['.', '..', 'foo.js', 'bar.js']);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/lib' },
|
||||
{ body: '{"type":"directory"}' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/lib/' },
|
||||
{ body: '[".","..","foo.js","bar.js"]' });
|
||||
},
|
||||
|
||||
'test "case sensitive routes" setting': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
@@ -263,5 +653,173 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/Account' },
|
||||
{ body: 'Account' });
|
||||
},
|
||||
|
||||
'override OPTIONS default': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
|
||||
});
|
||||
|
||||
app.options('/foo', function(req, res, next){
|
||||
res.header('Allow', 'GET')
|
||||
res.send('whatever');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', method: 'OPTIONS' },
|
||||
{ body: 'GET', headers: { Allow: 'GET' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/foo', method: 'OPTIONS' },
|
||||
{ body: 'whatever', headers: { Allow: 'GET' }});
|
||||
},
|
||||
|
||||
'test req.route': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
var routes = [];
|
||||
|
||||
app.get('/:foo?', function(req, res, next){
|
||||
routes.push(req.route.path);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/foo', function(req, res, next){
|
||||
routes.push(req.route.path);
|
||||
next();
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/foo' },
|
||||
function(){
|
||||
routes.should.eql(['/:foo?', '/foo']);
|
||||
});
|
||||
},
|
||||
|
||||
'test route callback error handling': function(){
|
||||
var app = express.createServer()
|
||||
, calls = [];
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
calls.push('one');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
calls.push('two');
|
||||
next(new Error('fail'));
|
||||
});
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
calls.push('three');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/*', function(err, req, res, next){
|
||||
res.statusCode = 500;
|
||||
res.send('error: ' + err.message);
|
||||
});
|
||||
|
||||
app.get('/user/*', function(req, res, next){
|
||||
calls.push('four');
|
||||
next();
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12' },
|
||||
{ body: 'error: fail' }, function(){
|
||||
calls.should.eql(['one', 'two']);
|
||||
});
|
||||
},
|
||||
|
||||
'test route callback thrown error handling': function(){
|
||||
var app = express.createServer()
|
||||
, calls = [];
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
calls.push('one');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
calls.push('two');
|
||||
throw new Error('fail');
|
||||
});
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
calls.push('three');
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/*', function(err, req, res, next){
|
||||
res.statusCode = 500;
|
||||
res.send('error: ' + err.message);
|
||||
});
|
||||
|
||||
app.get('/user/*', function(req, res, next){
|
||||
calls.push('four');
|
||||
next();
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12' },
|
||||
{ body: 'error: fail' }, function(){
|
||||
calls.should.eql(['one', 'two']);
|
||||
});
|
||||
},
|
||||
|
||||
'test route callback error recovery': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/user/:id', function(req, res, next){
|
||||
next(new Error('fail'));
|
||||
});
|
||||
|
||||
app.get('/user/*', function(err, req, res, next){
|
||||
req.error = err;
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/user/*', function(req, res, next){
|
||||
res.send('recovered from error: ' + req.error.message);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12' },
|
||||
{ body: 'recovered from error: fail' });
|
||||
},
|
||||
|
||||
'test multiple param callbacks': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.param('user', function(req, res, next, id){
|
||||
req.user = { id: id };
|
||||
next();
|
||||
});
|
||||
|
||||
app.param('forum_id', function(req, res, next, id){
|
||||
req.forum = { id: id };
|
||||
next();
|
||||
});
|
||||
|
||||
app.param('thread_id', function(req, res, next, id){
|
||||
req.thread = { id: id };
|
||||
next();
|
||||
});
|
||||
|
||||
function array(req, res, next) {
|
||||
req.arr = [req.user.id, req.forum.id, req.thread.id];
|
||||
next();
|
||||
}
|
||||
|
||||
app.get('/:user/:forum_id/:thread_id', array, function(req, res){
|
||||
res.send(req.arr);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/1/2/3' },
|
||||
{ body: '["1","2","3"]' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -453,19 +453,6 @@ module.exports = {
|
||||
{ url: '/user' },
|
||||
{ body: '<p>tj</p>' });
|
||||
|
||||
// as: this collection option
|
||||
app.get('/person', function(req, res){
|
||||
res.partial('person.jade', {
|
||||
as: this,
|
||||
collection: [{ name: 'tj' }],
|
||||
locals: { label: 'name:' }
|
||||
});
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/person' },
|
||||
{ body: '<p>name: tj</p>' });
|
||||
|
||||
// as: global collection option
|
||||
app.get('/videos', function(req, res){
|
||||
res.partial('video.jade', {
|
||||
@@ -513,19 +500,6 @@ module.exports = {
|
||||
{ url: '/video-global' },
|
||||
{ body: '<p>Tim Burton</p>' });
|
||||
|
||||
app.get('/person-this', function(req, res){
|
||||
res.partial('person.jade', {
|
||||
object: { name: 'tj' },
|
||||
locals: { label: 'User:' },
|
||||
as: this
|
||||
});
|
||||
});
|
||||
|
||||
// Non-collection as: this
|
||||
assert.response(app,
|
||||
{ url: '/person-this' },
|
||||
{ body: '<p>User: tj</p>' });
|
||||
|
||||
// No options
|
||||
app.get('/nothing', function(req, res){
|
||||
res.partial('hello.ejs');
|
||||
@@ -668,6 +642,67 @@ module.exports = {
|
||||
{ body: '<p>two</p>' });
|
||||
},
|
||||
|
||||
'test #partial() relative lookup with "view engine"': function(){
|
||||
var app = create();
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.render('forum/thread', { layout: false });
|
||||
});
|
||||
|
||||
app.get('/2', function(req, res, next){
|
||||
res.render('forum/../forum/thread', { layout: false });
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/2' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
},
|
||||
|
||||
'test #partial() relative lookup without "view engine"': function(){
|
||||
var app = create();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.render('forum/thread.jade', { layout: false });
|
||||
});
|
||||
|
||||
app.get('/2', function(req, res, next){
|
||||
res.render('forum/../forum/thread.jade', { layout: false });
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/2' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
},
|
||||
|
||||
'test #partial() relative lookup': function(){
|
||||
var app = create();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.partial('forum/thread.jade');
|
||||
});
|
||||
|
||||
app.get('/2', function(req, res, next){
|
||||
res.partial('forum/../forum/thread.jade');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/2' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
},
|
||||
|
||||
'test #partial() with several calls': function(){
|
||||
var app = create();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user