Compare commits

...

80 Commits
2.3.9 ... 2.4.2

Author SHA1 Message Date
Tj Holowaychuk
5ae994ee8f Release 2.4.2 2011-07-06 20:15:44 -07:00
Tj Holowaychuk
60d16eab77 Revert "removed jsonp stripping"
This reverts commit 0ae18bca60.
2011-07-06 20:14:42 -07:00
Tj Holowaychuk
fc60dfc1a6 docs 2011-07-06 09:59:09 -07:00
Tj Holowaychuk
45d149c146 docs for multiple envs in app.configure() calls 2011-07-06 09:58:32 -07:00
Tj Holowaychuk
4dfc1a69c3 Release 2.4.1 2011-07-06 09:57:06 -07:00
Tj Holowaychuk
0ae18bca60 removed jsonp stripping
I cannot recall why I added this, doesnt make
sense to me now haha.
2011-07-06 09:45:21 -07:00
Tj Holowaychuk
2aaf0defe7 res.send() using res.json() 2011-07-06 09:32:16 -07:00
Tj Holowaychuk
73ea5cd7ee Added res.json() JSONP support. Closes #737 2011-07-06 09:31:37 -07:00
Tj Holowaychuk
e7f2d229ec added failing jsonp tests for res.json() 2011-07-06 09:25:10 -07:00
Tj Holowaychuk
87bc265817 moved jsonp tests 2011-07-06 09:24:08 -07:00
Tj Holowaychuk
796aaff295 connect 1.5.2 2011-07-06 09:07:09 -07:00
Tj Holowaychuk
8f87c50320 fixed connect-redis example 2011-07-05 09:55:04 -07:00
Tj Holowaychuk
ee4471b345 when cookie path === null dont default it 2011-07-04 13:51:08 -07:00
Tj Holowaychuk
6815feb8cf docs 2011-07-04 13:45:08 -07:00
Tj Holowaychuk
dbbe7be891 Added extending-templates example. Closes #730 2011-07-04 13:34:41 -07:00
Tj Holowaychuk
09c9452e5c Changed; default cookie path to "home" setting. Closes #731 2011-07-04 13:18:50 -07:00
Tj Holowaychuk
b0e669ba00 remove pids/logs creation from express(1)
just confused people
2011-07-04 13:05:13 -07:00
Tj Holowaychuk
f46ae9f3b2 docs 2011-07-04 09:20:35 -07:00
Tj Holowaychuk
9f2b344be8 Added support for multiple env app.configure() calls. Closes #735 2011-07-04 09:16:12 -07:00
Tj Holowaychuk
6b47271679 updated error-pages example 2011-07-01 14:14:46 -07:00
Tj Holowaychuk
6f7075be74 Added "strict routing" setting for trailing slashes
for people who are:

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

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

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

this is a `Route` instance, so it has .path, .regexp, etc.
2011-05-27 09:12:49 -07:00
Tj Holowaychuk
1393187040 Merge branch 'refactor/executable' 2011-05-26 10:31:29 -07:00
Tj Holowaychuk
6e69c880d9 Added package.json generation support to express(1) 2011-05-26 10:31:21 -07:00
Tj Holowaychuk
59dcd03972 removed suggestions 2011-05-26 10:18:56 -07:00
Tj Holowaychuk
11482546a2 Fixed call to app.param() function for optional params. Closes #682 2011-05-26 09:56:04 -07:00
Tj Holowaychuk
1ce43dd347 added failing test for #682 2011-05-26 09:48:07 -07:00
Tj Holowaychuk
d1bfe137d4 test to ensure catch of invalid uri 2011-05-25 15:50:32 -07:00
Tj Holowaychuk
9d7452cdc2 more tests 2011-05-25 10:56:56 -07:00
38 changed files with 1042 additions and 256 deletions

View File

@@ -1,4 +1,55 @@
2.4.2. / 2011-07-06
==================
* Revert "removed jsonp stripping" for XSS
2.4.1 / 2011-07-06
==================
* Added `res.json()` JSONP support. Closes #737
* Added _extending-templates_ example. Closes #730
* Added "strict routing" setting for trailing slashes
* Added support for multiple envs in `app.configure()` calls. Closes #735
* Changed: `res.send()` using `res.json()`
* Changed: when cookie `path === null` don't default it
* Changed; default cookie path to "home" setting. Closes #731
* Removed _pids/logs_ creation from express(1)
2.4.0 / 2011-06-28
==================
* Added chainable `res.status(code)`
* Added `res.json()`, an explicit version of `res.send(obj)`
* Added simple web-service example
2.3.12 / 2011-06-22
==================
* \#express is now on freenode! come join!
* Added `req.get(field, param)`
* Added links to Japanese documentation, thanks @hideyukisaito!
* Added; the `express(1)` generated app outputs the env
* Added `content-negotiation` example
* Dependency: connect >= 1.5.1 < 2.0.0
* Fixed view layout bug. Closes #720
* Fixed; ignore body on 304. Closes #701
2.3.11 / 2011-06-04
==================
* Added `npm test`
* Removed generation of dummy test file from `express(1)`
* Fixed; `express(1)` adds express as a dep
* Fixed; prune on `prepublish`
2.3.10 / 2011-05-27
==================
* Added `req.route`, exposing the current route
* Added _package.json_ generation support to `express(1)`
* Fixed call to `app.param()` function for optional params. Closes #682
2.3.9 / 2011-05-25
==================

View File

