mirror of
https://github.com/expressjs/express.git
synced 2026-02-27 19:20:15 +00:00
Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
131f658779 | ||
|
|
9f2bd30dc7 | ||
|
|
6e633b31b4 | ||
|
|
1c65643488 | ||
|
|
388ad9067a | ||
|
|
f470f0bdc5 | ||
|
|
72384b0523 | ||
|
|
1b199b7d98 | ||
|
|
09b384ea44 | ||
|
|
56ae55f987 | ||
|
|
1c360a89ba | ||
|
|
8636dee13e | ||
|
|
70e6baf6fc | ||
|
|
3588c1eedc | ||
|
|
8d6f167a81 | ||
|
|
6106188347 | ||
|
|
eeb77541cd | ||
|
|
99b244b47c | ||
|
|
3043672448 | ||
|
|
0477a53c9f | ||
|
|
d9aa7c3bc9 | ||
|
|
986fac583b | ||
|
|
c6d76086e2 | ||
|
|
e2771364eb | ||
|
|
0d5a63798b | ||
|
|
7d15e2bf52 | ||
|
|
31fef407b6 | ||
|
|
6bef3ef891 | ||
|
|
b806846049 | ||
|
|
bc16020976 | ||
|
|
8afb905a43 | ||
|
|
53667728a8 | ||
|
|
5f0a854e29 | ||
|
|
e9ef3dd9cd | ||
|
|
f702884704 | ||
|
|
0cb866845d | ||
|
|
26483029db | ||
|
|
d2adcbdf67 | ||
|
|
d2f963db2a | ||
|
|
fc2bc1362f | ||
|
|
6ae45d0fd3 | ||
|
|
cc185a8c0e | ||
|
|
ae1078944c | ||
|
|
385a05dd10 | ||
|
|
2572a78648 | ||
|
|
470774cfba | ||
|
|
351f6abe4c | ||
|
|
53a16e1795 | ||
|
|
ff77c8b205 | ||
|
|
b33f38b109 | ||
|
|
b9596d7ce8 | ||
|
|
251175c025 | ||
|
|
fda1bc4630 | ||
|
|
83c2c176a9 | ||
|
|
9be5992f22 | ||
|
|
d8d23c0bf8 | ||
|
|
b2689fc40e | ||
|
|
a4cfde350f | ||
|
|
7374027457 | ||
|
|
63328c2177 | ||
|
|
c4e2ce23e5 | ||
|
|
dacad53b2e | ||
|
|
4ffd5280a7 | ||
|
|
74310fb464 | ||
|
|
8b2268cf38 | ||
|
|
fb655f4981 | ||
|
|
7208c33d72 | ||
|
|
4efb25d048 | ||
|
|
a3678cd7f6 | ||
|
|
393d38f1ab | ||
|
|
805b9ac3a9 | ||
|
|
379b9812be | ||
|
|
a9992b5647 | ||
|
|
b6c0a9b1b5 | ||
|
|
1d2dd2a375 | ||
|
|
63db694aa2 | ||
|
|
b6aca36ad9 | ||
|
|
8420ae93fd | ||
|
|
6722716fa7 | ||
|
|
658e064220 | ||
|
|
66b472e567 | ||
|
|
3d242a607e | ||
|
|
bc68e5e7a3 | ||
|
|
aa6858189c | ||
|
|
06fda62c9e | ||
|
|
5688ea650d | ||
|
|
f5c4e9d612 | ||
|
|
b9eda2a59d | ||
|
|
8c168b0971 | ||
|
|
9c20a50ee2 | ||
|
|
eac666574e | ||
|
|
e4c840f6b8 | ||
|
|
3afbcd0acf | ||
|
|
8f054dbcaf | ||
|
|
ccc39e5aa2 | ||
|
|
53d4da2a9c | ||
|
|
d9e7153fc9 | ||
|
|
dc02b0d5ae | ||
|
|
e0bc5711b8 | ||
|
|
957cf45fa1 | ||
|
|
f4487343df | ||
|
|
ca1bdb31e3 | ||
|
|
236a412459 | ||
|
|
759a57bdb6 | ||
|
|
1abb674a07 | ||
|
|
961146a287 | ||
|
|
573f940985 | ||
|
|
882916bb2e | ||
|
|
47d1c62732 | ||
|
|
d39293c025 | ||
|
|
2942dafdfd | ||
|
|
4e1aefa5b5 | ||
|
|
89383ecc57 | ||
|
|
08046f7692 | ||
|
|
885fb1fa92 | ||
|
|
6a58c71528 | ||
|
|
371d66ba2a | ||
|
|
25bddf3fb5 | ||
|
|
f6e9fb13f8 | ||
|
|
f0df8434e7 | ||
|
|
e4d3f239e5 | ||
|
|
bcc22dfa6f | ||
|
|
f614709a01 | ||
|
|
11250d22c9 | ||
|
|
4b4de29725 | ||
|
|
99b49d2718 | ||
|
|
6230ec55be | ||
|
|
0733d3c585 | ||
|
|
6f8370ff0e | ||
|
|
bc244ed07e | ||
|
|
41266aa8e4 |
69
History.md
69
History.md
@@ -1,11 +1,78 @@
|
||||
|
||||
2.3.5 / 2011-05-20
|
||||
==================
|
||||
|
||||
* Added export `.view` as alias for `.View`
|
||||
|
||||
2.3.4 / 2011-05-08
|
||||
==================
|
||||
|
||||
* Added `./examples/say`
|
||||
* Fixed `res.sendfile()` bug preventing the transfer of files with spaces
|
||||
|
||||
2.3.3 / 2011-05-03
|
||||
==================
|
||||
|
||||
* Added "case sensitive routes" option.
|
||||
* Changed; split methods supported per rfc [slaskis]
|
||||
* Fixed route-specific middleware when using the same callback function several times
|
||||
|
||||
2.3.2 / 2011-04-27
|
||||
==================
|
||||
|
||||
* Fixed view hints
|
||||
|
||||
2.3.1 / 2011-04-26
|
||||
==================
|
||||
|
||||
* Added `app.match()` as `app.match.all()`
|
||||
* Added `app.lookup()` as `app.lookup.all()`
|
||||
* Added `app.remove()` for `app.remove.all()`
|
||||
* Added `app.remove.VERB()`
|
||||
* Fixed template caching collision issue. Closes #644
|
||||
* Moved router over from connect and started refactor
|
||||
|
||||
2.3.0 / 2011-04-25
|
||||
==================
|
||||
|
||||
* Added options support to `res.clearCookie()`
|
||||
* Added `res.helpers()` as alias of `res.locals()`
|
||||
* Added; json defaults to UTF-8 with `res.send()`. Closes #632. [Daniel * Dependency `connect >= 1.4.0`
|
||||
* Changed; auto set Content-Type in res.attachement [Aaron Heckmann]
|
||||
* Renamed "cache views" to "view cache". Closes #628
|
||||
* Fixed caching of views when using several apps. Closes #637
|
||||
* Fixed gotcha invoking `app.param()` callbacks once per route middleware.
|
||||
Closes #638
|
||||
* Fixed partial lookup precedence. Closes #631
|
||||
Shaw]
|
||||
|
||||
2.2.2 / 2011-04-12
|
||||
==================
|
||||
|
||||
* Added second callback support for `res.download()` connection errors
|
||||
* Fixed `filename` option passing to template engine
|
||||
|
||||
2.2.1 / 2011-04-04
|
||||
==================
|
||||
|
||||
* Added `layout(path)` helper to change the layout within a view. Closes #610
|
||||
* Fixed `partial()` collection object support.
|
||||
Previously only anything with `.length` would work.
|
||||
When `.length` is present one must still be aware of holes,
|
||||
however now `{ collection: {foo: 'bar'}}` is valid, exposes
|
||||
`keyInCollection` and `keysInCollection`.
|
||||
|
||||
* Performance improved with better view caching
|
||||
* Removed `request` and `response` locals
|
||||
* Changed; errorHandler page title is now `Express` instead of `Connect`
|
||||
|
||||
2.2.0 / 2011-03-30
|
||||
==================
|
||||
|
||||
* Added `app.lookup.VERB()`, ex `app.lookup.put('/user/:id')`. Closes #606
|
||||
* Added `app.match.VERB()`, ex `app.match.put('/user/12')`. Closes #606
|
||||
* Added `app.VERB(path)` as alias of `app.lookup.VERB()`.
|
||||
* Dependency `connect >= 1.1.6`
|
||||
* Dependency `connect >= 1.2.0`
|
||||
|
||||
2.1.1 / 2011-03-29
|
||||
==================
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
$ npm install express
|
||||
|
||||
or to access the `express(1)` executable install globally:
|
||||
|
||||
$ npm install -g express
|
||||
|
||||
## Features
|
||||
|
||||
* Robust routing
|
||||
@@ -54,6 +58,7 @@ The following are the major contributors of Express (in no specific order).
|
||||
|
||||
## More Information
|
||||
|
||||
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js with ease
|
||||
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
|
||||
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
|
||||
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
|
||||
|
||||
@@ -11,7 +11,7 @@ var fs = require('fs')
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
var version = '2.2.0';
|
||||
var version = '2.3.5';
|
||||
|
||||
/**
|
||||
* Add session support.
|
||||
|
||||
178
docs/guide.html
178
docs/guide.html
@@ -232,6 +232,8 @@
|
||||
<li><a href="#app.error()">error()</a></li>
|
||||
<li><a href="#app.helpers()">helpers()</a></li>
|
||||
<li><a href="#app.dynamichelpers()">dynamicHelpers()</a></li>
|
||||
<li><a href="#app.lookup">lookup</a></li>
|
||||
<li><a href="#app.match">match</a></li>
|
||||
<li><a href="#app.mounted()">mounted()</a></li>
|
||||
<li><a href="#app.register()">register()</a></li>
|
||||
<li><a href="#app.listen()">listen()</a></li>
|
||||
@@ -338,6 +340,8 @@ app.configure('production', function(){
|
||||
<li><em>views</em> Root views directory defaulting to <strong>CWD/views</strong></li>
|
||||
<li><em>view engine</em> Default view engine name for views rendered without extensions</li>
|
||||
<li><em>view options</em> An object specifying global view options</li>
|
||||
<li><em>view cache</em> Enable view caching (enabled in production)</li>
|
||||
<li><em>case sensitive routes</em> Enable case-sensitive routing</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -429,7 +433,7 @@ app.post('/', function(req, res){
|
||||
app.listen(3000);
|
||||
</code></pre>
|
||||
|
||||
<p>Typically we may use a “dumb” placeholder such as “/user/:id” which has no restrictions, however say for example we are limiting a user id to digits, we may use <em>‘/user/:id(\d+)’</em> which will <em>not</em> match unless the placeholder value contains only digits.</p>
|
||||
<p>Typically we may use a “dumb” placeholder such as “/user/:id” which has no restrictions, however say for example we are limiting a user id to digits, we may use <em>‘/user/:id([0-9]+)’</em> which will <em>not</em> match unless the placeholder value contains only digits.</p>
|
||||
|
||||
<h3 id="passing-route control">Passing Route Control</h3>
|
||||
|
||||
@@ -517,6 +521,34 @@ app.use(connect.bodyParser());
|
||||
app.use(express.bodyParser());
|
||||
</code></pre>
|
||||
|
||||
<p>Middleware ordering is important, when Connect receives a request the <em>first</em> middleware we pass to <em>createServer()</em> or <em>use()</em> is executed with three parameters, <em>request</em>, <em>response</em>, and a callback function usually named <em>next</em>. When <em>next()</em> is invoked the second middleware will then have it’s turn and so on. This is important to note because many middleware depend on each other, for example <em>methodOverride()</em> checks <em>req.body.</em>method<em> for the HTTP method override, however </em>bodyParser()<em> parses the request body and populates </em>req.body<em>. Another example of this is cookie parsing and session support, we must first </em>use()<em> </em>cookieParser()<em> followed by </em>session()_.</p>
|
||||
|
||||
<p>Many Express applications may contain the line <em>app.use(app.router)</em>, while this may appear strange, it’s simply the middleware function that contains all defined routes, and performs route lookup based on the current request url and HTTP method. Express allows you to position this middleware, though by default it will be added to the bottom. By positioning the router, we can alter middleware precedence, for example we may want to add error reporting as the <em>last</em> middleware so that any exception passed to <em>next()</em> will be handled by it, or perhaps we want static file serving to have low precedence, allowing our routes to intercept requests to a static file to count downloads etc. This may look a little like below</p>
|
||||
|
||||
<pre><code>app.use(express.logger(...));
|
||||
app.use(express.bodyParser(...));
|
||||
app.use(express.cookieParser(...));
|
||||
app.use(express.session(...));
|
||||
app.use(app.router);
|
||||
app.use(express.static(...));
|
||||
app.use(express.errorHandler(...));
|
||||
</code></pre>
|
||||
|
||||
<p>First we add <em>logger()</em> so that it may wrap node’s <em>req.end()</em> method, providing us with response-time data. Next the request’s body will be parsed (if any), followed by cookie parsing and session support, meaning <em>req.session</em> will be defined by the time we hit our routes in <em>app.router</em>. If a request such as <em>GET /javascripts/jquery.js</em> is handled by our routes, and we do not call <em>next()</em> then the <em>static()</em> middleware will never see this request, however if were to define a route as shown below, we can record stats, refuse downloads, consume download credits etc.</p>
|
||||
|
||||
<pre><code>var downloads = {};
|
||||
|
||||
app.use(app.router);
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
app.get('/*', function(req, res, next){
|
||||
var file = req.params[0];
|
||||
downloads[file] = downloads[file] || 0;
|
||||
downloads[file]++;
|
||||
next();
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h3 id="route-middleware">Route Middleware</h3>
|
||||
|
||||
<p>Routes may utilize route-specific middleware by passing one or more additional callbacks (or arrays) to the method. This feature is extremely useful for restricting access, loading data used by the route etc.</p>
|
||||
@@ -593,6 +625,8 @@ app.get('/', all, function(){});
|
||||
|
||||
<p>For this example in full, view the <a href="http://github.com/visionmedia/express/blob/master/examples/route-middleware/app.js">route middleware example</a> in the repository.</p>
|
||||
|
||||
<p>There are times when we may want to “skip” passed remaining route middleware, but continue matching subsequent routes. To do this we invoke <code>next()</code> with the string “route” <code>next('route')</code>. If no remaining routes match the request url then Express will respond with 404 Not Found.</p>
|
||||
|
||||
<h3 id="http-methods">HTTP Methods</h3>
|
||||
|
||||
<p>We have seen <em>app.get()</em> a few times, however Express also exposes other familiar HTTP verbs in the same manor, such as <em>app.post()</em>, <em>app.del()</em>, etc.</p>
|
||||
@@ -1153,7 +1187,7 @@ an error occurs, or when the transfer is complete. By default failures call <cod
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h3 id="res.download()">res.download(file[, filename[, callback]])</h3>
|
||||
<h3 id="res.download()">res.download(file[, filename[, callback[, callback2]]])</h3>
|
||||
|
||||
<p>Transfer the given <em>file</em> as an attachment with optional alternative <em>filename</em>.</p>
|
||||
|
||||
@@ -1167,13 +1201,22 @@ res.download('path/to/image.png', 'foo.png');
|
||||
res.sendfile(file);
|
||||
</code></pre>
|
||||
|
||||
<p>An optional callback may be supplied as either the second or third argument, which is passed to <em>res.sendfile()</em>:</p>
|
||||
<p>An optional callback may be supplied as either the second or third argument, which is passed to <em>res.sendfile()</em>. Within this callback you may still respond, as the header has not been sent.</p>
|
||||
|
||||
<pre><code>res.download(path, 'expenses.doc', function(err){
|
||||
// handle
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p>An optional second callback, <em>callback2</em> may be given to allow you to act on connection related errors, however you should not attempt to respond.</p>
|
||||
|
||||
<pre><code>res.download(path, function(err){
|
||||
// error or finished
|
||||
}, function(err){
|
||||
// connection related error
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h3 id="res.send()">res.send(body|status[, headers|status[, status]])</h3>
|
||||
|
||||
<p>The <em>res.send()</em> method is a high level response utility allowing you to pass
|
||||
@@ -1222,7 +1265,7 @@ res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOn
|
||||
<pre><code>res.cookie('rememberme', 'yes', { maxAge: 900000 });
|
||||
</code></pre>
|
||||
|
||||
<p>To parse incoming <em>Cookie</em> headers, use the <em>cookieDecoder</em> middleware, which provides the <em>req.cookies</em> object:</p>
|
||||
<p>To parse incoming <em>Cookie</em> headers, use the <em>cookieParser</em> middleware, which provides the <em>req.cookies</em> object:</p>
|
||||
|
||||
<pre><code>app.use(express.cookieParser());
|
||||
|
||||
@@ -1231,7 +1274,7 @@ app.get('/', function(req, res){
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h3 id="res.clearcookie()">res.clearCookie(name)</h3>
|
||||
<h3 id="res.clearcookie()">res.clearCookie(name[, options])</h3>
|
||||
|
||||
<p>Clear cookie <em>name</em> by setting “expires” far in the past.</p>
|
||||
|
||||
@@ -1506,21 +1549,28 @@ should call <em>next(err)</em> if it does not wish to deal with the exception:</
|
||||
|
||||
<p>Registers static view helpers.</p>
|
||||
|
||||
<p> app.helpers({</p>
|
||||
|
||||
<pre><code> name: function(first, last){ return first + ', ' + last }
|
||||
, firstName: 'tj'
|
||||
, lastName: 'holowaychuk'
|
||||
<pre><code>app.helpers({
|
||||
name: function(first, last){ return first + ', ' + last }
|
||||
, firstName: 'tj'
|
||||
, lastName: 'holowaychuk'
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<p> });</p>
|
||||
|
||||
<p>Our view could now utilize the <em>firstName</em> and <em>lastName</em> variables,
|
||||
as well as the <em>name()</em> function exposed.</p>
|
||||
|
||||
<pre><code><%= name(firstName, lastName) %>
|
||||
</code></pre>
|
||||
|
||||
<p>Express also provides a few locals by default:</p>
|
||||
|
||||
<pre><code>- `settings` the app's settings object
|
||||
- `filename` the view's filename
|
||||
- `layout(path)` specify the layout from within a view
|
||||
</code></pre>
|
||||
|
||||
<p>This method is aliased as <em>app.locals()</em>.</p>
|
||||
|
||||
<h3 id="app.dynamichelpers()">app.dynamicHelpers(obj)</h3>
|
||||
|
||||
<p>Registers dynamic view helpers. Dynamic view helpers
|
||||
@@ -1540,6 +1590,110 @@ becomes the local variable it is associated with.</p>
|
||||
<pre><code><%= session.name %>
|
||||
</code></pre>
|
||||
|
||||
<h3 id="app.lookup">app.lookup</h3>
|
||||
|
||||
<p> The <em>app.lookup</em> http methods returns an array of callback functions
|
||||
associated with the given <em>path</em>.</p>
|
||||
|
||||
<p> Suppose we define the following routes:</p>
|
||||
|
||||
<pre><code> app.get('/user/:id', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
</code></pre>
|
||||
|
||||
<p> We can utilize this lookup functionality to check which routes
|
||||
have been defined, which can be extremely useful for higher level
|
||||
frameworks built on Express.</p>
|
||||
|
||||
<pre><code> app.lookup.get('/user/:id');
|
||||
// => [Function]
|
||||
|
||||
app.lookup.get('/user/:id/:op?');
|
||||
// => [Function]
|
||||
|
||||
app.lookup.put('/user/:id');
|
||||
// => [Function]
|
||||
|
||||
app.lookup.all('/user/:id');
|
||||
// => [Function, Function]
|
||||
|
||||
app.lookup.all('/hey');
|
||||
// => []
|
||||
</code></pre>
|
||||
|
||||
<p> To alias <em>app.lookup.VERB()</em>, we can simply invoke <em>app.VERB()</em>
|
||||
without a callback, as a shortcut, for example the following are
|
||||
equivalent:</p>
|
||||
|
||||
<pre><code> app.lookup.get('/user');
|
||||
app.get('/user');
|
||||
</code></pre>
|
||||
|
||||
<p> Each function returned has the following properties:</p>
|
||||
|
||||
<pre><code> var fn = app.get('/user/:id/:op?')[0];
|
||||
|
||||
fn.regexp
|
||||
// => /^\/user\/(?:([^\/]+?))(?:\/([^\/]+?))?\/?$/i
|
||||
|
||||
fn.keys
|
||||
// => ['id', 'op']
|
||||
|
||||
fn.path
|
||||
// => '/user/:id/:op?'
|
||||
|
||||
fn.method
|
||||
// => 'GET'
|
||||
</code></pre>
|
||||
|
||||
<h3 id="app.match">app.match</h3>
|
||||
|
||||
<p> The <em>app.match</em> http methods return an array of callback functions
|
||||
which match the given <em>url</em>, which may include a query string etc. This
|
||||
is useful when you want reflect on which routes have the opportunity to
|
||||
respond.</p>
|
||||
|
||||
<p> Suppose we define the following routes:</p>
|
||||
|
||||
<pre><code> app.get('/user/:id', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
</code></pre>
|
||||
|
||||
<p> Our match against <strong>GET</strong> will return two functions, since the <em>:op</em>
|
||||
in our second route is optional.</p>
|
||||
|
||||
<pre><code> app.match.get('/user/1');
|
||||
// => [Function, Function]
|
||||
</code></pre>
|
||||
|
||||
<p> This second call returns only the callback for <em>/user/:id/:op?</em>.</p>
|
||||
|
||||
<pre><code> app.match.get('/user/23/edit');
|
||||
// => [Function]
|
||||
</code></pre>
|
||||
|
||||
<p> We can also use <em>all()</em> to disregard the http method:</p>
|
||||
|
||||
<pre><code> app.match.all('/user/20');
|
||||
// => [Function, Function, Function]
|
||||
</code></pre>
|
||||
|
||||
<p> Each function matched has the following properties:</p>
|
||||
|
||||
<pre><code> var fn = app.match.get('/user/23/edit')[0];
|
||||
|
||||
fn.keys
|
||||
// => ['id', 'op']
|
||||
|
||||
fn.params
|
||||
// => { id: '23', op: 'edit' }
|
||||
|
||||
fn.method
|
||||
// => 'GET'
|
||||
</code></pre>
|
||||
|
||||
<h3 id="app.mounted()">app.mounted(fn)</h3>
|
||||
|
||||
<p>Assign a callback <em>fn</em> which is called when this <em>Server</em> is passed to <em>Server#use()</em>.</p>
|
||||
|
||||
146
docs/guide.md
146
docs/guide.md
@@ -82,6 +82,8 @@ Express supports the following settings out of the box:
|
||||
* _views_ Root views directory defaulting to **CWD/views**
|
||||
* _view engine_ Default view engine name for views rendered without extensions
|
||||
* _view options_ An object specifying global view options
|
||||
* _view cache_ Enable view caching (enabled in production)
|
||||
* _case sensitive routes_ Enable case-sensitive routing
|
||||
|
||||
### Routing
|
||||
|
||||
@@ -165,7 +167,7 @@ For example we can __POST__ some json, and echo the json back using the _bodyPar
|
||||
|
||||
app.listen(3000);
|
||||
|
||||
Typically we may use a "dumb" placeholder such as "/user/:id" which has no restrictions, however say for example we are limiting a user id to digits, we may use _'/user/:id(\\d+)'_ which will _not_ match unless the placeholder value contains only digits.
|
||||
Typically we may use a "dumb" placeholder such as "/user/:id" which has no restrictions, however say for example we are limiting a user id to digits, we may use _'/user/:id([0-9]+)'_ which will _not_ match unless the placeholder value contains only digits.
|
||||
|
||||
### Passing Route Control
|
||||
|
||||
@@ -247,6 +249,33 @@ This is somewhat annoying, so express re-exports these middleware properties, ho
|
||||
app.use(express.logger());
|
||||
app.use(express.bodyParser());
|
||||
|
||||
Middleware ordering is important, when Connect receives a request the _first_ middleware we pass to _createServer()_ or _use()_ is executed with three parameters, _request_, _response_, and a callback function usually named _next_. When _next()_ is invoked the second middleware will then have it's turn and so on. This is important to note because many middleware depend on each other, for example _methodOverride()_ checks _req.body._method_ for the HTTP method override, however _bodyParser()_ parses the request body and populates _req.body_. Another example of this is cookie parsing and session support, we must first _use()_ _cookieParser()_ followed by _session()_.
|
||||
|
||||
Many Express applications may contain the line _app.use(app.router)_, while this may appear strange, it's simply the middleware function that contains all defined routes, and performs route lookup based on the current request url and HTTP method. Express allows you to position this middleware, though by default it will be added to the bottom. By positioning the router, we can alter middleware precedence, for example we may want to add error reporting as the _last_ middleware so that any exception passed to _next()_ will be handled by it, or perhaps we want static file serving to have low precedence, allowing our routes to intercept requests to a static file to count downloads etc. This may look a little like below
|
||||
|
||||
app.use(express.logger(...));
|
||||
app.use(express.bodyParser(...));
|
||||
app.use(express.cookieParser(...));
|
||||
app.use(express.session(...));
|
||||
app.use(app.router);
|
||||
app.use(express.static(...));
|
||||
app.use(express.errorHandler(...));
|
||||
|
||||
First we add _logger()_ so that it may wrap node's _req.end()_ method, providing us with response-time data. Next the request's body will be parsed (if any), followed by cookie parsing and session support, meaning _req.session_ will be defined by the time we hit our routes in _app.router_. If a request such as _GET /javascripts/jquery.js_ is handled by our routes, and we do not call _next()_ then the _static()_ middleware will never see this request, however if were to define a route as shown below, we can record stats, refuse downloads, consume download credits etc.
|
||||
|
||||
var downloads = {};
|
||||
|
||||
app.use(app.router);
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
app.get('/*', function(req, res, next){
|
||||
var file = req.params[0];
|
||||
downloads[file] = downloads[file] || 0;
|
||||
downloads[file]++;
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
### Route Middleware
|
||||
|
||||
Routes may utilize route-specific middleware by passing one or more additional callbacks (or arrays) to the method. This feature is extremely useful for restricting access, loading data used by the route etc.
|
||||
@@ -318,6 +347,8 @@ Commonly used "stacks" of middleware can be passed as an array (_applied recursi
|
||||
|
||||
For this example in full, view the [route middleware example](http://github.com/visionmedia/express/blob/master/examples/route-middleware/app.js) in the repository.
|
||||
|
||||
There are times when we may want to "skip" passed remaining route middleware, but continue matching subsequent routes. To do this we invoke `next()` with the string "route" `next('route')`. If no remaining routes match the request url then Express will respond with 404 Not Found.
|
||||
|
||||
### HTTP Methods
|
||||
|
||||
We have seen _app.get()_ a few times, however Express also exposes other familiar HTTP verbs in the same manor, such as _app.post()_, _app.del()_, etc.
|
||||
@@ -816,7 +847,7 @@ Options may also be passed to the internal _fs.createReadStream()_ call, for exa
|
||||
// handle
|
||||
});
|
||||
|
||||
### res.download(file[, filename[, callback]])
|
||||
### res.download(file[, filename[, callback[, callback2]]])
|
||||
|
||||
Transfer the given _file_ as an attachment with optional alternative _filename_.
|
||||
|
||||
@@ -828,12 +859,20 @@ This is equivalent to:
|
||||
res.attachment(file);
|
||||
res.sendfile(file);
|
||||
|
||||
An optional callback may be supplied as either the second or third argument, which is passed to _res.sendfile()_:
|
||||
An optional callback may be supplied as either the second or third argument, which is passed to _res.sendfile()_. Within this callback you may still respond, as the header has not been sent.
|
||||
|
||||
res.download(path, 'expenses.doc', function(err){
|
||||
// handle
|
||||
});
|
||||
|
||||
An optional second callback, _callback2_ may be given to allow you to act on connection related errors, however you should not attempt to respond.
|
||||
|
||||
res.download(path, function(err){
|
||||
// error or finished
|
||||
}, function(err){
|
||||
// connection related error
|
||||
});
|
||||
|
||||
### res.send(body|status[, headers|status[, status]])
|
||||
|
||||
The _res.send()_ method is a high level response utility allowing you to pass
|
||||
@@ -886,7 +925,7 @@ To parse incoming _Cookie_ headers, use the _cookieParser_ middleware, which pro
|
||||
// use req.cookies.rememberme
|
||||
});
|
||||
|
||||
### res.clearCookie(name)
|
||||
### res.clearCookie(name[, options])
|
||||
|
||||
Clear cookie _name_ by setting "expires" far in the past.
|
||||
|
||||
@@ -1145,9 +1184,7 @@ Express also provides a few locals by default:
|
||||
|
||||
- `settings` the app's settings object
|
||||
- `filename` the view's filename
|
||||
- `request` the request object
|
||||
- `response` the response object
|
||||
- `app` the application itself
|
||||
- `layout(path)` specify the layout from within a view
|
||||
|
||||
This method is aliased as _app.locals()_.
|
||||
|
||||
@@ -1168,6 +1205,101 @@ All views would now have _session_ available so that session data can be accesse
|
||||
|
||||
<%= session.name %>
|
||||
|
||||
### app.lookup
|
||||
|
||||
The _app.lookup_ http methods returns an array of callback functions
|
||||
associated with the given _path_.
|
||||
|
||||
Suppose we define the following routes:
|
||||
|
||||
app.get('/user/:id', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
|
||||
We can utilize this lookup functionality to check which routes
|
||||
have been defined, which can be extremely useful for higher level
|
||||
frameworks built on Express.
|
||||
|
||||
app.lookup.get('/user/:id');
|
||||
// => [Function]
|
||||
|
||||
app.lookup.get('/user/:id/:op?');
|
||||
// => [Function]
|
||||
|
||||
app.lookup.put('/user/:id');
|
||||
// => [Function]
|
||||
|
||||
app.lookup.all('/user/:id');
|
||||
// => [Function, Function]
|
||||
|
||||
app.lookup.all('/hey');
|
||||
// => []
|
||||
|
||||
To alias _app.lookup.VERB()_, we can simply invoke _app.VERB()_
|
||||
without a callback, as a shortcut, for example the following are
|
||||
equivalent:
|
||||
|
||||
app.lookup.get('/user');
|
||||
app.get('/user');
|
||||
|
||||
Each function returned has the following properties:
|
||||
|
||||
var fn = app.get('/user/:id/:op?')[0];
|
||||
|
||||
fn.regexp
|
||||
// => /^\/user\/(?:([^\/]+?))(?:\/([^\/]+?))?\/?$/i
|
||||
|
||||
fn.keys
|
||||
// => ['id', 'op']
|
||||
|
||||
fn.path
|
||||
// => '/user/:id/:op?'
|
||||
|
||||
fn.method
|
||||
// => 'GET'
|
||||
|
||||
### app.match
|
||||
|
||||
The _app.match_ http methods return an array of callback functions
|
||||
which match the given _url_, which may include a query string etc. This
|
||||
is useful when you want reflect on which routes have the opportunity to
|
||||
respond.
|
||||
|
||||
Suppose we define the following routes:
|
||||
|
||||
app.get('/user/:id', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
|
||||
Our match against __GET__ will return two functions, since the _:op_
|
||||
in our second route is optional.
|
||||
|
||||
app.match.get('/user/1');
|
||||
// => [Function, Function]
|
||||
|
||||
This second call returns only the callback for _/user/:id/:op?_.
|
||||
|
||||
app.match.get('/user/23/edit');
|
||||
// => [Function]
|
||||
|
||||
We can also use _all()_ to disregard the http method:
|
||||
|
||||
app.match.all('/user/20');
|
||||
// => [Function, Function, Function]
|
||||
|
||||
Each function matched has the following properties:
|
||||
|
||||
var fn = app.match.get('/user/23/edit')[0];
|
||||
|
||||
fn.keys
|
||||
// => ['id', 'op']
|
||||
|
||||
fn.params
|
||||
// => { id: '23', op: 'edit' }
|
||||
|
||||
fn.method
|
||||
// => 'GET'
|
||||
|
||||
### app.mounted(fn)
|
||||
|
||||
Assign a callback _fn_ which is called when this _Server_ is passed to _Server#use()_.
|
||||
|
||||
@@ -237,7 +237,7 @@ app.listen(3000);
|
||||
<ul>
|
||||
<li><a href="http://github.com/visionmedia/express-resource">express-resource</a> provides resourceful routing</li>
|
||||
<li><a href="http://github.com/visionmedia/express-messages">express-messages</a> flash message notification rendering</li>
|
||||
<li><a href="http://github.com/visionmedia/express-configure">express-configure</a> async configuration support (load settings from redis etc)</li>
|
||||
<li><a href="http://github.com/visionmedia/express-configuration">express-configure</a> async configuration support (load settings from redis etc)</li>
|
||||
<li><a href="http://github.com/visionmedia/express-namespace">express-namespace</a> namespaced routing support</li>
|
||||
</ul>
|
||||
|
||||
|
||||
34
examples/layout-control/app.js
Normal file
34
examples/layout-control/app.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../lib/express');
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
app.set('views', __dirname + '/views');
|
||||
|
||||
// set default layout, usually "layout"
|
||||
app.set('view options', { layout: 'layouts/default' });
|
||||
|
||||
// Set our default template engine to "ejs"
|
||||
// which prevents the need for extensions
|
||||
// (although you can still mix and match)
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('pages/default');
|
||||
});
|
||||
|
||||
app.get('/alternate', function(req, res){
|
||||
// note that we do not explicitly
|
||||
// state the layout here, the view does,
|
||||
// although we could do it here as well.
|
||||
res.render('pages/alternate');
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
console.log('Express app started on port 3000');
|
||||
6
examples/layout-control/views/layouts/alternate.ejs
Normal file
6
examples/layout-control/views/layouts/alternate.ejs
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<h1>Alternate Layout</h1>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
6
examples/layout-control/views/layouts/default.ejs
Normal file
6
examples/layout-control/views/layouts/default.ejs
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<h1>Default Layout</h1>
|
||||
<%- body %>
|
||||
</body>
|
||||
</html>
|
||||
2
examples/layout-control/views/pages/alternate.ejs
Normal file
2
examples/layout-control/views/pages/alternate.ejs
Normal file
@@ -0,0 +1,2 @@
|
||||
<% layout('layouts/alternate') %>
|
||||
<h1>Page</h1>
|
||||
1
examples/layout-control/views/pages/default.ejs
Normal file
1
examples/layout-control/views/pages/default.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Page</h1>
|
||||
@@ -14,7 +14,7 @@ exports.boot = function(app){
|
||||
// App settings and middleware
|
||||
|
||||
function bootApplication(app) {
|
||||
app.use(express.logger({ format: ':method :url :status' }));
|
||||
app.use(express.logger(':method :url :status'));
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.cookieParser());
|
||||
@@ -45,6 +45,7 @@ function bootApplication(app) {
|
||||
},
|
||||
|
||||
hasMessages: function(req){
|
||||
if (!req.session) return false;
|
||||
return Object.keys(req.session.flash || {}).length;
|
||||
},
|
||||
|
||||
|
||||
47
examples/say/app.js
Normal file
47
examples/say/app.js
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
// Expose modules in ./support for demo purposes
|
||||
require.paths.unshift(__dirname + '/../../support');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('../../')
|
||||
, path = require('path')
|
||||
, exec = require('child_process').exec
|
||||
, fs = require('fs');
|
||||
|
||||
/**
|
||||
* Error handler.
|
||||
*/
|
||||
|
||||
function errorHandler(voice) {
|
||||
return function(err, req, res, next) {
|
||||
var parts = err.stack.split('\n')[1].split(/[()]/)[1].split(':')
|
||||
, filename = parts.shift()
|
||||
, basename = path.basename(filename)
|
||||
, lineno = parts.shift()
|
||||
, col = parts.shift()
|
||||
, lines = fs.readFileSync(filename, 'utf8').split('\n')
|
||||
, line = lines[lineno - 1].replace(/\./, ' ');
|
||||
|
||||
exec('say -v "' + voice + '" '
|
||||
+ err.message
|
||||
+ ' on line ' + lineno
|
||||
+ ' of ' + basename + '.'
|
||||
+ ' The contents of this line is '
|
||||
+ ' "' + line + '".');
|
||||
|
||||
res.send(500);
|
||||
}
|
||||
}
|
||||
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(request, response){
|
||||
if (request.is(foo)) response.end('bar');
|
||||
});
|
||||
|
||||
app.use(errorHandler('Vicki'));
|
||||
|
||||
app.listen(3000);
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
var connect = require('connect')
|
||||
, HTTPSServer = require('./https')
|
||||
, HTTPServer = require('./http');
|
||||
, HTTPServer = require('./http')
|
||||
, Route = require('./router/route')
|
||||
|
||||
/**
|
||||
* Re-export connect auto-loaders.
|
||||
@@ -27,7 +28,7 @@ var exports = module.exports = connect.middleware;
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
exports.version = '2.2.0';
|
||||
exports.version = '2.3.5';
|
||||
|
||||
/**
|
||||
* Shortcut for `new Server(...)`.
|
||||
@@ -51,12 +52,14 @@ exports.createServer = function(options){
|
||||
|
||||
exports.HTTPServer = HTTPServer;
|
||||
exports.HTTPSServer = HTTPSServer;
|
||||
exports.Route = Route;
|
||||
|
||||
/**
|
||||
* View extensions.
|
||||
*/
|
||||
|
||||
require('./view');
|
||||
exports.View =
|
||||
exports.view = require('./view');
|
||||
|
||||
/**
|
||||
* Response extensions.
|
||||
@@ -69,3 +72,8 @@ require('./response');
|
||||
*/
|
||||
|
||||
require('./request');
|
||||
|
||||
// Error handler title
|
||||
|
||||
exports.errorHandler.title = 'Express';
|
||||
|
||||
|
||||
128
lib/http.js
128
lib/http.js
@@ -11,12 +11,24 @@
|
||||
|
||||
var qs = require('qs')
|
||||
, connect = require('connect')
|
||||
, router = connect.router
|
||||
, methods = router.methods.concat(['del', 'all'])
|
||||
, router = require('./router')
|
||||
, methods = router.methods.concat('del', 'all')
|
||||
, view = require('./view')
|
||||
, url = require('url')
|
||||
, utils = connect.utils;
|
||||
|
||||
/**
|
||||
* Expose `HTTPServer`.
|
||||
*/
|
||||
|
||||
exports = module.exports = HTTPServer;
|
||||
|
||||
/**
|
||||
* Server proto.
|
||||
*/
|
||||
|
||||
var app = HTTPServer.prototype;
|
||||
|
||||
/**
|
||||
* Initialize a new `HTTPServer` with optional `middleware`.
|
||||
*
|
||||
@@ -24,7 +36,7 @@ var qs = require('qs')
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var Server = exports = module.exports = function HTTPServer(middleware){
|
||||
function HTTPServer(middleware){
|
||||
connect.HTTPServer.call(this, []);
|
||||
this.init(middleware);
|
||||
};
|
||||
@@ -33,7 +45,7 @@ var Server = exports = module.exports = function HTTPServer(middleware){
|
||||
* Inherit from `connect.HTTPServer`.
|
||||
*/
|
||||
|
||||
Server.prototype.__proto__ = connect.HTTPServer.prototype;
|
||||
app.__proto__ = connect.HTTPServer.prototype;
|
||||
|
||||
/**
|
||||
* Initialize the server.
|
||||
@@ -42,21 +54,17 @@ Server.prototype.__proto__ = connect.HTTPServer.prototype;
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.init = function(middleware){
|
||||
app.init = function(middleware){
|
||||
var self = this;
|
||||
this.match = {};
|
||||
this.lookup = {};
|
||||
this.cache = {};
|
||||
this.settings = {};
|
||||
this.redirects = {};
|
||||
this.isCallbacks = {};
|
||||
this.viewHelpers = {};
|
||||
this._locals = {};
|
||||
this.dynamicViewHelpers = {};
|
||||
this.errorHandlers = [];
|
||||
|
||||
// default "home" to /
|
||||
this.set('home', '/');
|
||||
|
||||
// set "env" to NODE_ENV, defaulting to "development"
|
||||
this.set('env', process.env.NODE_ENV || 'development');
|
||||
|
||||
// expose objects to each other
|
||||
@@ -79,7 +87,7 @@ Server.prototype.init = function(middleware){
|
||||
if (middleware) middleware.forEach(self.use.bind(self));
|
||||
|
||||
// use router, expose as app.get(), etc
|
||||
var fn = router(function(app){ self.routes = app; });
|
||||
var fn = router(function(app){ self.routes = app; }, this);
|
||||
this.__defineGetter__('router', function(){
|
||||
this.__usedRouter = true;
|
||||
return fn;
|
||||
@@ -91,12 +99,6 @@ Server.prototype.init = function(middleware){
|
||||
, app: this
|
||||
});
|
||||
|
||||
// default dynamic locals
|
||||
this.dynamicHelpers({
|
||||
request: function(req, res){ return req; }
|
||||
, response: function(req, res){ return res; }
|
||||
});
|
||||
|
||||
// default development configuration
|
||||
this.configure('development', function(){
|
||||
this.enable('hints');
|
||||
@@ -104,7 +106,7 @@ Server.prototype.init = function(middleware){
|
||||
|
||||
// default production configuration
|
||||
this.configure('production', function(){
|
||||
this.enable('cache views');
|
||||
this.enable('view cache');
|
||||
});
|
||||
|
||||
// register error handlers on "listening"
|
||||
@@ -112,6 +114,18 @@ Server.prototype.init = function(middleware){
|
||||
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);
|
||||
};
|
||||
|
||||
methods.forEach(function(method){
|
||||
self.match[method] = function(url){
|
||||
return self.router.match(url, 'all' == method
|
||||
@@ -119,6 +133,12 @@ Server.prototype.init = function(middleware){
|
||||
: 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
|
||||
@@ -131,7 +151,7 @@ Server.prototype.init = function(middleware){
|
||||
* When using the vhost() middleware register error handlers.
|
||||
*/
|
||||
|
||||
Server.prototype.onvhost = function(){
|
||||
app.onvhost = function(){
|
||||
this.registerErrorHandlers();
|
||||
};
|
||||
|
||||
@@ -142,7 +162,7 @@ Server.prototype.onvhost = function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.registerErrorHandlers = function(){
|
||||
app.registerErrorHandlers = function(){
|
||||
this.errorHandlers.forEach(function(fn){
|
||||
this.use(function(err, req, res, next){
|
||||
fn.apply(this, arguments);
|
||||
@@ -161,7 +181,7 @@ Server.prototype.registerErrorHandlers = function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.use = function(route, middleware){
|
||||
app.use = function(route, middleware){
|
||||
var app, home, handle;
|
||||
|
||||
if ('string' != typeof route) {
|
||||
@@ -219,7 +239,7 @@ Server.prototype.use = function(route, middleware){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.mounted = function(fn){
|
||||
app.mounted = function(fn){
|
||||
this.__mounted = fn;
|
||||
return this;
|
||||
};
|
||||
@@ -231,7 +251,7 @@ Server.prototype.mounted = function(fn){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.register = function(){
|
||||
app.register = function(){
|
||||
view.register.apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
@@ -245,9 +265,9 @@ Server.prototype.register = function(){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.helpers =
|
||||
Server.prototype.locals = function(obj){
|
||||
utils.merge(this.viewHelpers, obj);
|
||||
app.helpers =
|
||||
app.locals = function(obj){
|
||||
utils.merge(this._locals, obj);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -260,7 +280,7 @@ Server.prototype.locals = function(obj){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.dynamicHelpers = function(obj){
|
||||
app.dynamicHelpers = function(obj){
|
||||
utils.merge(this.dynamicViewHelpers, obj);
|
||||
return this;
|
||||
};
|
||||
@@ -308,7 +328,7 @@ Server.prototype.dynamicHelpers = function(obj){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.param = function(name, fn){
|
||||
app.param = function(name, fn){
|
||||
if (Array.isArray(name)) {
|
||||
name.forEach(function(name){
|
||||
this.param(name, fn);
|
||||
@@ -329,7 +349,7 @@ Server.prototype.param = function(name, fn){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.error = function(fn){
|
||||
app.error = function(fn){
|
||||
this.errorHandlers.push(fn);
|
||||
return this;
|
||||
};
|
||||
@@ -343,7 +363,7 @@ Server.prototype.error = function(fn){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.is = function(type, fn){
|
||||
app.is = function(type, fn){
|
||||
if (!fn) return this.isCallbacks[type];
|
||||
this.isCallbacks[type] = fn;
|
||||
return this;
|
||||
@@ -359,7 +379,7 @@ Server.prototype.is = function(type, fn){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.set = function(setting, val){
|
||||
app.set = function(setting, val){
|
||||
if (val === undefined) {
|
||||
if (this.settings.hasOwnProperty(setting)) {
|
||||
return this.settings[setting];
|
||||
@@ -380,7 +400,7 @@ Server.prototype.set = function(setting, val){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.enabled = function(setting){
|
||||
app.enabled = function(setting){
|
||||
return !!this.set(setting);
|
||||
};
|
||||
|
||||
@@ -392,7 +412,7 @@ Server.prototype.enabled = function(setting){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.disabled = function(setting){
|
||||
app.disabled = function(setting){
|
||||
return !this.set(setting);
|
||||
};
|
||||
|
||||
@@ -404,7 +424,7 @@ Server.prototype.disabled = function(setting){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.enable = function(setting){
|
||||
app.enable = function(setting){
|
||||
return this.set(setting, true);
|
||||
};
|
||||
|
||||
@@ -416,7 +436,7 @@ Server.prototype.enable = function(setting){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.disable = function(setting){
|
||||
app.disable = function(setting){
|
||||
return this.set(setting, false);
|
||||
};
|
||||
|
||||
@@ -429,7 +449,7 @@ Server.prototype.disable = function(setting){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.redirect = function(key, url){
|
||||
app.redirect = function(key, url){
|
||||
this.redirects[key] = url;
|
||||
return this;
|
||||
};
|
||||
@@ -443,7 +463,7 @@ Server.prototype.redirect = function(key, url){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.configure = function(env, fn){
|
||||
app.configure = function(env, fn){
|
||||
if ('function' == typeof env) {
|
||||
fn = env, env = 'all';
|
||||
}
|
||||
@@ -455,8 +475,8 @@ Server.prototype.configure = function(env, fn){
|
||||
|
||||
// Generate routing methods
|
||||
|
||||
function generateMethod(method){
|
||||
Server.prototype[method] = function(path, fn){
|
||||
methods.forEach(function(method){
|
||||
app[method] = function(path){
|
||||
var self = this;
|
||||
|
||||
// Lookup
|
||||
@@ -467,34 +487,14 @@ function generateMethod(method){
|
||||
}
|
||||
|
||||
// Ensure router is mounted
|
||||
if (!this.__usedRouter) {
|
||||
this.use(this.router);
|
||||
}
|
||||
|
||||
// Route specific middleware support
|
||||
if (arguments.length > 2) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
fn = args.pop();
|
||||
(function stack(middleware){
|
||||
middleware.forEach(function(fn){
|
||||
if (Array.isArray(fn)) {
|
||||
stack(fn);
|
||||
} else {
|
||||
self[method](path, fn);
|
||||
}
|
||||
});
|
||||
})(args);
|
||||
}
|
||||
if (!this.__usedRouter) this.use(this.router);
|
||||
|
||||
// Generate the route
|
||||
this.routes[method](path, fn);
|
||||
this.routes[method].apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
return arguments.callee;
|
||||
}
|
||||
|
||||
methods.forEach(generateMethod);
|
||||
});
|
||||
|
||||
// Alias delete as "del"
|
||||
|
||||
Server.prototype.del = Server.prototype.delete;
|
||||
app.del = app.delete;
|
||||
|
||||
18
lib/https.js
18
lib/https.js
@@ -13,6 +13,18 @@ var connect = require('connect')
|
||||
, HTTPServer = require('./http')
|
||||
, https = require('https');
|
||||
|
||||
/**
|
||||
* Expose `HTTPSServer`.
|
||||
*/
|
||||
|
||||
exports = module.exports = HTTPSServer;
|
||||
|
||||
/**
|
||||
* Server proto.
|
||||
*/
|
||||
|
||||
var app = HTTPSServer.prototype;
|
||||
|
||||
/**
|
||||
* Initialize a new `HTTPSServer` with the
|
||||
* given `options`, and optional `middleware`.
|
||||
@@ -22,7 +34,7 @@ var connect = require('connect')
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var Server = exports = module.exports = function HTTPSServer(options, middleware){
|
||||
function HTTPSServer(options, middleware){
|
||||
connect.HTTPSServer.call(this, options, []);
|
||||
this.init(middleware);
|
||||
};
|
||||
@@ -31,10 +43,10 @@ var Server = exports = module.exports = function HTTPSServer(options, middleware
|
||||
* Inherit from `connect.HTTPSServer`.
|
||||
*/
|
||||
|
||||
Server.prototype.__proto__ = connect.HTTPSServer.prototype;
|
||||
app.__proto__ = connect.HTTPSServer.prototype;
|
||||
|
||||
// mixin HTTPServer methods
|
||||
|
||||
Object.keys(HTTPServer.prototype).forEach(function(method){
|
||||
Server.prototype[method] = HTTPServer.prototype[method];
|
||||
app[method] = HTTPServer.prototype[method];
|
||||
});
|
||||
|
||||
@@ -197,7 +197,7 @@ req.flash = function(type, msg){
|
||||
, args = arguments
|
||||
, formatters = this.app.flashFormatters || {};
|
||||
formatters.__proto__ = flashFormatters;
|
||||
msg = utils.miniMarkdown(utils.htmlEscape(msg));
|
||||
msg = utils.miniMarkdown(utils.escape(msg));
|
||||
msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
|
||||
var formatter = formatters[format];
|
||||
if (formatter) return formatter(args[i++]);
|
||||
@@ -269,7 +269,7 @@ req.is = function(type){
|
||||
if ('*' == type[0] && type[1] == contentType[1]) return true;
|
||||
if ('*' == type[1] && type[0] == contentType[0]) return true;
|
||||
}
|
||||
return ~contentType.indexOf(type);
|
||||
return !! ~contentType.indexOf(type);
|
||||
};
|
||||
|
||||
// Callback for isXMLHttpRequest / xhr
|
||||
|
||||
@@ -17,8 +17,9 @@ var fs = require('fs')
|
||||
, parseRange = require('./utils').parseRange
|
||||
, res = http.ServerResponse.prototype
|
||||
, send = connect.static.send
|
||||
, join = require('path').join
|
||||
, mime = require('mime');
|
||||
, mime = require('mime')
|
||||
, basename = path.basename
|
||||
, join = path.join;
|
||||
|
||||
/**
|
||||
* Send a response with the given `body` and optional `headers` and `status` code.
|
||||
@@ -75,10 +76,12 @@ res.send = function(body, headers, status){
|
||||
}
|
||||
} else {
|
||||
if (!this.header('Content-Type')) {
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.contentType('.json');
|
||||
}
|
||||
body = JSON.stringify(body);
|
||||
if (this.req.query.callback && this.app.set('jsonp callback')) {
|
||||
this.charset = this.charset || 'utf-8';
|
||||
this.header('Content-Type', 'text/javascript');
|
||||
body = this.req.query.callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
|
||||
}
|
||||
@@ -130,6 +133,7 @@ res.send = function(body, headers, status){
|
||||
*/
|
||||
|
||||
res.sendfile = function(path, options, fn){
|
||||
var next = this.req.next;
|
||||
options = options || {};
|
||||
|
||||
// support function as second arg
|
||||
@@ -138,9 +142,9 @@ res.sendfile = function(path, options, fn){
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.path = path;
|
||||
options.path = encodeURIComponent(path);
|
||||
options.callback = fn;
|
||||
send(this.req, this, this.req.next, options);
|
||||
send(this.req, this, next, options);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -175,40 +179,50 @@ res.contentType = function(type){
|
||||
*/
|
||||
|
||||
res.attachment = function(filename){
|
||||
if (filename) this.contentType(filename);
|
||||
this.header('Content-Disposition', filename
|
||||
? 'attachment; filename="' + path.basename(filename) + '"'
|
||||
? 'attachment; filename="' + basename(filename) + '"'
|
||||
: 'attachment');
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transfer the file at the given `path`, with optional
|
||||
* `filename` as an attachment and optional callback `fn(err)`.
|
||||
* `filename` as an attachment and optional callback `fn(err)`,
|
||||
* and optional `fn2(err)` which is invoked when an error has
|
||||
* occurred after headers have been sent.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {String|Function} filename or fn
|
||||
* @param {Function} fn
|
||||
* @return {Type}
|
||||
* @param {Function} fn2
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.download = function(path, filename, fn){
|
||||
res.download = function(path, filename, fn, fn2){
|
||||
var self = this;
|
||||
|
||||
// support callback as second arg
|
||||
if ('function' == typeof filename) {
|
||||
fn2 = fn;
|
||||
fn = filename;
|
||||
filename = null;
|
||||
}
|
||||
|
||||
// transfer the file
|
||||
this.attachment(filename || path).sendfile(path, function(err){
|
||||
if (err) self.removeHeader('Content-Disposition');
|
||||
if (fn) return fn(err);
|
||||
var sentHeader = self._header;
|
||||
if (err) {
|
||||
self.req.next('ENOENT' == err.code
|
||||
? null
|
||||
: err);
|
||||
if (!sentHeader) self.removeHeader('Content-Disposition');
|
||||
if (sentHeader) {
|
||||
fn2 && fn2(err);
|
||||
} else if (fn) {
|
||||
fn(err);
|
||||
} else {
|
||||
self.req.next(err);
|
||||
}
|
||||
} else if (fn) {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -235,11 +249,15 @@ res.header = function(name, val){
|
||||
* Clear cookie `name`.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.clearCookie = function(name){
|
||||
this.cookie(name, '', { expires: new Date(1) });
|
||||
res.clearCookie = function(name, options){
|
||||
var opts = { expires: new Date(1) };
|
||||
this.cookie(name, '', options
|
||||
? utils.merge(options, opts)
|
||||
: opts);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -394,7 +412,8 @@ res.local = function(name, val){
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.locals = function(obj){
|
||||
res.locals =
|
||||
res.helpers = function(obj){
|
||||
if (obj) {
|
||||
for (var key in obj) {
|
||||
this.local(key, obj[key]);
|
||||
|
||||
331
lib/router/index.js
Normal file
331
lib/router/index.js
Normal file
@@ -0,0 +1,331 @@
|
||||
|
||||
/*!
|
||||
* Express - router
|
||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils')
|
||||
, parse = require('url').parse
|
||||
, _methods = require('./methods')
|
||||
, Route = require('./route');
|
||||
|
||||
/**
|
||||
* Expose router.
|
||||
*/
|
||||
|
||||
exports = module.exports = router;
|
||||
|
||||
/**
|
||||
* Expose methods.
|
||||
*/
|
||||
|
||||
exports.methods = _methods;
|
||||
|
||||
/**
|
||||
* Provides Sinatra-like routing capabilities.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function router(fn, app){
|
||||
var self = this
|
||||
, methods = {}
|
||||
, routes = {}
|
||||
, 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;
|
||||
};
|
||||
|
||||
// Register param callback
|
||||
methods.param = function(name, fn){
|
||||
params[name] = fn;
|
||||
};
|
||||
|
||||
fn.call(this, methods);
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
function router(req, res, next){
|
||||
var route
|
||||
, self = this;
|
||||
|
||||
(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);
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to OPTIONS.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {ServerResponse} req
|
||||
* @param {Array} routes
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function options(req, res, routes) {
|
||||
var pathname = parse(req.url).pathname
|
||||
, body = optionsFor(pathname, routes).join(',');
|
||||
res.send(body, { Allow: body });
|
||||
}
|
||||
|
||||
/**
|
||||
* Return OPTIONS array for the given `path`, matching `routes`.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}).map(function(method){
|
||||
return method.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to match the given request to
|
||||
* one of the routes. When successful
|
||||
* a route function is returned.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {Object} routes
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function match(req, routes, i) {
|
||||
var captures
|
||||
, method = req.method
|
||||
, i = i || 0;
|
||||
|
||||
// pass HEAD to GET routes
|
||||
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.params = [];
|
||||
for (var j = 1, l = captures.length; j < l; ++j) {
|
||||
var key = keys[j-1],
|
||||
val = 'string' == typeof captures[j]
|
||||
? decodeURIComponent(captures[j])
|
||||
: captures[j];
|
||||
if (key) {
|
||||
route.params[key] = val;
|
||||
} else {
|
||||
route.params.push(val);
|
||||
}
|
||||
}
|
||||
req._route_index = i;
|
||||
return route;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
lib/router/methods.js
Normal file
70
lib/router/methods.js
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
/*!
|
||||
* Express - router - methods
|
||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Hypertext Transfer Protocol -- HTTP/1.1
|
||||
* http://www.ietf.org/rfc/rfc2616.txt
|
||||
*/
|
||||
|
||||
var RFC2616 = ['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT'];
|
||||
|
||||
/**
|
||||
* HTTP Extensions for Distributed Authoring -- WEBDAV
|
||||
* http://www.ietf.org/rfc/rfc2518.txt
|
||||
*/
|
||||
|
||||
var RFC2518 = ['PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK'];
|
||||
|
||||
/**
|
||||
* Versioning Extensions to WebDAV
|
||||
* http://www.ietf.org/rfc/rfc3253.txt
|
||||
*/
|
||||
|
||||
var RFC3253 = ['VERSION-CONTROL', 'REPORT', 'CHECKOUT', 'CHECKIN', 'UNCHECKOUT', 'MKWORKSPACE', 'UPDATE', 'LABEL', 'MERGE', 'BASELINE-CONTROL', 'MKACTIVITY'];
|
||||
|
||||
/**
|
||||
* Ordered Collections Protocol (WebDAV)
|
||||
* http://www.ietf.org/rfc/rfc3648.txt
|
||||
*/
|
||||
|
||||
var RFC3648 = ['ORDERPATCH'];
|
||||
|
||||
/**
|
||||
* Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol
|
||||
* http://www.ietf.org/rfc/rfc3744.txt
|
||||
*/
|
||||
|
||||
var RFC3744 = ['ACL'];
|
||||
|
||||
/**
|
||||
* Web Distributed Authoring and Versioning (WebDAV) SEARCH
|
||||
* http://www.ietf.org/rfc/rfc5323.txt
|
||||
*/
|
||||
|
||||
var RFC5323 = ['SEARCH'];
|
||||
|
||||
/**
|
||||
* PATCH Method for HTTP
|
||||
* http://www.ietf.org/rfc/rfc5789.txt
|
||||
*/
|
||||
|
||||
var RFC5789 = ['PATCH'];
|
||||
|
||||
/**
|
||||
* Expose the methods.
|
||||
*/
|
||||
|
||||
module.exports = [].concat(
|
||||
RFC2616
|
||||
, RFC2518
|
||||
, RFC3253
|
||||
, RFC3648
|
||||
, RFC3744
|
||||
, RFC5323
|
||||
, RFC5789).map(function(method){
|
||||
return method.toLowerCase();
|
||||
});
|
||||
71
lib/router/route.js
Normal file
71
lib/router/route.js
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
/*!
|
||||
* Express - router - Route
|
||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Expose `Route`.
|
||||
*/
|
||||
|
||||
module.exports = Route;
|
||||
|
||||
/**
|
||||
* Initialize `Route` with the given HTTP `method`, `path`,
|
||||
* and callback `fn` and `options`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `sensitive` enable case-sensitive routes
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
* @param {Function} fn
|
||||
* @param {Object} options.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Route(method, path, fn, options) {
|
||||
options = options || {};
|
||||
this.callback = fn;
|
||||
this.path = path;
|
||||
this.regexp = normalize(path, this.keys = [], options.sensitive);
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given path string,
|
||||
* returning a regular expression.
|
||||
*
|
||||
* An empty array should be passed,
|
||||
* which will contain the placeholder
|
||||
* key names. For example "/user/:id" will
|
||||
* then contain ["id"].
|
||||
*
|
||||
* @param {String|RegExp} path
|
||||
* @param {Array} keys
|
||||
* @param {Boolean} sensitive
|
||||
* @return {RegExp}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function normalize(path, keys, sensitive) {
|
||||
if (path instanceof RegExp) return path;
|
||||
path = path
|
||||
.concat('/?')
|
||||
.replace(/\/\(/g, '(?:/')
|
||||
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
|
||||
keys.push(key);
|
||||
slash = slash || '';
|
||||
return ''
|
||||
+ (optional ? '' : slash)
|
||||
+ '(?:'
|
||||
+ (optional ? slash : '')
|
||||
+ (format || '') + (capture || '([^/]+?)') + ')'
|
||||
+ (optional || '');
|
||||
})
|
||||
.replace(/([\/.])/g, '\\$1')
|
||||
.replace(/\*/g, '(.+)');
|
||||
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
|
||||
}
|
||||
81
lib/utils.js
81
lib/utils.js
@@ -5,64 +5,6 @@
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var path = require('path')
|
||||
, basename = path.basename
|
||||
, dirname = path.dirname
|
||||
, extname = path.extname;
|
||||
|
||||
/**
|
||||
* Memory cache.
|
||||
*/
|
||||
|
||||
var cache = {
|
||||
basename: {}
|
||||
, dirname: {}
|
||||
, extname: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cached basename.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.basename = function(path){
|
||||
return cache.basename[path]
|
||||
|| (cache.basename[path] = basename(path));
|
||||
};
|
||||
|
||||
/**
|
||||
* Cached dirname.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.dirname = function(path){
|
||||
return cache.dirname[path]
|
||||
|| (cache.dirname[path] = dirname(path));
|
||||
};
|
||||
|
||||
/**
|
||||
* Cached extname.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.extname = function(path){
|
||||
return cache.extname[path]
|
||||
|| (cache.extname[path] = extname(path));
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge object `b` with `a` giving precedence to
|
||||
* values in object `a`.
|
||||
@@ -88,6 +30,27 @@ exports.union = function(a, b){
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* Flatten the given `arr`.
|
||||
*
|
||||
* @param {Array} arr
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.flatten = function(arr, ret){
|
||||
var ret = ret || []
|
||||
, len = arr.length;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (Array.isArray(arr[i])) {
|
||||
exports.flatten(arr[i], ret);
|
||||
} else {
|
||||
ret.push(arr[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse mini markdown implementation.
|
||||
* The following conversions are supported,
|
||||
@@ -117,7 +80,7 @@ exports.miniMarkdown = function(str){
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.htmlEscape = function(html) {
|
||||
exports.escape = function(html) {
|
||||
return String(html)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
|
||||
205
lib/view.js
205
lib/view.js
@@ -21,14 +21,6 @@ var path = require('path')
|
||||
, http = require('http')
|
||||
, res = http.ServerResponse.prototype;
|
||||
|
||||
/**
|
||||
* Memory cache.
|
||||
*
|
||||
* @type Object
|
||||
*/
|
||||
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Expose constructors.
|
||||
*/
|
||||
@@ -41,6 +33,91 @@ exports = module.exports = View;
|
||||
|
||||
exports.register = View.register;
|
||||
|
||||
/**
|
||||
* Lookup and compile `view` with cache support by supplying
|
||||
* both the `cache` object and `cid` string,
|
||||
* followed by `options` passed to `exports.lookup()`.
|
||||
*
|
||||
* @param {String} view
|
||||
* @param {Object} cache
|
||||
* @param {Object} cid
|
||||
* @param {Object} options
|
||||
* @return {View}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.compile = function(view, cache, cid, options){
|
||||
if (cache && cid && cache[cid]) return cache[cid];
|
||||
|
||||
// lookup
|
||||
view = exports.lookup(view, options);
|
||||
|
||||
// hints
|
||||
if (!view.exists) {
|
||||
if (options.hint) hintAtViewPaths(view.original, options);
|
||||
var err = new Error('failed to locate view "' + view.original.view + '"');
|
||||
err.view = view.original;
|
||||
throw err;
|
||||
}
|
||||
|
||||
// compile
|
||||
view.fn = view.templateEngine.compile(view.contents, options);
|
||||
cache[cid] = view;
|
||||
|
||||
return view;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup `view`, returning an instanceof `View`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `root` root directory path
|
||||
* - `defaultEngine` default template engine
|
||||
* - `parentView` parent `View` object
|
||||
* - `cache` cache object
|
||||
* - `cacheid` optional cache id
|
||||
*
|
||||
* Lookup:
|
||||
*
|
||||
* - partial `_<name>`
|
||||
* - any `<name>/index`
|
||||
* - non-layout `../<name>/index`
|
||||
* - any `<root>/<name>`
|
||||
* - partial `<root>/_<name>`
|
||||
*
|
||||
* @param {String} view
|
||||
* @param {Object} options
|
||||
* @return {View}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
exports.lookup = function(view, options){
|
||||
var orig = view = new View(view, options);
|
||||
|
||||
// Try _ prefix ex: ./views/_<name>.jade
|
||||
if (partial) {
|
||||
view = new View(orig.prefixPath, options);
|
||||
if (!view.exists) view = orig;
|
||||
}
|
||||
|
||||
// Try index ex: ./views/user/index.jade
|
||||
if (!view.exists) view = new View(orig.indexPath, options);
|
||||
|
||||
// Try ../<name>/index ex: ../user/index.jade
|
||||
// when calling partial('user') within the same dir
|
||||
if (!view.exists && !options.isLayout) view = new View(orig.upIndexPath, options);
|
||||
|
||||
// Try root ex: <root>/user.jade
|
||||
if (!view.exists) view = new View(orig.rootPath, options);
|
||||
|
||||
// Try root _ prefix ex: <root>/_user.jade
|
||||
if (!view.exists && partial) view = new View(view.prefixPath, options);
|
||||
|
||||
view.original = orig;
|
||||
return view;
|
||||
};
|
||||
|
||||
/**
|
||||
* Partial render helper.
|
||||
*
|
||||
@@ -113,16 +190,39 @@ function renderPartial(res, view, options, parentLocals, parent){
|
||||
// Collection support
|
||||
if (collection) {
|
||||
var len = collection.length
|
||||
, buf = '';
|
||||
, buf = ''
|
||||
, keys
|
||||
, key
|
||||
, val;
|
||||
|
||||
options.collectionLength = len;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
var val = collection[i];
|
||||
options.firstInCollection = i === 0;
|
||||
options.indexInCollection = i;
|
||||
options.lastInCollection = i === len - 1;
|
||||
object = val;
|
||||
buf += render();
|
||||
|
||||
if ('number' == typeof len || Array.isArray(collection)) {
|
||||
for (var i = 0; i < len; ++i) {
|
||||
val = collection[i];
|
||||
options.firstInCollection = i == 0;
|
||||
options.indexInCollection = i;
|
||||
options.lastInCollection = i == len - 1;
|
||||
object = val;
|
||||
buf += render();
|
||||
}
|
||||
} else {
|
||||
keys = Object.keys(collection);
|
||||
len = keys.length;
|
||||
options.collectionLength = len;
|
||||
options.collectionKeys = keys;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
key = keys[i];
|
||||
val = collection[key];
|
||||
options.keyInCollection = key;
|
||||
options.firstInCollection = i == 0;
|
||||
options.indexInCollection = i;
|
||||
options.lastInCollection = i == len - 1;
|
||||
object = val;
|
||||
buf += render();
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
} else {
|
||||
return render();
|
||||
@@ -156,6 +256,7 @@ function renderPartial(res, view, options, parentLocals, parent){
|
||||
res.partial = function(view, options, fn){
|
||||
var app = this.app
|
||||
, options = options || {}
|
||||
, viewEngine = app.set('view engine')
|
||||
, parent = {};
|
||||
|
||||
// accept callback as second argument
|
||||
@@ -168,9 +269,7 @@ res.partial = function(view, options, fn){
|
||||
parent.dirname = app.set('views') || process.cwd() + '/views';
|
||||
|
||||
// utilize "view engine" option
|
||||
if (app.set('view engine')) {
|
||||
parent.extension = '.' + app.set('view engine');
|
||||
}
|
||||
if (viewEngine) parent.extension = '.' + viewEngine;
|
||||
|
||||
// render the partial
|
||||
try {
|
||||
@@ -238,10 +337,14 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
var options = {}
|
||||
, self = this
|
||||
, app = this.app
|
||||
, helpers = app.viewHelpers
|
||||
, helpers = app._locals
|
||||
, dynamicHelpers = app.dynamicViewHelpers
|
||||
, viewOptions = app.set('view options')
|
||||
, cacheTemplates = app.set('cache views');
|
||||
, cacheViews = app.enabled('view cache')
|
||||
, root = app.set('views') || process.cwd() + '/views';
|
||||
|
||||
// cache id
|
||||
var cid = view + (parent ? ':' + parent.path : '');
|
||||
|
||||
// merge "view options"
|
||||
if (viewOptions) merge(options, viewOptions);
|
||||
@@ -258,10 +361,10 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
// status support
|
||||
if (options.status) this.statusCode = options.status;
|
||||
|
||||
// Defaults
|
||||
var self = this
|
||||
, root = app.set('views') || process.cwd() + '/views'
|
||||
, partial = options.renderPartial
|
||||
// capture attempts
|
||||
options.attempts = [];
|
||||
|
||||
var partial = options.renderPartial
|
||||
, layout = options.layout;
|
||||
|
||||
// Layout support
|
||||
@@ -284,33 +387,6 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
// charset option
|
||||
if (options.charset) this.charset = options.charset;
|
||||
|
||||
// Populate view
|
||||
var orig = view = new View(view, options);
|
||||
|
||||
// Try _ prefix ex: ./views/_<name>.jade
|
||||
if (!view.exists) view = new View(orig.prefixPath, options);
|
||||
|
||||
// Try index ex: ./views/user/index.jade
|
||||
if (!view.exists) view = new View(orig.indexPath, options);
|
||||
|
||||
// Try ../<name>/index ex: ../user/index.jade
|
||||
// when calling partial('user') within the same dir
|
||||
if (!view.exists && !options.isLayout) view = new View(orig.upIndexPath, options);
|
||||
|
||||
// Try root ex: <root>/user.jade
|
||||
if (!view.exists) view = new View(orig.rootPath, options);
|
||||
|
||||
// Try root _ prefix ex: <root>/_user.jade
|
||||
if (!view.exists && partial) view = new View(view.prefixPath, options);
|
||||
|
||||
// Does not exist
|
||||
if (!view.exists) {
|
||||
if (app.enabled('hints')) hintAtViewPaths(orig, options);
|
||||
var err = new Error('failed to locate view "' + orig.view + '"');
|
||||
err.view = orig;
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Dynamic helper support
|
||||
if (false !== options.dynamicHelpers) {
|
||||
// cache
|
||||
@@ -336,15 +412,18 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
return renderPartial(self, path, opts, options, view);
|
||||
};
|
||||
|
||||
// Provide filename to engine
|
||||
// View lookup
|
||||
options.hint = app.enabled('hints');
|
||||
view = exports.compile(view, app.cache, cid, options);
|
||||
options.filename = view.path;
|
||||
|
||||
// Attempt render
|
||||
var engine = view.templateEngine
|
||||
, template = cacheTemplates
|
||||
? cache[view.path] || (cache[view.path] = engine.compile(view.contents, options))
|
||||
: engine.compile(view.contents, options)
|
||||
, str = template.call(options.scope, options);
|
||||
// layout helper
|
||||
options.layout = function(path){
|
||||
layout = path;
|
||||
};
|
||||
|
||||
// render
|
||||
var str = view.fn.call(options.scope, options);
|
||||
|
||||
// layout expected
|
||||
if (layout) {
|
||||
@@ -375,10 +454,8 @@ res._render = function(view, opts, fn, parent, sub){
|
||||
function hintAtViewPaths(view, options) {
|
||||
console.error();
|
||||
console.error('failed to locate view "' + view.view + '", tried:');
|
||||
console.error(' - ' + new View(view.path, options).path);
|
||||
console.error(' - ' + new View(view.prefixPath, options).path);
|
||||
console.error(' - ' + new View(view.indexPath, options).path);
|
||||
if (!options.isLayout) console.error(' - ' + new View(view.upIndexPath, options).path);
|
||||
if (options.isLayout) console.error(' - ' + new View(view.rootPath, options).path);
|
||||
options.attempts.forEach(function(path){
|
||||
console.error(' - %s', path);
|
||||
});
|
||||
console.error();
|
||||
}
|
||||
@@ -9,25 +9,25 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils')
|
||||
, extname = utils.extname
|
||||
, dirname = utils.dirname
|
||||
, basename = utils.basename
|
||||
var path = require('path')
|
||||
, extname = path.extname
|
||||
, dirname = path.dirname
|
||||
, basename = path.basename
|
||||
, fs = require('fs')
|
||||
, stat = fs.statSync;
|
||||
|
||||
/**
|
||||
* Memory cache.
|
||||
* Expose `View`.
|
||||
*/
|
||||
|
||||
exports = module.exports = View;
|
||||
|
||||
/**
|
||||
* Require cache.
|
||||
*/
|
||||
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Existance cache.
|
||||
*/
|
||||
|
||||
var exists = {};
|
||||
|
||||
/**
|
||||
* Initialize a new `View` with the given `view` path and `options`.
|
||||
*
|
||||
@@ -36,9 +36,8 @@ var exists = {};
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var View = exports = module.exports = function View(view, options) {
|
||||
function View(view, options) {
|
||||
options = options || {};
|
||||
// TODO: more caching
|
||||
this.view = view;
|
||||
this.root = options.root;
|
||||
this.relative = false !== options.relative;
|
||||
@@ -50,6 +49,10 @@ var View = exports = module.exports = function View(view, options) {
|
||||
this.name = this.basename.replace(this.extension, '');
|
||||
this.path = this.resolvePath();
|
||||
this.dirname = dirname(this.path);
|
||||
if (options.attempts) {
|
||||
if (!~options.attempts.indexOf(this.path))
|
||||
options.attempts.push(this.path);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,16 +63,11 @@ var View = exports = module.exports = function View(view, options) {
|
||||
*/
|
||||
|
||||
View.prototype.__defineGetter__('exists', function(){
|
||||
var path = this.path;
|
||||
if (null != exists[path]) {
|
||||
return exists[path];
|
||||
} else {
|
||||
try {
|
||||
stat(path);
|
||||
return exists[path] = true;
|
||||
} catch (err) {
|
||||
return exists[path] = false;
|
||||
}
|
||||
try {
|
||||
stat(this.path);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.5",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
|
||||
@@ -10,7 +10,7 @@
|
||||
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
|
||||
],
|
||||
"dependencies": {
|
||||
"connect": ">= 1.1.6 < 2.0.0",
|
||||
"connect": ">= 1.4.0 < 2.0.0",
|
||||
"mime": ">= 0.0.1",
|
||||
"qs": ">= 0.0.6"
|
||||
},
|
||||
|
||||
Submodule support/connect updated: b8e5d9c28e...76816734b8
Submodule support/jade updated: 9ec358e0f1...02f6fa456f
@@ -6,7 +6,8 @@
|
||||
var express = require('express')
|
||||
, connect = require('connect')
|
||||
, assert = require('assert')
|
||||
, should = require('should');
|
||||
, should = require('should')
|
||||
, Route = express.Route;
|
||||
|
||||
module.exports = {
|
||||
'test inheritance': function(){
|
||||
@@ -17,6 +18,7 @@ module.exports = {
|
||||
'test constructor exports': function(){
|
||||
express.should.have.property('HTTPServer');
|
||||
express.should.have.property('HTTPSServer');
|
||||
express.should.have.property('Route');
|
||||
},
|
||||
|
||||
'test connect middleware autoloaders': function(){
|
||||
@@ -435,173 +437,29 @@ module.exports = {
|
||||
);
|
||||
},
|
||||
|
||||
'test route middleware': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
function allow(role) {
|
||||
return function(req, res, next) {
|
||||
// this is totally not real, dont use this :)
|
||||
// for tests only
|
||||
if (req.headers['x-role'] == role) {
|
||||
next();
|
||||
} else {
|
||||
res.send(401);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function restrictAge(age) {
|
||||
return function(req, res, next){
|
||||
if (req.headers['x-age'] >= age) {
|
||||
next();
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
}
|
||||
'test routes with same callback': function(){
|
||||
function handle(req, res) {
|
||||
res.send('got ' + req.string);
|
||||
}
|
||||
|
||||
app.get('/xxx', allow('member'), restrictAge(18), function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
app.get('/booze', [allow('member')], restrictAge(18), function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
app.get('/tobi', [allow('member')], [[restrictAge(18)]], function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
['xxx', 'booze', 'tobi'].forEach(function(thing){
|
||||
assert.response(app,
|
||||
{ url: '/' + thing },
|
||||
{ body: 'Unauthorized', status: 401 });
|
||||
assert.response(app,
|
||||
{ url: '/' + thing, headers: { 'X-Role': 'member' }},
|
||||
{ body: 'Forbidden', status: 403 });
|
||||
assert.response(app,
|
||||
{ url: '/' + thing, headers: { 'X-Role': 'member', 'X-Age': 18 }},
|
||||
{ body: 'OK', status: 200 });
|
||||
});
|
||||
},
|
||||
|
||||
'test named capture groups': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/user/:id([0-9]{2,10})', function(req, res){
|
||||
res.send('user ' + req.params.id);
|
||||
});
|
||||
app.get('/', function(req, res, next){
|
||||
req.string = '/';
|
||||
next();
|
||||
}, handle);
|
||||
|
||||
app.get('/another', function(req, res, next){
|
||||
req.string = '/another';
|
||||
next();
|
||||
}, handle);
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12' },
|
||||
{ body: 'user 12' });
|
||||
|
||||
{ url: '/' },
|
||||
{ body: 'got /' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/ab' },
|
||||
{ body: 'Cannot GET /user/ab' });
|
||||
},
|
||||
|
||||
'test .param()': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
var users = [
|
||||
{ name: 'tj' }
|
||||
, { name: 'tobi' }
|
||||
, { name: 'loki' }
|
||||
, { name: 'jane' }
|
||||
, { name: 'bandit' }
|
||||
];
|
||||
|
||||
function integer(n){ return parseInt(n, 10); };
|
||||
app.param(['to', 'from'], integer);
|
||||
|
||||
app.param('user', function(req, res, next, id){
|
||||
if (req.user = users[id]) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('failed to find user'));
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/user/:user', function(req, res, next){
|
||||
res.send('user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.get('/users/:from-:to', function(req, res, next){
|
||||
var names = users.slice(req.params.from, req.params.to).map(function(user){
|
||||
return user.name;
|
||||
});
|
||||
res.send('users ' + names.join(', '));
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/0' },
|
||||
{ body: 'user tj' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/1' },
|
||||
{ body: 'user tobi' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/users/0-3' },
|
||||
{ body: 'users tj, tobi, loki' });
|
||||
},
|
||||
|
||||
'test OPTIONS': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', method: 'OPTIONS' },
|
||||
{ headers: { Allow: 'GET' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12', method: 'OPTIONS' },
|
||||
{ headers: { Allow: 'GET,PUT' }});
|
||||
},
|
||||
|
||||
'test app.lookup': function(){
|
||||
var app = express.createServer();
|
||||
app.get('/user', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/edit', function(){});
|
||||
|
||||
app.get('/user').should.have.length(1);
|
||||
app.get('/user/:id').should.have.length(1);
|
||||
app.get('/user/:id/:op?').should.have.length(1);
|
||||
app.put('/user/:id').should.have.length(1);
|
||||
app.get('/user/:id/edit').should.have.length(1);
|
||||
app.get('/').should.have.be.empty;
|
||||
app.all('/user/:id').should.have.length(2);
|
||||
|
||||
app.lookup.get('/user').should.have.length(1);
|
||||
app.lookup.get('/user/:id').should.have.length(1);
|
||||
app.lookup.get('/user/:id/:op?').should.have.length(1);
|
||||
app.lookup.put('/user/:id').should.have.length(1);
|
||||
app.lookup.get('/user/:id/edit').should.have.length(1);
|
||||
app.lookup.get('/').should.have.be.empty;
|
||||
app.lookup.all('/user/:id').should.have.length(2);
|
||||
},
|
||||
|
||||
'test app.match': function(){
|
||||
var app = express.createServer();
|
||||
app.get('/user', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/edit', function(){});
|
||||
|
||||
app.match.get('/user').should.have.length(1);
|
||||
app.match.get('/user/12').should.have.length(2);
|
||||
app.match.get('/user/12/:op?').should.have.length(1);
|
||||
app.match.put('/user/100').should.have.length(1);
|
||||
app.match.get('/user/5/edit').should.have.length(2);
|
||||
app.match.get('/').should.have.be.empty;
|
||||
app.match.all('/user/123').should.have.length(3);
|
||||
{ url: '/another' },
|
||||
{ body: 'got /another' });
|
||||
}
|
||||
};
|
||||
|
||||
1
test/fixtures/_foobar.jade
vendored
Normal file
1
test/fixtures/_foobar.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
p two
|
||||
1
test/fixtures/foobar.jade
vendored
Normal file
1
test/fixtures/foobar.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
p one
|
||||
2
test/fixtures/layout-switch.jade
vendored
Normal file
2
test/fixtures/layout-switch.jade
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
- layout('layouts/alternate')
|
||||
h1 My Page
|
||||
1
test/fixtures/layouts/alternate.jade
vendored
Normal file
1
test/fixtures/layouts/alternate.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#alternate!= body
|
||||
1
test/fixtures/object-item.jade
vendored
Normal file
1
test/fixtures/object-item.jade
vendored
Normal file
@@ -0,0 +1 @@
|
||||
li #{keyInCollection}: #{item}
|
||||
1
test/fixtures/some random text file.txt
vendored
Normal file
1
test/fixtures/some random text file.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
app.get('/html', function(req, res){
|
||||
res.send('<p>test</p>', { 'Content-Language': 'en' });
|
||||
});
|
||||
|
||||
|
||||
app.get('/json', function(req, res){
|
||||
res.header('X-Foo', 'bar');
|
||||
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
|
||||
@@ -38,6 +38,10 @@ module.exports = {
|
||||
res.send(404);
|
||||
});
|
||||
|
||||
app.get('/status/text', function(req, res){
|
||||
res.send('Oh noes!', 404);
|
||||
});
|
||||
|
||||
app.get('/error', function(req, res){
|
||||
res.send('Oh shit!', { 'Content-Type': 'text/plain' }, 500);
|
||||
});
|
||||
@@ -61,7 +65,7 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/bool' },
|
||||
{ body: 'true'
|
||||
, headers: { 'Content-Type': 'application/json' }});
|
||||
, headers: { 'Content-Type': 'application/json; charset=utf-8' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/html' },
|
||||
@@ -76,7 +80,7 @@ module.exports = {
|
||||
{ body: '{"foo":"bar"}'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -85,7 +89,7 @@ module.exports = {
|
||||
{ body: 'test({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript'
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -93,7 +97,7 @@ module.exports = {
|
||||
{ url: '/jsonp?callback=baz' },
|
||||
{ body: 'baz({"foo":"bar"});'
|
||||
, status: 201, headers: {
|
||||
'Content-Type': 'text/javascript'
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -102,7 +106,7 @@ module.exports = {
|
||||
{ body: 'invalid({"foo":"bar"});'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'text/javascript'
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -111,7 +115,7 @@ module.exports = {
|
||||
{ body: '{"foo":"bar"}'
|
||||
, status: 201
|
||||
, headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
, 'X-Foo': 'baz'
|
||||
}});
|
||||
|
||||
@@ -122,7 +126,11 @@ module.exports = {
|
||||
'Content-Type': 'text/plain'
|
||||
, 'X-Foo': 'bar'
|
||||
}});
|
||||
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/status/text' },
|
||||
{ body: 'Oh noes!', status: 404 });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/status' },
|
||||
{ body: 'Not Found'
|
||||
@@ -207,12 +215,14 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/javascripts/jquery.js' },
|
||||
{ body: 'whatever'
|
||||
, headers: { 'Content-Disposition': 'attachment; filename="jquery.js"' }});
|
||||
, headers: { 'Content-Type': 'application/javascript'
|
||||
, 'Content-Disposition': 'attachment; filename="jquery.js"' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/style.css' },
|
||||
{ body: 'some stylezzz'
|
||||
, headers: { 'Content-Disposition': 'attachment' }});
|
||||
, headers: { 'Content-Type': 'text/html; charset=utf-8'
|
||||
, 'Content-Disposition': 'attachment' }});
|
||||
},
|
||||
|
||||
'test #redirect()': function(){
|
||||
@@ -542,6 +552,10 @@ module.exports = {
|
||||
assert.equal(null, res.headers['content-disposition']);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/some%20random%20text%20file.txt' },
|
||||
{ body: 'hello' });
|
||||
|
||||
beforeExit(function(){
|
||||
calls.should.equal(1);
|
||||
});
|
||||
@@ -568,7 +582,7 @@ module.exports = {
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.clearCookie('rememberme');
|
||||
res.clearCookie('rememberme', { path: '/foo' });
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
@@ -576,7 +590,7 @@ module.exports = {
|
||||
{ url: '/' },
|
||||
function(res){
|
||||
res.headers['set-cookie']
|
||||
.should.eql(['rememberme=; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
|
||||
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
267
test/router.test.js
Normal file
267
test/router.test.js
Normal file
@@ -0,0 +1,267 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var express = require('express')
|
||||
, connect = require('connect')
|
||||
, assert = require('assert')
|
||||
, should = require('should')
|
||||
, Route = express.Route;
|
||||
|
||||
module.exports = {
|
||||
'test route middleware': function(beforeExit){
|
||||
var app = express.createServer()
|
||||
, calls = 0;
|
||||
|
||||
function allow(role) {
|
||||
return function(req, res, next) {
|
||||
// this is totally not real, dont use this :)
|
||||
// for tests only
|
||||
if (req.headers['x-role'] == role) {
|
||||
next();
|
||||
} else {
|
||||
res.send(401);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function restrictAge(age) {
|
||||
return function(req, res, next){
|
||||
if (req.headers['x-age'] >= age) {
|
||||
next();
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.param('user', function(req, res, next, user){
|
||||
++calls;
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/xxx', allow('member'), restrictAge(18), function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
app.get('/booze', [allow('member')], restrictAge(18), function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
app.get('/tobi', [allow('member')], [[restrictAge(18)]], function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
app.get('/user/:user', [allow('member'), [[restrictAge(18)]]], function(req, res){
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
['xxx', 'booze', 'tobi', 'user/tj'].forEach(function(thing){
|
||||
assert.response(app,
|
||||
{ url: '/' + thing },
|
||||
{ body: 'Unauthorized', status: 401 });
|
||||
assert.response(app,
|
||||
{ url: '/' + thing, headers: { 'X-Role': 'member' }},
|
||||
{ body: 'Forbidden', status: 403 });
|
||||
assert.response(app,
|
||||
{ url: '/' + thing, headers: { 'X-Role': 'member', 'X-Age': 18 }},
|
||||
{ body: 'OK', status: 200 });
|
||||
});
|
||||
|
||||
beforeExit(function(){
|
||||
calls.should.equal(3);
|
||||
});
|
||||
},
|
||||
|
||||
'test named capture groups': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/user/:id([0-9]{2,10})', function(req, res){
|
||||
res.send('user ' + req.params.id);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12' },
|
||||
{ body: 'user 12' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/ab' },
|
||||
{ body: 'Cannot GET /user/ab' });
|
||||
},
|
||||
|
||||
'test .param()': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
var users = [
|
||||
{ name: 'tj' }
|
||||
, { name: 'tobi' }
|
||||
, { name: 'loki' }
|
||||
, { name: 'jane' }
|
||||
, { name: 'bandit' }
|
||||
];
|
||||
|
||||
function integer(n){ return parseInt(n, 10); };
|
||||
app.param(['to', 'from'], integer);
|
||||
|
||||
app.param('user', function(req, res, next, id){
|
||||
if (req.user = users[id]) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('failed to find user'));
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/user/:user', function(req, res, next){
|
||||
res.send('user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.get('/users/:from-:to', function(req, res, next){
|
||||
var names = users.slice(req.params.from, req.params.to).map(function(user){
|
||||
return user.name;
|
||||
});
|
||||
res.send('users ' + names.join(', '));
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/0' },
|
||||
{ body: 'user tj' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/1' },
|
||||
{ body: 'user tobi' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/users/0-3' },
|
||||
{ body: 'users tj, tobi, loki' });
|
||||
},
|
||||
|
||||
'test OPTIONS': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', method: 'OPTIONS' },
|
||||
{ headers: { Allow: 'GET' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12', method: 'OPTIONS' },
|
||||
{ headers: { Allow: 'GET,PUT' }});
|
||||
},
|
||||
|
||||
'test app.lookup': function(){
|
||||
var app = express.createServer();
|
||||
app.get('/user', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/edit', function(){});
|
||||
|
||||
var route = app.get('/user/:id')[0]
|
||||
route.should.be.an.instanceof(Route);
|
||||
route.callback.should.be.a('function');
|
||||
route.path.should.equal('/user/:id');
|
||||
route.regexp.should.be.an.instanceof(RegExp);
|
||||
route.method.should.equal('GET');
|
||||
route.index.should.equal(1);
|
||||
route.keys.should.eql(['id']);
|
||||
|
||||
app.get('/user').should.have.length(1);
|
||||
app.get('/user/:id').should.have.length(1);
|
||||
app.get('/user/:id/:op?').should.have.length(1);
|
||||
app.put('/user/:id').should.have.length(1);
|
||||
app.get('/user/:id/edit').should.have.length(1);
|
||||
app.get('/').should.have.be.empty;
|
||||
app.all('/user/:id').should.have.length(2);
|
||||
|
||||
app.lookup.get('/user').should.have.length(1);
|
||||
app.lookup.get('/user/:id').should.have.length(1);
|
||||
app.lookup.get('/user/:id/:op?').should.have.length(1);
|
||||
app.lookup.put('/user/:id').should.have.length(1);
|
||||
app.lookup.get('/user/:id/edit').should.have.length(1);
|
||||
app.lookup.get('/').should.have.be.empty;
|
||||
app.lookup.all('/user/:id').should.have.length(2);
|
||||
app.lookup('/user/:id').should.have.length(2);
|
||||
},
|
||||
|
||||
'test app.remove': function(){
|
||||
var app = express.createServer();
|
||||
app.get('/user', function(){});
|
||||
app.get('/user', function(){});
|
||||
app.put('/user', function(){});
|
||||
|
||||
app.get('/user').should.have.length(2);
|
||||
var removed = app.remove.get('/user');
|
||||
removed.should.have.length(2);
|
||||
|
||||
var removed = app.remove.get('/user');
|
||||
removed.should.have.length(0);
|
||||
app.get('/user').should.have.length(0);
|
||||
|
||||
app.get('/user/:id', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.del('/user/:id', function(){});
|
||||
|
||||
app.remove.all('/user/:id').should.have.length(3);
|
||||
app.remove.all('/user/:id').should.have.length(0);
|
||||
|
||||
app.get('/user/:id', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.del('/user/:id', function(){});
|
||||
|
||||
app.remove('/user/:id').should.have.length(3);
|
||||
},
|
||||
|
||||
'test app.match': function(){
|
||||
var app = express.createServer();
|
||||
app.get('/user', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/edit', function(){});
|
||||
|
||||
var route = app.match.get('/user/12')[0];
|
||||
route.should.be.an.instanceof(Route);
|
||||
route.callback.should.be.a('function');
|
||||
route.path.should.equal('/user/:id');
|
||||
route.regexp.should.be.an.instanceof(RegExp);
|
||||
route.method.should.equal('GET');
|
||||
route.index.should.equal(2);
|
||||
route.keys.should.eql(['id']);
|
||||
route.params.id.should.equal('12');
|
||||
|
||||
app.match.get('/user').should.have.length(1);
|
||||
app.match.get('/user/12').should.have.length(2);
|
||||
app.match.get('/user/12/:op?').should.have.length(1);
|
||||
app.match.put('/user/100').should.have.length(1);
|
||||
app.match.get('/user/5/edit').should.have.length(2);
|
||||
app.match.get('/').should.have.be.empty;
|
||||
app.match.all('/user/123').should.have.length(3);
|
||||
app.match('/user/123').should.have.length(3);
|
||||
},
|
||||
|
||||
'test "case sensitive routes" setting': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.enable('case sensitive routes');
|
||||
|
||||
app.get('/account', function(req, res){
|
||||
res.send('account');
|
||||
});
|
||||
|
||||
app.get('/Account', function(req, res){
|
||||
res.send('Account');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/account' },
|
||||
{ body: 'account' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/Account' },
|
||||
{ body: 'Account' });
|
||||
}
|
||||
};
|
||||
@@ -287,6 +287,19 @@ module.exports = {
|
||||
{ body: '<cool><p>Welcome</p></cool>' });
|
||||
},
|
||||
|
||||
'test #render() view layout control': function(){
|
||||
var app = create();
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.render('layout-switch');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<div id="alternate"><h1>My Page</h1></div>' });
|
||||
},
|
||||
|
||||
'test #render() "view engine" with periods in dirname': function(){
|
||||
var app = create();
|
||||
app.set('view engine', 'jade');
|
||||
@@ -358,6 +371,19 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
'test #partial() collection object': function(){
|
||||
var app = create();
|
||||
|
||||
app.get('/', function(req, res){
|
||||
var items = { 2: 'foo', bar: 'bar' };
|
||||
res.partial('object-item.jade', { as: 'item', collection: items });
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<li>2: foo</li><li>bar: bar</li>' });
|
||||
},
|
||||
|
||||
'test #partial()': function(){
|
||||
var app = create();
|
||||
|
||||
@@ -631,6 +657,14 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/error' },
|
||||
{ status: 500 });
|
||||
|
||||
app.get('/underscore', function(req, res, next){
|
||||
res.partial('foobar');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/underscore' },
|
||||
{ body: '<p>two</p>' });
|
||||
},
|
||||
|
||||
'test #partial() with several calls': function(){
|
||||
|
||||
Reference in New Issue
Block a user