@@ -20,6 +20,23 @@ or to access the `express(1)` executable install globally:
$ npm install -g express
## Quick Start
The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
Create the app:
$ npm install -g express
$ express /tmp/foo && cd /tmp/foo
Install dependencies:
$ npm install -d
Start the server:
$ node app.js
## Features
* Robust routing
@@ -58,6 +75,7 @@ The following are the major contributors of Express (in no specific order).
## More Information
* #express on freenode
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js with ease
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
@@ -67,6 +85,7 @@ The following are the major contributors of Express (in no specific order).
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
* [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito)
* Screencast - [Introduction](http://bit.ly/eRYu0O)
* Screencast - [View Partials](http://bit.ly/dU13Fx)
* Screencast - [Route Specific Middleware](http://bit.ly/hX4IaH)

View File

@@ -11,7 +11,7 @@ var fs = require('fs')
* Framework version.
*/
var version = '2.3.9';
var version = '2.4.2';
/**
* Add session support.
@@ -149,33 +149,6 @@ var stylus = [
, ' color #00B7FF'
].join('\n');
/**
* App test template.
*/
var appTest = [
""
, "// Run $ expresso"
, ""
, "/**"
, " * Module dependencies."
, " */"
, ""
, "var app = require('../app')"
, " , assert = require('assert');"
, "",
, "module.exports = {"
, " 'GET /': function(){"
, " assert.response(app,"
, " { url: '/' },"
, " { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }},"
, " function(res){"
, " assert.includes(res.body, '<title>Express</title>');"
, " });"
, " }"
, "};"
].join('\n');
/**
* App template.
*/
@@ -218,7 +191,7 @@ var app = [
, '});'
, ''
, 'app.listen(3000);'
, 'console.log("Express server listening on port %d", app.address().port);'
, 'console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);'
, ''
].join('\n');
@@ -287,8 +260,6 @@ while (args.length) {
function createApplicationAt(path) {
mkdir(path, function(){
mkdir(path + '/pids');
mkdir(path + '/logs');
mkdir(path + '/public/javascripts');
mkdir(path + '/public/images');
mkdir(path + '/public/stylesheets', function(){
@@ -318,9 +289,6 @@ function createApplicationAt(path) {
break;
}
});
mkdir(path + '/test', function(){
write(path + '/test/app.test.js', appTest);
});
// CSS Engine support
switch (cssEngine) {
@@ -343,19 +311,21 @@ function createApplicationAt(path) {
// Template support
app = app.replace(':TEMPLATE', templateEngine);
write(path + '/app.js', app);
// package.json
var json = '{\n';
json += ' "name": "application-name"\n';
json += ' , "version": "0.0.1"\n';
json += ' , "private": true\n';
json += ' , "dependencies": {\n';
json += ' "express": "' + version + '"\n';
if (cssEngine) json += ' , "' + cssEngine + '": ">= 0.0.1"\n';
if (templateEngine) json += ' , "' + templateEngine + '": ">= 0.0.1"\n';
json += ' }\n';
json += '}';
// Suggestions
process.on('exit', function(){
if (cssEngine) {
console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
, cssEngine
, cssEngine);
}
console.log(' - make sure you have installed %s: \x1b[33m$ npm install %s\x1b[0m'
, templateEngine
, templateEngine);
});
write(path + '/package.json', json);
write(path + '/app.js', app);
});
}

View File

@@ -1,6 +1,7 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {

View File

@@ -1,6 +1,7 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {
@@ -192,10 +193,9 @@
</ul>
<h3>Development Dependencies</h3>
<p>Express development dependencies are stored within the <em>./support</em> directory. To
update them execute:</p>
<p>First install the dev dependencies by executing the following command in the repo&rsquo;s directory:</p>
<pre><code>$ git submodule update --init
<pre><code>$ npm install
</code></pre>
<h3>Running Tests</h3>

View File

@@ -1,6 +1,7 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {

View File

@@ -1,6 +1,7 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {
@@ -179,6 +180,8 @@
</a>
<div id="wrapper">
<div id="container"><ul id="toc">
</ul><ul id="toc">
<li><a href="#installation">Installation</a></li>
<li><a href="#creating-a server">Creating A Server</a></li>
<li><a href="#creating-an https server">Creating An HTTPS Server</a></li>
@@ -202,6 +205,7 @@
<li><a href="#req.accepts()">accepts()</a></li>
<li><a href="#req.is()">is()</a></li>
<li><a href="#req.param()">param()</a></li>
<li><a href="#req.get()">get()</a></li>
<li><a href="#req.flash()">flash()</a></li>
<li><a href="#req.isxmlhttprequest">isXMLHttpRequest</a></li>
</ul></li>
@@ -255,6 +259,31 @@
<pre><code>$ npm install express
</code></pre>
<p>or to access the <code>express(1)</code> executable install globally:</p>
<pre><code>$ npm install -g express
</code></pre>
<h2>Quick Start</h2>
<p> The quickest way to get started with express is to utilize the executable <code>express(1)</code> to generate an application as shown below:</p>
<p> Create the app:</p>
<pre><code>$ npm install -g express
$ express /tmp/foo &amp;&amp; cd /tmp/foo
</code></pre>
<p> Install dependencies:</p>
<pre><code>$ npm install -d
</code></pre>
<p> Start the server:</p>
<pre><code>$ node app.js
</code></pre>
<h3 id="creating-a server">Creating A Server</h3>
<p> To create an instance of the <em>express.HTTPServer</em>, simply invoke the <em>createServer()</em> method. With our instance <em>app</em> we can then define routes based on the HTTP verbs, in this example <em>app.get()</em>.</p>
@@ -505,7 +534,7 @@ var app = express.createServer(
<p>Alternatively we can <em>use()</em> them which is useful when adding middleware within <em>configure()</em> blocks, in a progressive manor.</p>
<pre><code>app.use(express.logger({ format: ':method :uri' }));
<pre><code>app.use(express.logger({ format: ':method :url' }));
</code></pre>
<p>Typically with connect middleware you would <em>require(&lsquo;connect&rsquo;)</em> like so:</p>
@@ -772,16 +801,6 @@ is present, which is useful for developing apps that rely heavily on client-side
});
</code></pre>
<p>For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to <em>next(err)</em>.</p>
<pre><code>app.param('number', function(n){ return parseInt(n, 10); });
</code></pre>
<p>We may also apply the same callback to several placeholders, for example a route GET <em>/commits/:from-:to</em> are both numbers, so we may define them as an array:</p>
<pre><code>app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
</code></pre>
<h3 id="view-rendering">View Rendering</h3>
<p>View filenames take the form &ldquo;&lt;name&gt;.&lt;engine&gt;&rdquo;, where &lt;engine&gt; is the name
@@ -916,14 +935,14 @@ app.use(express.session({ secret: "keyboard cat" }));
<p>By default the <em>session</em> middleware uses the memory store bundled with Connect, however many implementations exist. For example <a href="http://github.com/visionmedia/connect-redis">connect-redis</a> supplies a <a href="http://code.google.com/p/redis/">Redis</a> session store and can be used as shown below:</p>
<pre><code>var RedisStore = require('connect-redis');
<pre><code>var RedisStore = require('connect-redis')(express);
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
</code></pre>
<p>Now the <em>req.session</em> and <em>req.sessionStore</em> properties will be accessible to all routes and subsequent middleware. Properties on <em>req.session</em> are automatically saved on a response, so for example if we wish to shopping cart data:</p>
<pre><code>var RedisStore = require('connect-redis');
<pre><code>var RedisStore = require('connect-redis')(express);
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
@@ -1067,6 +1086,18 @@ can perform any request assertion you wish.</p>
should be an object. This can be done by using
the _express.bodyParser middleware.</p>
<h3 id="req.get()">req.get(field, param)</h3>
<p> Get <em>field</em>&rsquo;s <em>param</em> value, defaulting to &lsquo;&rsquo; when the <em>param</em>
or <em>field</em> is not present.</p>
<pre><code> req.get('content-disposition', 'filename');
// =&gt; "something.png"
req.get('Content-Type', 'boundary');
// =&gt; "--foo-bar-baz"
</code></pre>
<h3 id="req.flash()">req.flash(type[, msg])</h3>
<p>Queue flash <em>msg</em> of the given <em>type</em>.</p>

View File

@@ -2,6 +2,27 @@
$ npm install express
or to access the `express(1)` executable install globally:
$ npm install -g express
## Quick Start
The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
Create the app:
$ npm install -g express
$ express /tmp/foo && cd /tmp/foo
Install dependencies:
$ npm install -d
Start the server:
$ node app.js
### Creating A Server
To create an instance of the _express.HTTPServer_, simply invoke the _createServer()_ method. With our instance _app_ we can then define routes based on the HTTP verbs, in this example _app.get()_.
@@ -50,6 +71,12 @@ otherwise the first call to _app.get()_, _app.post()_, etc will mount the routes
app.use(express.errorHandler());
});
For similar environments you may also pass several env strings:
app.configure('stage', 'prod', function(){
// config
});
For internal and arbitrary settings Express provides the _set(key[, val])_, _enable(key)_, _disable(key)_ methods:
app.configure(function(){
@@ -83,6 +110,8 @@ Express supports the following settings out of the box:
* _view options_ An object specifying global view options
* _view cache_ Enable view caching (enabled in production)
* _case sensitive routes_ Enable case-sensitive routing
* _strict routing_ When enabled trailing slashes are no longer ignored
* _jsonp callback_ Enable _res.send()_ / _res.json()_ transparent jsonp support
### Routing
@@ -594,13 +623,13 @@ Sessions support can be added by using Connect's _session_ middleware. To do so
By default the _session_ middleware uses the memory store bundled with Connect, however many implementations exist. For example [connect-redis](http://github.com/visionmedia/connect-redis) supplies a [Redis](http://code.google.com/p/redis/) session store and can be used as shown below:
var RedisStore = require('connect-redis');
var RedisStore = require('connect-redis')(express);
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
Now the _req.session_ and _req.sessionStore_ properties will be accessible to all routes and subsequent middleware. Properties on _req.session_ are automatically saved on a response, so for example if we wish to shopping cart data:
var RedisStore = require('connect-redis');
var RedisStore = require('connect-redis')(express);
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
@@ -732,6 +761,17 @@ To utilize urlencoded request bodies, _req.body_
should be an object. This can be done by using
the _express.bodyParser middleware.
### req.get(field, param)
Get _field_'s _param_ value, defaulting to '' when the _param_
or _field_ is not present.
req.get('content-disposition', 'filename');
// => "something.png"
req.get('Content-Type', 'boundary');
// => "--foo-bar-baz"
### req.flash(type[, msg])
Queue flash _msg_ of the given _type_.
@@ -883,6 +923,18 @@ it will not be set again.
Note that this method _end()_s the response, so you will want to use node's _res.write()_ for multiple writes or streaming.
### res.json(obj[, headers|status[, status]])
Send a JSON response with optional _headers_ and _status_. This method
is ideal for JSON-only APIs, however _res.send(obj)_ will send JSON as
well, though not ideal for cases when you want to send for example a string
as JSON, since the default for _res.send(string)_ is text/html.
res.json(null);
res.json({ user: 'tj' });
res.json('oh noes!', 500);
res.json('I dont have that', 404);
### res.redirect(url[, status])
Redirect to the given _url_ with a default response _status_ of 302.
@@ -899,7 +951,8 @@ the "home" setting and defaults to "/".
### res.cookie(name, val[, options])
Sets the given cookie _name_ to _val_, with options _httpOnly_, _secure_, _expires_ etc.
Sets the given cookie _name_ to _val_, with options _httpOnly_, _secure_, _expires_ etc. The _path_ option defaults to the app's "home" setting, which
is typically "/".
// "Remember me" for 15 minutes
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
@@ -918,7 +971,8 @@ To parse incoming _Cookie_ headers, use the _cookieParser_ middleware, which pro
### res.clearCookie(name[, options])
Clear cookie _name_ by setting "expires" far in the past.
Clear cookie _name_ by setting "expires" far in the past. Much like
_res.cookie()_ the _path_ option also defaults to the "home" setting.
res.clearCookie('rememberme');

View File

@@ -1,6 +1,7 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {
@@ -239,19 +240,24 @@ app.listen(3000);
<li><a href="http://github.com/visionmedia/express-messages">express-messages</a> flash message notification rendering</li>
<li><a href="http://github.com/visionmedia/express-configuration">express-configure</a> async configuration support (load settings from redis etc)</li>
<li><a href="http://github.com/visionmedia/express-namespace">express-namespace</a> namespaced routing support</li>
<li><a href="http://github.com/visionmedia/express-expose">express-expose</a> expose objects, functions, modules and more to client-side js</li>
<li><a href="https://github.com/visionmedia/express-params">express-params</a> app.param() extensions</li>
<li><a href="https://github.com/LearnBoost/express-mongoose">express-mongoose</a> plugin for easy rendering of Mongoose async Query results</li>
</ul>
<h2>More Information</h2>
<ul>
<li><a href="http://groups.google.com/group/express-js">Google Group</a> for discussion</li>
<li>#express on freenode</li>
<li>Follow <a href="http://twitter.com/tjholowaychuk">tjholowaychuk</a> on twitter for updates</li>
<li>View the <a href="http://senchalabs.github.com/connect">Connect</a> documentation</li>
<li>View the <a href="http://wiki.github.com/senchalabs/connect/">Connect Wiki</a> for contrib middleware</li>
<li>View the <a href="http://github.com/visionmedia/express/tree/master/examples/">examples</a></li>
<li>View the <a href="http://github.com/visionmedia/express">source</a></li>
<li>View the <a href="contrib.html">contrib guide</a></li>
<li><a href="http://groups.google.com/group/express-js">Google Group</a> for discussion</li>
<li>Visit the <a href="http://github.com/visionmedia/express/wiki">Wiki</a></li>
<li><a href="http://hideyukisaito.com/doc/expressjs/">日本語ドキュメンテーション</a> by <a href="https://github.com/hideyukisaito">hideyukisaito</a></li>
<li>Screencast &ndash; <a href="http://bit.ly/eRYu0O">Introduction</a></li>
<li>Screencast &ndash; <a href="http://bit.ly/dU13Fx">View Partials</a></li>
<li>Screencast &ndash; <a href="http://bit.ly/hX4IaH">Route Specific Middleware</a></li>
<li>Screencast &ndash; <a href="http://bit.ly/eNqmVs">Route Path Placeholder Preconditions</a></li>
</ul>
</div>

View File

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

View File

@@ -1,6 +1,7 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {

View File

@@ -1,6 +1,7 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {
@@ -326,7 +327,7 @@
<h3>partial() locals</h3>
<p> Both <em>res.partial()</em> and the <em>partial()</em> functions accept an single object consisting of both the options and the locals. Previously with Express 1.x you may pass <em>user</em> to a partial, along with <em>date</em> like so:</p>
<p> Both <em>res.partial()</em> and the <em>partial()</em> functions accept a single object consisting of both the options and the locals. Previously with Express 1.x you may pass <em>user</em> to a partial, along with <em>date</em> like so:</p>
<pre><code> partial('user', { object: user, locals: { date: new Date }})
</code></pre>
@@ -367,7 +368,7 @@
<h3>View Partial Lookup</h3>
<p> Previously partials were loaded relative to the now removed <em>view partials</em> directory setting, or by default <em>views/partials</em>, now they are relative to the view calling them, read more on <a href="guide.html#View-Lookup">view lookup</a>.</p>
<p> Previously partials were loaded relative to the now removed <em>view partials</em> directory setting, or by default <em>views/partials</em>, now they are relative to the view calling them, read more on <a href="guide.html#view-lookup">view lookup</a>.</p>
<h3>Mime Types</h3>

View File

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

View File

@@ -1,6 +1,7 @@
<html>
<head>
<title>Express - node web framework</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ var app = express.createServer(
express.favicon(),
// Custom logger format
express.logger({ format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time' }),
express.logger({ format: '\x1b[36m:method\x1b[0m \x1b[90m:url\x1b[0m :response-time' }),
// Provides req.cookies
express.cookieParser(),
@@ -36,9 +36,8 @@ app.get('/forget', function(req, res){
});
app.post('/', function(req, res){
if (req.body.remember) {
res.cookie('remember', '1', { path: '/', expires: new Date(Date.now() + 900000), httpOnly: true });
}
var minute = 60000;
if (req.body.remember) res.cookie('remember', 1, { maxAge: minute });
res.redirect('back');
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,21 +5,26 @@
var express = require('../../lib/express');
// $ npm install connect-redis
var RedisStore = require('connect-redis');
// pass the express to the connect redis module
// allowing it to inherit from express.session.Store
var RedisStore = require('connect-redis')(express);
var app = express.createServer(
express.logger(),
var app = express.createServer();
// Required by session() middleware
express.cookieParser(),
app.use(express.favicon());
// Populates:
// - req.session
// - req.sessionStore
// - req.sessionID (or req.session.id)
express.session({ secret: 'keyboard cat', store: new RedisStore })
);
// request logging
app.use(express.logger());
// required to parse the session cookie
app.use(express.cookieParser());
// Populates:
// - req.session
// - req.sessionStore
// - req.sessionID (or req.session.id)
app.use(express.session({ secret: 'keyboard cat', store: new RedisStore }));
app.get('/', function(req, res){
var body = '';

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

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

View File

@@ -28,7 +28,7 @@ var exports = module.exports = connect.middleware;
* Framework version.
*/
exports.version = '2.3.9';
exports.version = '2.4.2';
/**
* Shortcut for `new Server(...)`.

View File

@@ -471,17 +471,37 @@ app.redirect = function(key, url){
};
/**
* Configure callback for the given `env`.
* Configure callback for zero or more envs,
* when no env is specified that callback will
* be invoked for all environments. Any combination
* can be used multiple times, in any order desired.
*
* @param {String} env
* Examples:
*
* app.configure(function(){
* // executed for all envs
* });
*
* app.configure('stage', function(){
* // executed staging env
* });
*
* app.configure('stage', 'production', function(){
* // executed for stage and production
* });
*
* @param {String} env...
* @param {Function} fn
* @return {Server} for chaining
* @api public
*/
app.configure = function(env, fn){
if ('function' == typeof env) fn = env, env = 'all';
if ('all' == env || env == this.settings.env) fn.call(this);
var envs = 'all'
, args = toArray(arguments);
fn = args.pop();
if (args.length) envs = args;
if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
return this;
};

View File

@@ -65,6 +65,28 @@ req.header = function(name, defaultValue){
}
};
/**
* Get `field`'s `param` value, defaulting to ''.
*
* Examples:
*
* req.get('content-disposition', 'filename');
* // => "something.png"
*
* @param {String} field
* @param {String} param
* @return {String}
* @api public
*/
req.get = function(field, param){
var val = this.header(field);
if (!val) return '';
var regexp = new RegExp(param + ' *= *(?:"([^"]+)"|([^;]+))', 'i');
if (!regexp.exec(val)) return '';
return RegExp.$1 || RegExp.$2;
};
/**
* Check if the _Accept_ header is present, and includes the given `type`.
*
@@ -106,16 +128,14 @@ req.accepts = function(type){
return true;
} else if (type) {
// allow "html" vs "text/html" etc
if (type.indexOf('/') < 0) {
type = mime.lookup(type);
}
if (!~type.indexOf('/')) type = mime.lookup(type);
// check if we have a direct match
if (~accept.indexOf(type)) return true;
// check if we have type/*
type = type.split('/')[0] + '/*';
return accept.indexOf(type) >= 0;
return !!~accept.indexOf(type);
} else {
return false;
}

View File

@@ -75,16 +75,7 @@ res.send = function(body, headers, status){
this.contentType('.bin');
}
} else {
if (!this.header('Content-Type')) {
this.charset = this.charset || 'utf-8';
this.contentType('.json');
}
body = JSON.stringify(body);
if (this.req.query.callback && this.app.set('jsonp callback')) {
this.charset = this.charset || 'utf-8';
this.header('Content-Type', 'text/javascript');
body = this.req.query.callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
}
return this.json(body, headers, status);
}
break;
}
@@ -106,7 +97,7 @@ res.send = function(body, headers, status){
}
// strip irrelevant headers
if (204 == status) {
if (204 == status || 304 == status) {
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
}
@@ -114,6 +105,53 @@ res.send = function(body, headers, status){
// respond
this.statusCode = status;
this.end('HEAD' == this.req.method ? undefined : body);
return this;
};
/**
* Send JSON response with `obj`, optional `headers`, and optional `status`.
*
* Examples:
*
* res.json(null);
* res.json({ user: 'tj' });
* res.json('oh noes!', 500);
* res.json('I dont have that', 404);
*
* @param {Mixed} obj
* @param {Object|Number} headers or status
* @param {Number} status
* @return {ServerResponse}
* @api public
*/
res.json = function(obj, headers, status){
var body = JSON.stringify(obj)
, callback = this.req.query.callback
, jsonp = this.app.enabled('jsonp callback');
this.charset = this.charset || 'utf-8';
this.header('Content-Type', 'application/json');
if (callback && jsonp) {
this.header('Content-Type', 'text/javascript');
body = callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
}
return this.send(body, headers, status);
};
/**
* Set status `code`.
*
* @param {Number} code
* @return {ServerResponse}
* @api public
*/
res.status = function(code){
this.statusCode = code;
return this;
};
/**
@@ -190,7 +228,7 @@ res.attachment = function(filename){
* Transfer the file at the given `path`, with optional
* `filename` as an attachment and optional callback `fn(err)`,
* and optional `fn2(err)` which is invoked when an error has
* occurred after headers have been sent.
* occurred after header has been sent.
*
* @param {String} path
* @param {String|Function} filename or fn
@@ -266,6 +304,7 @@ res.clearCookie = function(name, options){
* Options:
*
* - `maxAge` max-age in milliseconds, converted to `expires`
* - `path` defaults to the "home" setting which is typically "/"
*
* Examples:
*
@@ -284,6 +323,7 @@ res.clearCookie = function(name, options){
res.cookie = function(name, val, options){
options = options || {};
if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge);
if (undefined === options.path) options.path = this.app.set('home');
var cookie = utils.serializeCookie(name, val, options);
this.header('Set-Cookie', cookie);
};
@@ -360,9 +400,7 @@ res.redirect = function(url, status){
// Relative
if (!~url.indexOf('://')) {
// Respect mount-point
if (app.route) {
url = join(app.route, url);
}
if (app.route) url = join(app.route, url);
// Absolute
var host = req.headers.host

View File

@@ -193,7 +193,7 @@ Router.prototype._dispatch = function(req, res, next){
}
// match route
route = self._match(req, i);
req.route = route = self._match(req, i);
// implied OPTIONS
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
@@ -209,8 +209,8 @@ Router.prototype._dispatch = function(req, res, next){
(function param(err) {
var key = keys[i++]
, val = req.params[key]
, fn = params[key]
, val = key && req.params[key.name]
, fn = key && params[key.name]
, ret;
try {
@@ -218,7 +218,7 @@ Router.prototype._dispatch = function(req, res, next){
nextRoute();
} else if (err) {
next(err);
} else if (fn) {
} else if (fn && undefined !== val) {
fn(req, res, param, val);
} else if (key) {
param();
@@ -329,7 +329,7 @@ Router.prototype._match = function(req, i){
? decodeURIComponent(captures[j])
: captures[j];
if (key) {
route.params[key] = val;
route.params[key.name] = val;
} else {
route.params.push(val);
}
@@ -373,6 +373,7 @@ Router.prototype._route = function(method, path, fn){
// create the route
var route = new Route(method, path, fn, {
sensitive: app.enabled('case sensitive routes')
, strict: app.enabled('strict routing')
, middleware: middleware
});

View File

@@ -17,7 +17,8 @@ module.exports = Route;
*
* Options:
*
* - `sensitive` enable case-sensitive routes
* - `sensitive` enable case-sensitive routes
* - `strict` enable strict matching for trailing slashes
* - `middleware` array of middleware
*
* @param {String} method
@@ -32,8 +33,11 @@ function Route(method, path, fn, options) {
this.callback = fn;
this.path = path;
this.method = method;
this.regexp = normalize(path, this.keys = [], options.sensitive);
this.middleware = options.middleware;
this.regexp = normalize(path
, this.keys = []
, options.sensitive
, options.strict);
}
/**
@@ -60,17 +64,18 @@ Route.prototype.match = function(path){
* @param {String|RegExp} path
* @param {Array} keys
* @param {Boolean} sensitive
* @param {Boolean} strict
* @return {RegExp}
* @api private
*/
function normalize(path, keys, sensitive) {
if (path instanceof RegExp) return path;
function normalize(path, keys, sensitive, strict) {
if (path instanceof RegExp) return path;
path = path
.concat('/?')
.concat(strict ? '' : '/?')
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
keys.push(key);
keys.push({ name: key, optional: !! optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)

View File

@@ -93,20 +93,23 @@ exports.compile = function(view, cache, cid, options){
*/
exports.lookup = function(view, options){
var orig = view = new View(view, options);
var orig = view = new View(view, options)
, partial = options.isPartial
, layout = options.isLayout;
// Try _ prefix ex: ./views/_<name>.jade
// taking precedence over the direct path
if (partial) {
view = new View(orig.prefixPath, options);
if (!view.exists) view = orig;
}
// Try index ex: ./views/user/index.jade
if (!view.exists) view = new View(orig.indexPath, options);
if (!layout && !view.exists) view = new View(orig.indexPath, options);
// Try ../<name>/index ex: ../user/index.jade
// when calling partial('user') within the same dir
if (!view.exists && !options.isLayout) view = new View(orig.upIndexPath, options);
if (!layout && !view.exists) view = new View(orig.upIndexPath, options);
// Try root ex: <root>/user.jade
if (!view.exists) view = new View(orig.rootPath, options);
@@ -162,7 +165,7 @@ function renderPartial(res, view, options, parentLocals, parent){
if (locals) merge(options, locals);
// Partials dont need layouts
options.renderPartial = true;
options.isPartial = true;
options.layout = false;
// Deduce name from view path
@@ -315,8 +318,7 @@ res.render = function(view, opts, fn, parent, sub){
// callback given
if (fn) {
fn(err);
// unwind to root call to prevent
// several next(err) calls
// unwind to root call to prevent multiple callbacks
} else if (sub) {
throw err;
// root template, next(err)
@@ -360,7 +362,7 @@ res._render = function(view, opts, fn, parent, sub){
// capture attempts
options.attempts = [];
var partial = options.renderPartial
var partial = options.isPartial
, layout = options.layout;
// Layout support

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "2.3.9",
"version": "2.4.2",
"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.4.1 < 2.0.0",
"connect": ">= 1.5.2 < 2.0.0",
"mime": ">= 0.0.1",
"qs": ">= 0.0.6"
},
@@ -30,5 +30,9 @@
"repository": "git://github.com/visionmedia/express",
"main": "index",
"bin": { "express": "./bin/express" },
"scripts": {
"test": "make test",
"prepublish" : "npm prune"
},
"engines": { "node": ">= 0.4.1 < 0.5.0" }
}

View File

@@ -236,7 +236,6 @@ module.exports = {
var server = express.createServer();
server.set('env', 'development');
// Config blocks
var ret = server.configure(function(){
assert.equal(this, server, 'Test context of configure() is the server');
calls.push('any');
@@ -297,7 +296,23 @@ module.exports = {
{ url: '/' },
{ body: 'first route last' });
},
'test #configure() multiple envs': function(){
var app = express.createServer();
app.set('env', 'prod');
var calls = [];
app.configure('stage', 'prod', function(){
calls.push('stage/prod');
});
app.configure('prod', function(){
calls.push('prod');
});
calls.should.eql(['stage/prod', 'prod']);
},
'test #set()': function(){
var app = express.createServer();
var ret = app.set('title', 'My App').set('something', 'else');
@@ -461,5 +476,17 @@ module.exports = {
assert.response(app,
{ url: '/another' },
{ body: 'got /another' });
},
'invalid chars': function(){
var app = express.createServer();
app.get('/:name', function(req, res, next){
res.send('invalid');
});
assert.response(app,
{ url: '/%a0' },
{ status: 500 });
}
};

View File

@@ -303,5 +303,36 @@ module.exports = {
assert.response(app,
{ url: '/incorrect', headers: { Referer: 'expressjs.com' }},
{ body: 'expressjs.com' });
},
'test #get(field, param)': function(){
var app = express.createServer();
app.get('/', function(req, res, next){
req.get('content-disposition', 'filename')
.should.equal('foo bar.jpg');
req.get('Content-Disposition', 'filename')
.should.equal('foo bar.jpg');
req.get('x-content-foo', 'foo').should.equal('bar');
req.get('x-content-foo', 'bar').should.equal('foo bar baz');
req.get('x-content-foo', 'woot').should.equal('tobi loki jane');
req.get('cache-control', 'max-age').should.equal('500');
req.get('foo').should.equal('');
req.get('foo', 'bar').should.equal('');
res.end();
});
var fields = {
'Content-Disposition': 'attachment; filename="foo bar.jpg"'
, 'X-Content-Foo': 'foo=bar; bar=foo bar baz; woot=tobi loki jane;'
, 'Cache-Control': 'max-age = 500'
};
assert.response(app,
{ url: '/', headers: fields },
{ body: '' });
}
};

View File

@@ -9,6 +9,57 @@ var express = require('express')
, should = require('should');
module.exports = {
'test #json()': function(){
var app = express.createServer()
, json = 'application/json; charset=utf-8';
app.get('/user', function(req, res, next){
res.json({ name: 'tj' });
});
app.get('/string', function(req, res, next){
res.json('whoop!');
});
app.get('/error', function(req, res, next){
res.json('oh noes!', 500);
});
app.get('/headers', function(req, res, next){
res.json(undefined, { 'X-Foo': 'bar' }, 302);
});
assert.response(app,
{ url: '/error' },
{ body: '"oh noes!"'
, status: 500
, headers: { 'Content-Type': json }});
assert.response(app,
{ url: '/string' },
{ body: '"whoop!"'
, headers: {
'Content-Type': json
, 'Content-Length': 8
}});
assert.response(app,
{ url: '/user' },
{ body: '{"name":"tj"}', headers: { 'Content-Type': json }});
},
'test #status()': function(){
var app = express.createServer();
app.get('/error', function(req, res, next){
res.status(500).send('OH NO');
});
assert.response(app,
{ url: '/error' },
{ body: 'OH NO', status: 500 });
},
'test #send()': function(){
var app = express.createServer();
@@ -21,13 +72,6 @@ module.exports = {
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
});
app.get('/jsonp', function(req, res){
app.enable('jsonp callback');
res.header('X-Foo', 'bar');
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
app.disable('jsonp callback');
});
app.get('/text', function(req, res){
res.header('X-Foo', 'bar');
res.contentType('txt');
@@ -84,41 +128,6 @@ module.exports = {
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/jsonp?callback=test' },
{ body: 'test({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/jsonp?callback=baz' },
{ body: 'baz({"foo":"bar"});'
, status: 201, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/jsonp?callback=invalid()[]' },
{ body: 'invalid({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/json?callback=test' },
{ body: '{"foo":"bar"}'
, status: 201
, headers: {
'Content-Type': 'application/json; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/text' },
{ body: 'wahoo'
@@ -167,8 +176,91 @@ module.exports = {
assert.equal(undefined, res.headers['content-type']);
assert.equal(undefined, res.headers['content-length']);
});
assert.response(app,
{ url: '/json?callback=test' },
{ body: '{"foo":"bar"}'
, status: 201
, headers: {
'Content-Type': 'application/json; charset=utf-8'
, 'X-Foo': 'baz'
}});
},
'test #send() JSONP': function(){
var app = express.createServer();
app.enable('jsonp callback');
app.get('/jsonp', function(req, res){
res.header('X-Foo', 'bar');
res.send({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
});
assert.response(app,
{ url: '/jsonp?callback=test' },
{ body: 'test({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/jsonp?callback=baz' },
{ body: 'baz({"foo":"bar"});'
, status: 201, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/jsonp?callback=invalid()[]' },
{ body: 'invalid({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
},
'test #json() JSONP': function(){
var app = express.createServer();
app.enable('jsonp callback');
app.get('/jsonp', function(req, res){
res.header('X-Foo', 'bar');
res.json({ foo: 'bar' }, { 'X-Foo': 'baz' }, 201);
});
assert.response(app,
{ url: '/jsonp?callback=test' },
{ body: 'test({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/jsonp?callback=baz' },
{ body: 'baz({"foo":"bar"});'
, status: 201, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
assert.response(app,
{ url: '/jsonp?callback=invalid()[]' },
{ body: 'invalid({"foo":"bar"});'
, status: 201
, headers: {
'Content-Type': 'text/javascript; charset=utf-8'
, 'X-Foo': 'baz'
}});
},
'test #contentType()': function(){
var app = express.createServer();
@@ -561,15 +653,55 @@ module.exports = {
});
},
'test #cookie()': function(){
'test #cookie() path default': function(){
var app = express.createServer();
app.set('home', '/foo');
app.get('/', function(req, res){
res.cookie('rememberme', 'yes', { expires: new Date(1), httpOnly: true });
res.cookie('something', 'else');
res.redirect('/');
});
assert.response(app,
{ url: '/', headers: { Host: 'foo.com' }},
function(res){
res.headers['set-cookie']
.should.eql(['rememberme=yes; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else; path=/foo']);
});
},
'test #cookie() explicit path': function(){
var app = express.createServer();
app.set('/home', '/foo');
app.get('/', function(req, res){
res.cookie('rememberme', 'yes', { path: '/', expires: new Date(1), httpOnly: true });
res.cookie('something', 'else');
res.redirect('/');
});
assert.response(app,
{ url: '/', headers: { Host: 'foo.com' }},
function(res){
res.headers['set-cookie']
.should.eql(['rememberme=yes; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else; path=/']);
});
},
'test #cookie() null path': function(){
var app = express.createServer();
app.set('/home', '/foo');
app.get('/', function(req, res){
res.cookie('rememberme', 'yes', { path: null, expires: new Date(1), httpOnly: true });
res.cookie('something', 'else', { path: null });
res.redirect('/');
});
assert.response(app,
{ url: '/', headers: { Host: 'foo.com' }},
function(res){
@@ -577,10 +709,30 @@ module.exports = {
.should.eql(['rememberme=yes; expires=Thu, 01 Jan 1970 00:00:00 GMT; httpOnly', 'something=else']);
});
},
'test #clearCookie()': function(){
'test #clearCookie() default path': function(){
var app = express.createServer();
app.set('home', '/foo');
app.get('/', function(req, res){
res.clearCookie('rememberme');
res.redirect('/');
});
assert.response(app,
{ url: '/' },
function(res){
res.headers['set-cookie']
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
});
},
'test #clearCookie() explicit path': function(){
var app = express.createServer();
app.set('home', '/bar');
app.get('/', function(req, res){
res.clearCookie('rememberme', { path: '/foo' });
res.redirect('/');
@@ -593,7 +745,7 @@ module.exports = {
.should.eql(['rememberme=; path=/foo; expires=Thu, 01 Jan 1970 00:00:00 GMT']);
});
},
'test HEAD': function(){
var app = express.createServer();

View File

@@ -74,6 +74,38 @@ module.exports = {
});
},
'test precedence': function(){
var app = express.createServer();
var hits = [];
app.all('*', function(req, res, next){
hits.push('all');
next();
});
app.get('/foo', function(req, res, next){
hits.push('GET /foo');
next();
});
app.get('/foo', function(req, res, next){
hits.push('GET /foo2');
next();
});
app.put('/foo', function(req, res, next){
hits.push('PUT /foo');
next();
});
assert.response(app,
{ url: '/foo' },
function(){
hits.should.eql(['all', 'GET /foo', 'GET /foo2']);
});
},
'test named capture groups': function(){
var app = express.createServer();
@@ -106,7 +138,7 @@ module.exports = {
{ body: 'Cannot GET /user/ab' });
},
'test .param()': function(){
'test app.param()': function(){
var app = express.createServer();
var users = [
@@ -137,6 +169,35 @@ module.exports = {
{ url: '/user/1' },
{ body: 'user tobi' });
},
'test app.param() optional execution': function(beforeExit){
var app = express.createServer()
, calls = 0;
var months = ['Jan', 'Feb', 'Mar'];
app.param('month', function(req, res, next, n){
req.params.month = months[n];
++calls;
next();
});
app.get('/calendar/:month?', function(req, res, next){
res.send(req.params.month || months[0]);
});
assert.response(app,
{ url: '/calendar' },
{ body: 'Jan' });
assert.response(app,
{ url: '/calendar/1' },
{ body: 'Feb' });
beforeExit(function(){
calls.should.equal(1);
});
},
'test OPTIONS': function(){
var app = express.createServer();
@@ -168,7 +229,7 @@ module.exports = {
route.path.should.equal('/user/:id');
route.regexp.should.be.an.instanceof(RegExp);
route.method.should.equal('get');
route.keys.should.eql(['id']);
route.keys.should.eql([{ name: 'id', optional: false }]);
app.get('/user').should.have.length(1);
app.get('/user/:id').should.have.length(1);
@@ -230,7 +291,7 @@ module.exports = {
route.path.should.equal('/user/:id');
route.regexp.should.be.an.instanceof(RegExp);
route.method.should.equal('get');
route.keys.should.eql(['id']);
route.keys.should.eql([{ name: 'id', optional: false }]);
//route.params.id.should.equal('12');
app.match.get('/user').should.have.length(1);
@@ -256,7 +317,29 @@ module.exports = {
app.match.all('/user/12').should.have.length(0);
app.get('/user/:id').should.have.length(0);
},
'test "strict routing" setting': function(){
var app = express.createServer();
app.enable('strict routing');
app.get('/:path', function(req, res, next){
res.send({ type: 'directory' });
});
app.get('/:path/', function(req, res, next){
res.send(['.', '..', 'foo.js', 'bar.js']);
});
assert.response(app,
{ url: '/lib' },
{ body: '{"type":"directory"}' });
assert.response(app,
{ url: '/lib/' },
{ body: '[".","..","foo.js","bar.js"]' });
},
'test "case sensitive routes" setting': function(){
var app = express.createServer();
@@ -298,5 +381,27 @@ module.exports = {
assert.response(app,
{ url: '/foo', method: 'OPTIONS' },
{ body: 'whatever', headers: { Allow: 'GET' }});
},
'test req.route': function(){
var app = express.createServer();
var routes = [];
app.get('/:foo?', function(req, res, next){
routes.push(req.route.path);
next();
});
app.get('/foo', function(req, res, next){
routes.push(req.route.path);
next();
});
assert.response(app,
{ url: '/foo' },
function(){
routes.should.eql(['/:foo?', '/foo']);
});
}
};