Compare commits

...

94 Commits

Author SHA1 Message Date
TJ Holowaychuk
20e8f08cb2 Release 3.0.0rc2 2012-08-03 13:32:53 -07:00
TJ Holowaychuk
bac0c64633 escape res.redirect() link 2012-08-02 19:41:37 -07:00
TJ Holowaychuk
48923055eb docs 2012-08-01 13:18:30 -07:00
TJ Holowaychuk
0f20a5e06a add CORS example 2012-08-01 13:17:46 -07:00
TJ Holowaychuk
56bfb9249f Merge branch 'master' of github.com:visionmedia/express 2012-07-31 20:51:07 -07:00
TJ Holowaychuk
5ed1544cab remove generated docs 2012-07-31 20:50:52 -07:00
TJ Holowaychuk
e5c7be9364 Merge pull request #1250 from silvinci/1249-mvc-boot.js-fix-rc
Fixed double inclusion of methods in mvc example
2012-07-26 12:53:06 -07:00
Jan Buschtöns
73ce9d028c Line 40 removed. Fixed! 2012-07-26 21:46:22 +02:00
TJ Holowaychuk
75debbe5bc update connect dep 2012-07-25 09:26:20 -07:00
TJ Holowaychuk
5f33d89ea5 fix vhost example 2012-07-24 15:41:12 -07:00
TJ Holowaychuk
42fd29efe8 deprecate .createServer() & remove old stale examples 2012-07-24 15:40:05 -07:00
TJ Holowaychuk
2d91eac811 Release 3.0.0rc1 2012-07-24 13:32:49 -07:00
TJ Holowaychuk
a50f02e87d update cookie dep 2012-07-24 13:30:44 -07:00
TJ Holowaychuk
214f913b0c merge 2012-07-24 13:28:13 -07:00
TJ Holowaychuk
1021c86300 update connect dep 2012-07-24 13:25:50 -07:00
TJ Holowaychuk
386516815a Merge branch 'master' of github.com:visionmedia/express 2012-07-24 09:57:00 -07:00
TJ Holowaychuk
d5e5647bba fix express(1) -h flag, use -H for hogan. Closes #1245 2012-07-24 09:56:48 -07:00
TJ Holowaychuk
a861ea7eaf Merge pull request #1243 from saintedlama/master
Fixes path joining in app stub generator on win32 systems
2012-07-24 08:46:11 -07:00
TJ Holowaychuk
cb844132e6 Merge pull request #1244 from RubenVerborgh/master
EventEmitter memory leak with successful sendfile
2012-07-24 08:36:53 -07:00
Ruben Verborgh
8050308706 Remove socket error handler if file was sent successfully. 2012-07-24 11:57:02 +02:00
Christoph Walcher
e79f72bf88 Fixes path joining in win32 systems 2012-07-24 07:18:16 +02:00
TJ Holowaychuk
07b6c9f563 update connect dep 2012-07-23 11:35:54 -07:00
TJ Holowaychuk
8f4e61a474 fix res.render docs 2012-07-20 17:22:21 -07:00
TJ Holowaychuk
54d37c60f5 add more examples to view-locals 2012-07-18 10:41:07 -07:00
TJ Holowaychuk
a93d375acc add res.redirect("//foo.com") support 2012-07-18 09:29:11 -07:00
TJ Holowaychuk
d66f0e5eb9 change res.redirect() to use scheme-relative urls 2012-07-18 09:07:21 -07:00
TJ Holowaychuk
e84db12783 update send dep 2012-07-16 19:27:53 -07:00
TJ Holowaychuk
1ad2ecefe8 Release 3.0.0beta7 2012-07-16 09:25:03 -07:00
TJ Holowaychuk
08b68ec8cd udpate connect dep 2012-07-16 09:23:40 -07:00
TJ Holowaychuk
48be9233d8 add res.download() content-disposition on error removal test 2012-07-13 09:24:09 -07:00
TJ Holowaychuk
a512d9b47d Release 3.0.0beta6 2012-07-13 09:19:24 -07:00
TJ Holowaychuk
62234cc106 clean up package.json 2012-07-13 09:00:26 -07:00
TJ Holowaychuk
ab61837885 change res.sendfile() to use send() module 2012-07-13 08:58:40 -07:00
TJ Holowaychuk
7a5041bf9c keywords 2012-07-12 12:51:29 -07:00
TJ Holowaychuk
4d219135b2 update connect dep 2012-07-12 12:03:21 -07:00
TJ Holowaychuk
26eeb64640 add err.view property for view errors. Closes #1226 2012-07-11 08:45:37 -07:00
riadh
5426eb0b62 add "jsonp callback name" setting 2012-07-06 08:53:31 -07:00
TJ Holowaychuk
b9e32ec2c4 styling 2012-07-06 08:15:28 -07:00
TJ Holowaychuk
32b8613708 fix matchRequest tests 2012-07-06 08:14:53 -07:00
TJ Holowaychuk
90eddb3439 dont .toLowerCase() twice 2012-07-06 08:12:47 -07:00
TJ Holowaychuk
accd6180c1 rename matchReq -> matchRequest 2012-07-06 08:12:33 -07:00
TJ Holowaychuk
e770b674ff Merge pull request #1212 from riadhchtara/Issue944
issue#944 : Changing Router#match() signature
2012-07-06 08:08:11 -07:00
TJ Holowaychuk
4f7c4d1051 remove app.locals.use and res.locals.use
there are a few reasons for this:

  a) less API, simpler implementation ...
  b) difficult to inherit cleanly from subapps
  c) effectively the same as parallelized middleware (use connect-parallel for example)

lastly this api in a sense promotes some obscure uses since
they may be scattered throughout rather than explicitly
given to specific routes or used globally as middleware etc
2012-07-05 18:46:19 -07:00
TJ Holowaychuk
78845e7d23 fix app.locals.use() when mounting apps 2012-07-05 18:31:02 -07:00
TJ Holowaychuk
93f1cecc92 update connect dep 2012-07-05 09:43:24 -07:00
riadh
8ccd89f6ad Update Issue944 2012-07-05 13:32:33 +03:00
riadh
0a210cce7a fix to test 2012-07-04 18:51:48 +02:00
riadhchtara
f110248462 Issue 944:
Changing Router#match() signature from (req[, i]) to (method, url[, i]), and making it public
2012-07-04 13:19:40 +02:00
TJ Holowaychuk
96e4014a70 Replace res.send() with res.send = require("response-send") 2012-07-03 21:09:17 -07:00
TJ Holowaychuk
2173d00829 whitespace ocd 2012-07-03 13:57:40 -07:00
riadh
3827f5ef8b tests for issues 1115 fix for utils 2012-07-03 13:56:21 -07:00
riadh
73bed61afa * immediately after :param would become not greedy 2012-07-03 13:56:21 -07:00
TJ Holowaychuk
ee89ff5026 Release 3.0.0beta5 2012-07-03 10:20:19 -07:00
TJ Holowaychuk
8df9e745d5 Merge branch 'master' of github.com:visionmedia/express 2012-07-03 09:39:52 -07:00
TJ Holowaychuk
c76e954504 upgrade connect dep 2012-07-03 09:30:53 -07:00
TJ Holowaychuk
136f054614 Merge pull request #1202 from strk/make_check
Add "make check" support.
2012-07-02 11:41:09 -07:00
Sandro Santilli
e9a4b4f8ff Add "make check" support.
This is to follow GNU coding stadards. See:
www.gnu.org/prep/standards/html_node/Standard-Targets.html
2012-07-02 15:45:33 +02:00
TJ Holowaychuk
5415c98ff9 add route-map tests 2012-06-30 12:18:32 -07:00
TJ Holowaychuk
39efa452fc Added route-map example 2012-06-29 09:01:06 -07:00
TJ Holowaychuk
bddcdee3fe docs 2012-06-27 13:58:35 -07:00
TJ Holowaychuk
2e8d44b444 docs 2012-06-27 13:36:12 -07:00
TJ Holowaychuk
70d68419b0 Added res.json(obj, status) support back for BC 2012-06-27 13:34:53 -07:00
TJ Holowaychuk
26fab2a27d update auth example to utilize cores pbkdf2 2012-06-27 13:20:39 -07:00
TJ Holowaychuk
0f5560eebd updated tests to use "supertest" 2012-06-26 17:14:07 -07:00
TJ Holowaychuk
170dcc2907 Added "methods" dep 2012-06-26 11:38:55 -07:00
TJ Holowaychuk
18d6c78ef4 fixed a test race condition 2012-06-25 12:42:51 -07:00
TJ Holowaychuk
1248f0338b Release 3.0.0beta4 2012-06-25 12:08:22 -07:00
TJ Holowaychuk
b400814d00 Added req.auth
tests to come
2012-06-22 16:25:31 -07:00
TJ Holowaychuk
a934929bb3 update connect dep 2012-06-22 10:48:07 -07:00
TJ Holowaychuk
140efb574c Revert "Added + support to the router"
This reverts commit 6aaa7dc26d.
2012-06-22 08:20:23 -07:00
TJ Holowaychuk
3c0c96114f Added res.send(body, status) support back for backwards compat 2012-06-21 16:37:26 -07:00
TJ Holowaychuk
362c96c8c1 refactor res.redirect() relative check 2012-06-21 08:25:43 -07:00
TJ Holowaychuk
5db3d0f9fc Merge branch 'master' of github.com:visionmedia/express 2012-06-18 09:37:37 -07:00
TJ Holowaychuk
80c814c393 Merge pull request #1182 from langpavel/patch-1
Refactoring: no need for add 'del' and 'all' to methods
2012-06-18 09:37:25 -07:00
TJ Holowaychuk
bf66465937 Merge pull request #1183 from nullfirm/master
Re: [express] add hogan.js template engine for express@3.0 (#1176)
2012-06-17 21:36:24 -07:00
Min-su Ok
72eea7e6cd modify from --hjs to --hogan. 2012-06-18 13:17:51 +09:00
TJ Holowaychuk
8f4cd13c89 docs 2012-06-17 19:00:41 -07:00
Pavel Lang
6556cefc71 Refactoring: no need for add 'del' and 'all' to methods 2012-06-18 04:55:25 +03:00
TJ Holowaychuk
7bfb58920a docs 2012-06-17 18:08:35 -07:00
TJ Holowaychuk
2e324ccf5f docs 2012-06-17 18:08:14 -07:00
TJ Holowaychuk
1a10ee76b3 update range-parser dep 2012-06-17 18:06:04 -07:00
TJ Holowaychuk
619e6349f6 ws 2012-06-17 17:48:52 -07:00
TJ Holowaychuk
b1ff68548f add inline range example 2012-06-17 17:41:28 -07:00
TJ Holowaychuk
376b6c3bad add note about inclusive ranges 2012-06-17 17:37:42 -07:00
TJ Holowaychuk
f4c8a59b17 Added req.range(size) 2012-06-17 17:33:05 -07:00
TJ Holowaychuk
e6129d8ba5 Added res.links(obj) 2012-06-17 16:34:42 -07:00
TJ Holowaychuk
d2baf11b8a docs 2012-06-17 13:21:13 -07:00
TJ Holowaychuk
82b5b12ca7 Added .default() support to res.format() 2012-06-17 13:15:55 -07:00
TJ Holowaychuk
c145ab9b81 Update mkdirp 2012-06-15 16:15:16 -07:00
TJ Holowaychuk
c10223b803 GET / HEAD only for req.fresh 2012-06-15 16:09:32 -07:00
TJ Holowaychuk
1e09b54ad2 update fresh 2012-06-15 16:07:27 -07:00
TJ Holowaychuk
d073e0aeb5 Added 2xx / 304 check to req.fresh 2012-06-15 16:03:20 -07:00
TJ Holowaychuk
ce7293de13 misc 2012-06-15 15:44:44 -07:00
TJ Holowaychuk
108e66c24b Fixed res.send() freshness check, respect res.statusCode 2012-06-15 15:42:46 -07:00
91 changed files with 1223 additions and 2394 deletions

View File

@@ -1,4 +1,62 @@
3.0.0rc2 / 2012-08-03
==================
* add CORS example
* update connect dep
* deprecate `.createServer()` & remove old stale examples
* fix: escape `res.redirect()` link
* fix vhost example
3.0.0rc1 / 2012-07-24
==================
* add more examples to view-locals
* add scheme-relative redirects (`res.redirect("//foo.com")`) support
* update cookie dep
* update connect dep
* update send dep
* fix `express(1)` -h flag, use -H for hogan. Closes #1245
* fix `res.sendfile()` socket error handling regression
3.0.0beta7 / 2012-07-16
==================
* update connect dep for `send()` root normalization regression
3.0.0beta6 / 2012-07-13
==================
* add `err.view` property for view errors. Closes #1226
* add "jsonp callback name" setting
* add support for "/foo/:bar*" non-greedy matches
* change `res.sendfile()` to use `send()` module
* change `res.send` to use "response-send" module
* remove `app.locals.use` and `res.locals.use`, use regular middleware
3.0.0beta5 / 2012-07-03
==================
* add "make check" support
* add route-map example
* add `res.json(obj, status)` support back for BC
* add "methods" dep, remove internal methods module
* update connect dep
* update auth example to utilize cores pbkdf2
* updated tests to use "supertest"
3.0.0beta4 / 2012-06-25
==================
* Added `req.auth`
* Added `req.range(size)`
* Added `res.links(obj)`
* Added `res.send(body, status)` support back for backwards compat
* Added `.default()` support to `res.format()`
* Added 2xx / 304 check to `req.fresh`
* Revert "Added + support to the router"
* Fixed `res.send()` freshness check, respect res.statusCode
3.0.0beta3 / 2012-06-15
==================

View File

@@ -1,24 +1,20 @@
MOCHA_OPTS=
REPORTER = dot
docs: docs/express.md
docs/express.md: docs/application.md docs/request.md docs/response.md
cat $^ > $@
docs/%.md: lib/%.js
@mkdir -p docs
dox --raw < $< | ./support/docs > $@
check: test
test: test-unit test-acceptance
test-unit:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER)
--reporter $(REPORTER) \
$(MOCHA_OPTS)
test-acceptance:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--bail \
test/acceptance/*.js
test-cov: lib-cov
@@ -27,10 +23,7 @@ test-cov: lib-cov
lib-cov:
@jscoverage lib lib-cov
docclean:
rm -fr docs
benchmark:
@./support/bench
.PHONY: docs docclean test test-unit test-acceptance benchmark
.PHONY: test test-unit test-acceptance benchmark

View File

@@ -19,7 +19,7 @@ program
.option('-s, --sessions', 'add session support')
.option('-e, --ejs', 'add ejs engine support (defaults to jade)')
.option('-J, --jshtml', 'add jshtml engine support (defaults to jade)')
.option('-h, --hjs', 'add hogan.js engine support')
.option('-H, --hogan', 'add hogan.js engine support')
.option('-c, --css <engine>', 'add stylesheet <engine> support (less|stylus) (defaults to plain css)')
.option('-f, --force', 'force on non-empty directory')
.parse(process.argv);
@@ -37,7 +37,7 @@ var eol = 'win32' == os.platform() ? '\r\n' : '\n'
program.template = 'jade';
if (program.ejs) program.template = 'ejs';
if (program.jshtml) program.template = 'jshtml';
if (program.hjs) program.template = 'hjs';
if (program.hogan) program.template = 'hjs';
/**
* Routes index template.
@@ -127,7 +127,7 @@ var jshtmlIndex = [
/**
* Hogan.js index template.
*/
var hjsIndex = [
var hoganIndex = [
'<!DOCTYPE html>'
, '<html>'
, ' <head>'
@@ -195,7 +195,8 @@ var app = [
, ''
, 'var express = require(\'express\')'
, ' , routes = require(\'./routes\')'
, ' , http = require(\'http\');'
, ' , http = require(\'http\')'
, ' , path = require(\'path\');'
, ''
, 'var app = express();'
, ''
@@ -208,7 +209,7 @@ var app = [
, ' app.use(express.bodyParser());'
, ' app.use(express.methodOverride());{sess}'
, ' app.use(app.router);{css}'
, ' app.use(express.static(__dirname + \'/public\'));'
, ' app.use(express.static(path.join(__dirname, \'public\')));'
, '});'
, ''
, 'app.configure(\'development\', function(){'
@@ -295,7 +296,7 @@ function createApplicationAt(path) {
write(path + '/views/index.jshtml', jshtmlIndex);
break;
case 'hjs':
write(path + '/views/index.hjs', hjsIndex);
write(path + '/views/index.hjs', hoganIndex);
break;
}

View File

@@ -1,181 +0,0 @@
# app
Application prototype.
# app.use()
Proxy `connect#use()` to apply settings to
mounted applications.
# app.engine()
Register the given template engine callback `fn`
as `ext`.
By default will `require()` the engine based on the
file extension. For example if you try to render
a "foo.jade" file Express will invoke the following internally:
app.engine('jade', require('jade').__express);
For engines that do not provide `.__express` out of the box,
or if you wish to "map" a different extension to the template engine
you may use this method. For example mapping the EJS template engine to
".html" files
app.engine('html', require('ejs').renderFile);
In this case EJS provides a `.renderFile()` method with
the same signature that Express expects: `(path, options, callback)`,
though note that it aliases this method as `ejs.__express` internally
so if you're using ".ejs" extensions you dont need to do anything.
Some template engines do not follow this convention, the
[Consolidate.js](https://github.com/visionmedia/consolidate.js)
library was created to map all of node's popular template
engines to follow this convention, thus allowing them to
work seemlessly within Express.
# app.param()
Map the given param placeholder `name`(s) to the given callback(s).
Parameter mapping is used to provide pre-conditions to routes
which use normalized placeholders. For example a _:user_id_ parameter
could automatically load a user's information from the database without
any additional code,
The callback uses the samesignature as middleware, the only differencing
being that the value of the placeholder is passed, in this case the _id_
of the user. Once the `next()` function is invoked, just like middleware
it will continue on to execute the route, or subsequent parameter functions.
app.param('user_id', function(req, res, next, id){
User.find(id, function(err, user){
if (err) {
next(err);
} else if (user) {
req.user = user;
next();
} else {
next(new Error('failed to load user'));
}
});
});
# app.set()
Assign `setting` to `val`, or return `setting`'s value.
app.set('foo', 'bar');
app.get('foo');
// => "bar"
Mounted servers inherit their parent server's settings.
# app.enabled()
Check if `setting` is enabled (truthy).
app.enabled('foo')
// => false
app.enable('foo')
app.enabled('foo')
// => true
# app.disabled()
Check if `setting` is disabled.
app.disabled('foo')
// => true
app.enable('foo')
app.disabled('foo')
// => false
# app.enable()
Enable `setting`.
# app.disable()
Disable `setting`.
# app.configure()
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.
## 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
});
## Note
These callbacks are invoked immediately, and
are effectively sugar for the following.
var env = process.env.NODE_ENV || 'development';
switch (env) {
case 'development':
...
break;
case 'stage':
...
break;
case 'production':
...
break;
}
# app.all()
Special-cased "all" method, applying the given route `path`,
middleware, and callback to _every_ HTTP method.
# app.render()
Render the given view `name` name with `options`
and a callback accepting an error and the
rendered template string.
## Example
app.render('email', { name: 'Tobi' }, function(err, html){
// ...
})
# app.listen()
Listen for connections.
A node `http.Server` is returned, with this
application (which is a `Function`) as its
callback. If you wish to create both an HTTP
and HTTPS server you may do so with the "http"
and "https" modules as shown here.
var http = require('http')
, https = require('https')
, express = require('express')
, app = express();
http.createServer(app).listen(80);
http.createServer({ ... }, app).listen(443);

View File

@@ -1,506 +0,0 @@
# app
Application prototype.
# app.use()
Proxy `connect#use()` to apply settings to
mounted applications.
# app.engine()
Register the given template engine callback `fn`
as `ext`.
By default will `require()` the engine based on the
file extension. For example if you try to render
a "foo.jade" file Express will invoke the following internally:
app.engine('jade', require('jade').__express);
For engines that do not provide `.__express` out of the box,
or if you wish to "map" a different extension to the template engine
you may use this method. For example mapping the EJS template engine to
".html" files
app.engine('html', require('ejs').renderFile);
In this case EJS provides a `.renderFile()` method with
the same signature that Express expects: `(path, options, callback)`,
though note that it aliases this method as `ejs.__express` internally
so if you're using ".ejs" extensions you dont need to do anything.
Some template engines do not follow this convention, the
[Consolidate.js](https://github.com/visionmedia/consolidate.js)
library was created to map all of node's popular template
engines to follow this convention, thus allowing them to
work seemlessly within Express.
# app.param()
Map the given param placeholder `name`(s) to the given callback(s).
Parameter mapping is used to provide pre-conditions to routes
which use normalized placeholders. For example a _:user_id_ parameter
could automatically load a user's information from the database without
any additional code,
The callback uses the samesignature as middleware, the only differencing
being that the value of the placeholder is passed, in this case the _id_
of the user. Once the `next()` function is invoked, just like middleware
it will continue on to execute the route, or subsequent parameter functions.
app.param('user_id', function(req, res, next, id){
User.find(id, function(err, user){
if (err) {
next(err);
} else if (user) {
req.user = user;
next();
} else {
next(new Error('failed to load user'));
}
});
});
# app.set()
Assign `setting` to `val`, or return `setting`'s value.
app.set('foo', 'bar');
app.get('foo');
// => "bar"
Mounted servers inherit their parent server's settings.
# app.enabled()
Check if `setting` is enabled (truthy).
app.enabled('foo')
// => false
app.enable('foo')
app.enabled('foo')
// => true
# app.disabled()
Check if `setting` is disabled.
app.disabled('foo')
// => true
app.enable('foo')
app.disabled('foo')
// => false
# app.enable()
Enable `setting`.
# app.disable()
Disable `setting`.
# app.configure()
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.
## 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
});
## Note
These callbacks are invoked immediately, and
are effectively sugar for the following.
var env = process.env.NODE_ENV || 'development';
switch (env) {
case 'development':
...
break;
case 'stage':
...
break;
case 'production':
...
break;
}
# app.all()
Special-cased "all" method, applying the given route `path`,
middleware, and callback to _every_ HTTP method.
# app.render()
Render the given view `name` name with `options`
and a callback accepting an error and the
rendered template string.
## Example
app.render('email', { name: 'Tobi' }, function(err, html){
// ...
})
# app.listen()
Listen for connections.
A node `http.Server` is returned, with this
application (which is a `Function`) as its
callback. If you wish to create both an HTTP
and HTTPS server you may do so with the "http"
and "https" modules as shown here.
var http = require('http')
, https = require('https')
, express = require('express')
, app = express();
http.createServer(app).listen(80);
http.createServer({ ... }, app).listen(443);
# req
Request prototype.
# req.get
Return request header.
The `Referrer` header field is special-cased,
both `Referrer` and `Referer` are interchangeable.
## Examples
req.get('Content-Type');
// => "text/plain"
req.get('content-type');
// => "text/plain"
req.get('Something');
// => undefined
Aliased as `req.header()`.
# req.accepts()
Check if the given `type(s)` is acceptable, returning
the best match when true, otherwise `undefined`, in which
case you should respond with 406 "Not Acceptable".
The `type` value may be a single mime type string
such as "application/json", the extension name
such as "json", a comma-delimted list such as "json, html, text/plain",
or an array `["json", "html", "text/plain"]`. When a list
or array is given the _best_ match, if any is returned.
## Examples
// Accept: text/html
req.accepts('html');
// => "html"
// Accept: text/*, application/json
req.accepts('html');
// => "html"
req.accepts('text/html');
// => "text/html"
req.accepts('json, text');
// => "json"
req.accepts('application/json');
// => "application/json"
// Accept: text/*, application/json
req.accepts('image/png');
req.accepts('png');
// => undefined
// Accept: text/*;q=.5, application/json
req.accepts(['html', 'json']);
req.accepts('html, json');
// => "json"
# req.acceptsCharset()
Check if the given `charset` is acceptable,
otherwise you should respond with 406 "Not Acceptable".
# req.acceptsLanguage()
Check if the given `lang` is acceptable,
otherwise you should respond with 406 "Not Acceptable".
# req.param()
Return the value of param `name` when present or `defaultValue`.
- Checks route placeholders, ex: _/user/:id_
- Checks body params, ex: id=12, {"id":12}
- Checks query string params, ex: ?id=12
To utilize request bodies, `req.body`
should be an object. This can be done by using
the `connect.bodyParser()` middleware.
# req.is()
Check if the incoming request contains the "Content-Type"
header field, and it contains the give mime `type`.
## Examples
// With Content-Type: text/html; charset=utf-8
req.is('html');
req.is('text/html');
req.is('text/*');
// => true
// When Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true
req.is('html');
// => false
# res
Response prototype.
# res.status()
Set status `code`.
# res.send()
Send a response.
## Examples
res.send(new Buffer('wahoo'));
res.send({ some: 'json' });
res.send('<p>some html</p>');
res.send(404, 'Sorry, cant find that');
res.send(404);
# res.json()
Send JSON response.
## Examples
res.json(null);
res.json({ user: 'tj' });
res.json(500, 'oh noes!');
res.json(404, 'I dont have that');
# res.sendfile()
Transfer the file at the given `path`.
Automatically sets the _Content-Type_ response header field.
The callback `fn(err)` is invoked when the transfer is complete
or when an error occurs. Be sure to check `res.sentHeader`
if you wish to attempt responding, as the header and some data
may have already been transferred.
## Options
- `maxAge` defaulting to 0
- `root` root directory for relative filenames
## Examples
The following example illustrates how `res.sendfile()` may
be used as an alternative for the `static()` middleware for
dynamic situations. The code backing `res.sendfile()` is actually
the same code, so HTTP cache support etc is identical.
app.get('/user/:uid/photos/:file', function(req, res){
var uid = req.params.uid
, file = req.params.file;
req.user.mayViewFilesFrom(uid, function(yes){
if (yes) {
res.sendfile('/uploads/' + uid + '/' + file);
} else {
res.send(403, 'Sorry! you cant see that.');
}
});
});
# res.download()
Transfer the file at the given `path` as an attachment.
Optionally providing an alternate attachment `filename`,
and optional callback `fn(err)`. The callback is invoked
when the data transfer is complete, or when an error has
ocurred. Be sure to check `res.headerSent` if you plan to respond.
This method uses `res.sendfile()`.
# res.format()
Respond to the Acceptable formats using an `obj`
of mime-type callbacks.
This method uses `req.accepted`, an array of
acceptable types ordered by their quality values.
When "Accept" is not present the _first_ callback
is invoked, otherwise the first match is used. When
no match is performed the server responds with
406 "Not Acceptable".
Content-Type is set for you, however if you choose
you may alter this within the callback using `res.type()`
or `res.set('Content-Type', ...)`.
res.format({
'text/plain': function(){
res.send('hey');
},
'text/html': function(){
res.send('<p>hey</p>');
},
'appliation/json': function(){
res.send({ message: 'hey' });
}
});
In addition to canonicalized MIME types you may
also use extnames mapped to these types:
res.format({
text: function(){
res.send('hey');
},
html: function(){
res.send('<p>hey</p>');
},
json: function(){
res.send({ message: 'hey' });
}
});
By default Express passes an `Error`
with a `.status` of 406 to `next(err)`
if a match is not made, however you may
provide an optional callback `fn` to
be invoked instead.
# res.attachment()
Set _Content-Disposition_ header to _attachment_ with optional `filename`.
# res.set
Set header `field` to `val`, or pass
an object of header fields.
## Examples
res.set('Accept', 'application/json');
res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
Aliased as `res.header()`.
# res.get()
Get value for header `field`.
# res.clearCookie()
Clear cookie `name`.
# res.cookie()
Set cookie `name` to `val`, with the given `options`.
## Options
- `maxAge` max-age in milliseconds, converted to `expires`
- `signed` sign the cookie
- `path` defaults to "/"
## Examples
// "Remember Me" for 15 minutes
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
// save as above
res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
# res.redirect()
Redirect to the given `url` with optional response `status`
defaulting to 302.
The given `url` can also be the name of a mapped url, for
example by default express supports "back" which redirects
to the _Referrer_ or _Referer_ headers or "/".
## Examples
res.redirect('/foo/bar');
res.redirect('http://example.com');
res.redirect(301, 'http://example.com');
res.redirect('../login'); // /blog/post/1 -> /blog/login
## Mounting
When an application is mounted, and `res.redirect()`
is given a path that does _not_ lead with "/". For
example suppose a "blog" app is mounted at "/blog",
the following redirect would result in "/blog/login":
res.redirect('login');
While the leading slash would result in a redirect to "/login":
res.redirect('/login');
# res.render()
Render `view` with the given `options` and optional callback `fn`.
When a callback function is given a response will _not_ be made
automatically, otherwise a response of _200_ and _text/html_ is given.
## Options
- `status` Response status code (`res.statusCode`)
- `charset` Set the charset (`res.charset`)
## Reserved locals
- `cache` boolean hinting to the engine it should cache
- `filename` filename of the view being rendered

View File

@@ -1,107 +0,0 @@
# req
Request prototype.
# req.get
Return request header.
The `Referrer` header field is special-cased,
both `Referrer` and `Referer` are interchangeable.
## Examples
req.get('Content-Type');
// => "text/plain"
req.get('content-type');
// => "text/plain"
req.get('Something');
// => undefined
Aliased as `req.header()`.
# req.accepts()
Check if the given `type(s)` is acceptable, returning
the best match when true, otherwise `undefined`, in which
case you should respond with 406 "Not Acceptable".
The `type` value may be a single mime type string
such as "application/json", the extension name
such as "json", a comma-delimted list such as "json, html, text/plain",
or an array `["json", "html", "text/plain"]`. When a list
or array is given the _best_ match, if any is returned.
## Examples
// Accept: text/html
req.accepts('html');
// => "html"
// Accept: text/*, application/json
req.accepts('html');
// => "html"
req.accepts('text/html');
// => "text/html"
req.accepts('json, text');
// => "json"
req.accepts('application/json');
// => "application/json"
// Accept: text/*, application/json
req.accepts('image/png');
req.accepts('png');
// => undefined
// Accept: text/*;q=.5, application/json
req.accepts(['html', 'json']);
req.accepts('html, json');
// => "json"
# req.acceptsCharset()
Check if the given `charset` is acceptable,
otherwise you should respond with 406 "Not Acceptable".
# req.acceptsLanguage()
Check if the given `lang` is acceptable,
otherwise you should respond with 406 "Not Acceptable".
# req.param()
Return the value of param `name` when present or `defaultValue`.
- Checks route placeholders, ex: _/user/:id_
- Checks body params, ex: id=12, {"id":12}
- Checks query string params, ex: ?id=12
To utilize request bodies, `req.body`
should be an object. This can be done by using
the `connect.bodyParser()` middleware.
# req.is()
Check if the incoming request contains the "Content-Type"
header field, and it contains the give mime `type`.
## Examples
// With Content-Type: text/html; charset=utf-8
req.is('html');
req.is('text/html');
req.is('text/*');
// => true
// When Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true
req.is('html');
// => false

View File

@@ -1,218 +0,0 @@
# res
Response prototype.
# res.status()
Set status `code`.
# res.send()
Send a response.
## Examples
res.send(new Buffer('wahoo'));
res.send({ some: 'json' });
res.send('<p>some html</p>');
res.send(404, 'Sorry, cant find that');
res.send(404);
# res.json()
Send JSON response.
## Examples
res.json(null);
res.json({ user: 'tj' });
res.json(500, 'oh noes!');
res.json(404, 'I dont have that');
# res.sendfile()
Transfer the file at the given `path`.
Automatically sets the _Content-Type_ response header field.
The callback `fn(err)` is invoked when the transfer is complete
or when an error occurs. Be sure to check `res.sentHeader`
if you wish to attempt responding, as the header and some data
may have already been transferred.
## Options
- `maxAge` defaulting to 0
- `root` root directory for relative filenames
## Examples
The following example illustrates how `res.sendfile()` may
be used as an alternative for the `static()` middleware for
dynamic situations. The code backing `res.sendfile()` is actually
the same code, so HTTP cache support etc is identical.
app.get('/user/:uid/photos/:file', function(req, res){
var uid = req.params.uid
, file = req.params.file;
req.user.mayViewFilesFrom(uid, function(yes){
if (yes) {
res.sendfile('/uploads/' + uid + '/' + file);
} else {
res.send(403, 'Sorry! you cant see that.');
}
});
});
# res.download()
Transfer the file at the given `path` as an attachment.
Optionally providing an alternate attachment `filename`,
and optional callback `fn(err)`. The callback is invoked
when the data transfer is complete, or when an error has
ocurred. Be sure to check `res.headerSent` if you plan to respond.
This method uses `res.sendfile()`.
# res.format()
Respond to the Acceptable formats using an `obj`
of mime-type callbacks.
This method uses `req.accepted`, an array of
acceptable types ordered by their quality values.
When "Accept" is not present the _first_ callback
is invoked, otherwise the first match is used. When
no match is performed the server responds with
406 "Not Acceptable".
Content-Type is set for you, however if you choose
you may alter this within the callback using `res.type()`
or `res.set('Content-Type', ...)`.
res.format({
'text/plain': function(){
res.send('hey');
},
'text/html': function(){
res.send('<p>hey</p>');
},
'appliation/json': function(){
res.send({ message: 'hey' });
}
});
In addition to canonicalized MIME types you may
also use extnames mapped to these types:
res.format({
text: function(){
res.send('hey');
},
html: function(){
res.send('<p>hey</p>');
},
json: function(){
res.send({ message: 'hey' });
}
});
By default Express passes an `Error`
with a `.status` of 406 to `next(err)`
if a match is not made, however you may
provide an optional callback `fn` to
be invoked instead.
# res.attachment()
Set _Content-Disposition_ header to _attachment_ with optional `filename`.
# res.set
Set header `field` to `val`, or pass
an object of header fields.
## Examples
res.set('Accept', 'application/json');
res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
Aliased as `res.header()`.
# res.get()
Get value for header `field`.
# res.clearCookie()
Clear cookie `name`.
# res.cookie()
Set cookie `name` to `val`, with the given `options`.
## Options
- `maxAge` max-age in milliseconds, converted to `expires`
- `signed` sign the cookie
- `path` defaults to "/"
## Examples
// "Remember Me" for 15 minutes
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
// save as above
res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
# res.redirect()
Redirect to the given `url` with optional response `status`
defaulting to 302.
The given `url` can also be the name of a mapped url, for
example by default express supports "back" which redirects
to the _Referrer_ or _Referer_ headers or "/".
## Examples
res.redirect('/foo/bar');
res.redirect('http://example.com');
res.redirect(301, 'http://example.com');
res.redirect('../login'); // /blog/post/1 -> /blog/login
## Mounting
When an application is mounted, and `res.redirect()`
is given a path that does _not_ lead with "/". For
example suppose a "blog" app is mounted at "/blog",
the following redirect would result in "/blog/login":
res.redirect('login');
While the leading slash would result in a redirect to "/login":
res.redirect('/login');
# res.render()
Render `view` with the given `options` and optional callback `fn`.
When a callback function is given a response will _not_ be made
automatically, otherwise a response of _200_ and _text/html_ is given.
## Options
- `status` Response status code (`res.statusCode`)
- `charset` Set the charset (`res.charset`)
## Reserved locals
- `cache` boolean hinting to the engine it should cache
- `filename` filename of the view being rendered

View File

@@ -4,20 +4,24 @@
*/
var express = require('../../lib/express')
, crypto = require('crypto');
, hash = require('./pass').hash;
var app = module.exports = express();
// config
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
// middleware
app.use(express.bodyParser());
app.use(express.cookieParser('shhhh, very secret'));
app.use(express.session());
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
// Session-persisted message middleware
app.locals.use(function(req,res){
app.use(function(req, res, next){
var err = req.session.error
, msg = req.session.success;
delete req.session.error;
@@ -25,26 +29,25 @@ app.locals.use(function(req,res){
res.locals.message = '';
if (err) res.locals.message = '<p class="msg error">' + err + '</p>';
if (msg) res.locals.message = '<p class="msg success">' + msg + '</p>';
})
next();
});
// dummy database
// Generate a salt for the user to prevent rainbow table attacks
// for better security take a look at the bcrypt c++ addon:
// https://github.com/ncb000gt/node.bcrypt.js
var users = {
tj: {
name: 'tj'
, salt: 'randomly-generated-salt'
, pass: hash('foobar', 'randomly-generated-salt')
}
tj: { name: 'tj' }
};
// Used to generate a hash of the plain-text password + salt
function hash(msg, key) {
return crypto
.createHmac('sha256', key)
.update(msg)
.digest('hex');
}
// when you create a user, generate a salt
// and hash the password ('foobar' is the pass here)
hash('foobar', function(err, salt, hash){
if (err) throw err;
// store the salt & hash in the "db"
users.tj.salt = salt;
users.tj.hash = hash;
});
// Authenticate using our plain-object database of doom!
function authenticate(name, pass, fn) {
@@ -55,9 +58,11 @@ function authenticate(name, pass, fn) {
// apply the same algorithm to the POSTed password, applying
// the hash against the pass / salt, if there is a match we
// found the user
if (user.pass == hash(pass, user.salt)) return fn(null, user);
// Otherwise password is invalid
fn(new Error('invalid password'));
hash(pass, user.salt, function(err, hash){
if (err) return fn(err);
if (hash == user.hash) return fn(null, user);
fn(new Error('invalid password'));
})
}
function restrict(req, res, next) {

46
examples/auth/pass.js Normal file
View File

@@ -0,0 +1,46 @@
// check out https://github.com/visionmedia/node-pwd
/**
* Module dependencies.
*/
var crypto = require('crypto');
/**
* Bytesize.
*/
var len = 128;
/**
* Iterations. ~300ms
*/
var iterations = 12000;
/**
* Hashes a password with optional `salt`, otherwise
* generate a salt for `pass` and invoke `fn(err, salt, hash)`.
*
* @param {String} password to hash
* @param {String} optional salt
* @param {Function} callback
* @api public
*/
exports.hash = function (pwd, salt, fn) {
if (3 == arguments.length) {
crypto.pbkdf2(pwd, salt, iterations, len, fn);
} else {
fn = salt;
crypto.randomBytes(len, function(err, salt){
if (err) return fn(err);
salt = salt.toString('base64');
crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash){
if (err) return fn(err);
fn(null, salt, hash);
});
});
}
};

45
examples/cors/index.js Normal file
View File

@@ -0,0 +1,45 @@
/**
* Module dependencies.
*/
var express = require('../..')
, app = express()
, api = express();
// app middleware
app.use(express.static(__dirname + '/public'));
// api middleware
api.use(express.logger('dev'));
api.use(express.bodyParser());
/**
* CORS support.
*/
api.all('*', function(req, res, next){
// use "*" here to accept any origin
res.set('Access-Control-Allow-Origin', 'http://localhost:3000');
res.set('Access-Control-Allow-Methods', 'GET, POST');
res.set('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
// res.set('Access-Control-Allow-Max-Age', 3600);
next();
});
/**
* POST a user.
*/
api.post('/user', function(req, res){
console.log(req.body);
res.send(201);
});
app.listen(3000);
api.listen(3001);
console.log('app listening on 3000');
console.log('api listening on 3001');

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<script>
var req = new XMLHttpRequest;
req.open('POST', 'http://localhost:3001/user', false);
req.setRequestHeader('Content-Type', 'application/json');
req.send('{"name":"tobi","species":"ferret"}');
console.log(req.responseText);
</script>
</body>
</html>

View File

@@ -45,14 +45,6 @@ app.use(function(req, res, next){
next();
});
// if you wanted to _always_ expose
// the user you might do something like this:
/*
app.locals.use(function(req, res){
if (req.user) res.locals.expose.user = req.user;
})
*/
app.get('/', function(req, res){
res.redirect('/user');
});

View File

@@ -9,10 +9,9 @@ var express = require('../../lib/express');
var pub = __dirname + '/public';
// Auto-compile sass to css with "compiler"
// and then serve with connect's staticProvider
// setup middleware
var app = express.createServer();
var app = express();
app.use(app.router);
app.use(express.static(pub));
app.use(express.errorHandler());

View File

@@ -25,8 +25,24 @@ app.response.message = function(msg){
return this;
};
// log
if (!module.parent) app.use(express.logger('dev'));
// serve static files
app.use(express.static(__dirname + '/public'));
// session support
app.use(express.cookieParser('some secret here'));
app.use(express.session());
// parse request bodies (req.body)
app.use(express.bodyParser());
// support _method (PUT in forms etc)
app.use(express.methodOverride());
// expose the "messages" local variable when views are rendered
app.locals.use(function(req, res){
app.use(function(req, res, next){
var msgs = req.session.messages || [];
// expose "messages" local variable
@@ -45,24 +61,9 @@ app.locals.use(function(req, res){
// empty or "flush" the messages so they
// don't build up
req.session.messages = [];
next();
});
// log
if (!module.parent) app.use(express.logger('dev'));
// serve static files
app.use(express.static(__dirname + '/public'));
// session support
app.use(express.cookieParser('some secret here'));
app.use(express.session());
// parse request bodies (req.body)
app.use(express.bodyParser());
// support _method (PUT in forms etc)
app.use(express.methodOverride());
// load controllers
require('./lib/boot')(app, { verbose: !module.parent });

View File

@@ -37,7 +37,6 @@ module.exports = function(parent, options){
case 'show':
method = 'get';
path = '/' + name + '/:' + name + '_id';
app[method](path, obj[key]);
break;
case 'list':
method = 'get';

View File

@@ -1,32 +0,0 @@
/**
* Module dependencies.
*/
var express = require('../../lib/express');
var app = express.createServer();
// Optional since express defaults to CWD/views
app.set('views', __dirname + '/views');
// Set our default template engine to "jade"
// which prevents the need for extensions
// (although you can still mix and match)
app.set('view engine', 'jade');
// Dummy record
var ninja = {
name: 'leonardo',
summary: { email: 'hunter.loftis+github@gmail.com', master: 'splinter', description: 'peaceful leader' },
weapons: ['katana', 'fists', 'shell'],
victims: ['shredder', 'brain', 'beebop', 'rocksteady']
};
app.get('/', function(req, res){
res.render('ninja', { ninja: ninja });
});
app.listen(3000);
console.log('Express app started on port 3000');

View File

@@ -1,5 +0,0 @@
!!!
html
head
title Partials Example
body!= body

View File

@@ -1 +0,0 @@
li= value

View File

@@ -1 +0,0 @@
li.weapon= weapon

View File

@@ -1,22 +0,0 @@
h1= ninja.name
// file, partial name, and partial object all match ('summary')
// the partial filename prefix '_' is completely optional.
// In this case we need to specify ninja.summary as the object
// option, since it is a "plain" object Express cannot otherwise
// tell if it is intended to be locals, or THE summary object
#summary!= partial('summary', { object: ninja.summary })
// file, partial name = '_weapon', resolves to 'weapon' object within partial
#weapons
h2 Weapons
// the weapon partial is rendered once per item in
// the weapons array or "collection"
ul!= partial('weapon', ninja.weapons)
// partial name 'victim' resolves to 'victim.jade'
// or 'victim/index.jade', providing the "victim" local
#victims
h2 Victims
ul!= partial('victim', ninja.victims)

View File

@@ -1,4 +0,0 @@
h2 Summary
p= summary.email
p= summary.description
p taught by master #{summary.master}

View File

@@ -1,5 +0,0 @@
// this is insane overkill, I do not recommend
// doing tiny partials like this as it gets expensive
// with collections, however this illustrates the new
// partial lookup mechanism
!= partial('../../li', { object: victim, as: 'value' })

View File

@@ -0,0 +1,63 @@
var express = require('../../lib/express')
, verbose = process.env.NODE_ENV != 'test'
, app = module.exports = express();
app.map = function(a, route){
route = route || '';
for (var key in a) {
switch (typeof a[key]) {
// { '/path': { ... }}
case 'object':
app.map(a[key], route + key);
break;
// get: function(){ ... }
case 'function':
if (verbose) console.log('%s %s', key, route);
app[key](route, a[key]);
break;
}
}
};
var users = {
list: function(req, res){
res.send('user list');
},
get: function(req, res){
res.send('user ' + req.params.uid);
},
del: function(req, res){
res.send('delete users');
}
};
var pets = {
list: function(req, res){
res.send('user ' + req.params.uid + '\'s pets');
},
del: function(req, res){
res.send('delete ' + req.params.uid + '\'s pet ' + req.params.pid);
}
};
app.map({
'/users': {
get: users.list,
del: users.del,
'/:uid': {
get: users.get,
'/pets': {
get: pets.list,
'/:pid': {
del: pets.del
}
}
}
}
});
app.listen(3000);

View File

@@ -4,7 +4,7 @@
var express = require('../../lib/express');
var app = express.createServer();
var app = express();
// Example requests:
// curl http://localhost:3000/user/0

View File

@@ -3,8 +3,8 @@
* Module dependencies.
*/
var express = require('../../lib/express')
, app = express.createServer()
var express = require('../..')
, app = express()
, site = require('./site')
, post = require('./post')
, user = require('./user');

View File

@@ -1,44 +0,0 @@
/**
* Module dependencies.
*/
var express = require('../../')
, path = require('path')
, exec = require('child_process').exec
, fs = require('fs');
/**
* Error handler.
*/
function errorHandler(voice) {
return function(err, req, res, next) {
var parts = err.stack.split('\n')[1].split(/[()]/)[1].split(':')
, filename = parts.shift()
, basename = path.basename(filename)
, lineno = parts.shift()
, col = parts.shift()
, lines = fs.readFileSync(filename, 'utf8').split('\n')
, line = lines[lineno - 1].replace(/\./, ' ');
exec('say -v "' + voice + '" '
+ err.message
+ ' on line ' + lineno
+ ' of ' + basename + '.'
+ ' The contents of this line is '
+ ' "' + line + '".');
res.send(500);
}
}
var app = express.createServer();
app.get('/', function(request, response){
if (request.is(foo)) response.end('bar');
});
app.use(errorHandler('Vicki'));
app.listen(3000);

View File

@@ -1,51 +0,0 @@
/**
* Module dependencies.
*/
var express = require('../../lib/express')
, stylus = require('stylus');
var app = express.createServer();
// $ npm install stylus
// completely optional, however
// the compile function allows you to
// define additional functions exposed to Stylus,
// alter settings, etc
function compile(str, path) {
return stylus(str)
.set('filename', path)
.set('compress', true);
};
// add the stylus middleware, which re-compiles when
// a stylesheet has changed, compiling FROM src,
// TO dest. dest is optional, defaulting to src
app.use(stylus.middleware({
src: __dirname + '/views'
, dest: __dirname + '/public'
, compile: compile
}));
// minimal setup both reading and writting to ./public
// would look like:
// app.use(stylus.middleware({ src: __dirname + '/public' }));
// the middleware itself does not serve the static
// css files, so we need to expose them with staticProvider
app.use(express.static(__dirname + '/public'));
app.set('views', __dirname + '/views');
app.get('/', function(req, res){
res.render('index.jade');
});
app.listen(3000);
console.log('server listening on port 3000');

View File

@@ -1 +0,0 @@
*.css

View File

@@ -1,2 +0,0 @@
h1 Stylus
p Just an example of using Stylus with Express.

View File

@@ -1,6 +0,0 @@
html
head
title Stylus Example
link(rel='stylesheet', href='/reset.css')
link(rel='stylesheet', href='/main.css')
body!= body

View File

@@ -1,8 +0,0 @@
body
font 14px helvetica, arial, sans-serif
padding 50px
h1
font-size 50px
p
margin 15px 0

View File

@@ -3,13 +3,13 @@
* Module dependencies.
*/
var express = require('../../lib/express');
var express = require('../..');
// Edit /etc/vhosts
// First app
var one = express.createServer();
var one = express();
one.use(express.logger());
@@ -23,7 +23,7 @@ one.get('/:sub', function(req, res){
// App two
var two = express.createServer();
var two = express();
two.get('/', function(req, res){
res.send('Hello from app two!')
@@ -31,7 +31,7 @@ two.get('/', function(req, res){
// Redirect app
var redirect = express.createServer();
var redirect = express();
redirect.all('*', function(req, res){
console.log(req.subdomains);
@@ -40,7 +40,7 @@ redirect.all('*', function(req, res){
// Main app
var app = express.createServer();
var app = express();
app.use(express.vhost('*.localhost', redirect))
app.use(express.vhost('localhost', one));

View File

@@ -88,7 +88,6 @@ function count2(req, res, next) {
function users2(req, res, next) {
User.all(function(err, users){
if (err) return next(err);
// this would not be ideal for *this*
res.locals.users = users.filter(ferrets);
next();
})
@@ -103,31 +102,50 @@ app.get('/middleware-locals', count2, users2, function(req, res, next){
});
// let's assume we wanted to load the users
// and count for every res.render() call, we
// could use app.locals.use() for this. These
// are callbacks which run in parallel ONLY
// when res.render() is invoked. If no views
// are rendered, there is no overhead.
// This may be ideal if you want to load auxiliary
// user information, but only for templates. Note
// that (req, res) are available to you, so you may
// access req.session.user etc.
// Keep in mind these execute in *parallel*, so these
// callbacks should not depend on each other, this
// also makes them slightly more efficient than
// using middleware which execute sequentially
app.locals.use(count2);
app.locals.use(users2);
app.get('/locals', function(req, res){
res.render('user', { title: 'Users' });
});
// keep in mind that middleware may be placed anywhere
// and in various combinations, so if you have locals
// that you wish to make available to all subsequent
// middleware/routes you can do something like this:
/*
app.use(function(req, res, next){
res.locals.user = req.user;
res.locals.sess = req.session;
next();
});
*/
// or suppose you have some /admin
// "global" local variables:
/*
app.use('/api', function(req, res, next){
res.locals.user = req.user;
res.locals.sess = req.session;
next();
});
*/
// the following is effectively the same,
// but uses a route instead:
/*
app.all('/api/*', function(req, res, next){
res.locals.user = req.user;
res.locals.sess = req.session;
next();
});
*/
app.listen(3000);
console.log('Application listening on port 3000');

View File

@@ -1,11 +1,10 @@
/**
* Module dependencies.
*/
var connect = require('connect')
, Router = require('./router')
, methods = Router.methods.concat('del', 'all')
, methods = require('methods')
, middleware = require('./middleware')
, debug = require('debug')('express:application')
, locals = require('./utils').locals
@@ -59,12 +58,11 @@ app.defaultConfiguration = function(){
this.use(connect.query());
this.use(middleware.init(this));
// inherit view callbacks
// inherit protos
this.on('mount', function(parent){
this.request.__proto__ = parent.request;
this.response.__proto__ = parent.response;
this.engines.__proto__ = parent.engines;
this.viewCallbacks = parent.viewCallbacks.slice(0);
});
// router
@@ -85,6 +83,7 @@ app.defaultConfiguration = function(){
// default configuration
this.enable('jsonp callback');
this.set('jsonp callback name', 'callback');
this.configure('development', function(){
this.set('json spaces', 2);
@@ -427,7 +426,6 @@ methods.forEach(function(method){
app.all = function(path){
var args = arguments;
methods.forEach(function(method){
if ('all' == method || 'del' == method) return;
app[method].apply(this, args);
}, this);
return this;
@@ -492,7 +490,9 @@ app.render = function(name, options, fn){
});
if (!view.path) {
return fn(new Error('Failed to lookup view "' + name + '"'));
var err = new Error('Failed to lookup view "' + name + '"');
err.view = view;
return fn(err);
}
// prime the cache

View File

@@ -10,6 +10,7 @@ var http = require('http')
, Router = require('./router')
, req = require('./request')
, res = require('./response')
, send = require('send')
, utils = connect.utils;
/**
@@ -22,13 +23,13 @@ exports = module.exports = createApplication;
* Framework version.
*/
exports.version = '3.0.0beta3';
exports.version = '3.0.0rc2';
/**
* Expose mime.
*/
exports.mime = connect.mime;
exports.mime = send.mime;
/**
* Create an express application.
@@ -59,10 +60,19 @@ for (var key in connect.middleware) {
}
/**
* Backwards compat.
* Error on createServer().
*/
exports.createServer = createApplication;
exports.createServer = function(){
console.warn('Warning: express.createServer() is deprecated, express');
console.warn('applications no longer inherit from http.Server,');
console.warn('please use:');
console.warn('');
console.warn(' var express = require("express");');
console.warn(' var app = express();');
console.warn('');
return createApplication();
};
/**
* Expose the prototypes.
@@ -79,12 +89,6 @@ exports.response = res;
exports.Route = Route;
exports.Router = Router;
/**
* Expose HTTP methods.
*/
exports.methods = require('./router/methods');
// Error handler title
exports.errorHandler.title = 'Express';

View File

@@ -26,7 +26,7 @@ exports.init = function(app){
req.__proto__ = app.request;
res.__proto__ = app.response;
res.locals = utils.locals(res);
res.locals = res.locals || utils.locals(res);
next();
}

View File

@@ -7,6 +7,7 @@ var http = require('http')
, utils = require('./utils')
, connect = require('connect')
, fresh = require('fresh')
, parseRange = require('range-parser')
, parse = connect.utils.parseUrl
, mime = connect.mime;
@@ -132,6 +133,32 @@ req.acceptsLanguage = function(lang){
: true;
};
/**
* Parse Range header field,
* capping to the given `size`.
*
* Unspecified ranges such as "0-" require
* knowledge of your resource length. In
* the case of a byte range this is of course
* the total number of bytes. If the Range
* header field is not given `null` is returned,
* `-1` when unsatisfiable, `-2` when syntactically invalid.
*
* NOTE: remember that ranges are inclusive, so
* for example "Range: users=0-3" should respond
* with 4 users when available, not 3.
*
* @param {Number} size
* @return {Array}
* @api public
*/
req.range = function(size){
var range = this.get('Range');
if (!range) return;
return parseRange(size, range);
};
/**
* Return an array of Accepted media types
* ordered from highest quality to lowest.
@@ -341,6 +368,33 @@ req.__defineGetter__('ips', function(){
: [];
});
/**
* Return basic auth credentials.
*
* Examples:
*
* // http://tobi:hello@example.com
* req.auth
* // => { username: 'tobi', password: 'hello' }
*
* @return {Object}
* @api public
*/
req.__defineGetter__('auth', function(){
// missing
var auth = this.get('Authorization');
if (!auth) return {};
// malformed
auth = auth.split(' ')[1];
if (!auth) return {};
// credentials
auth = new Buffer(auth, 'base64').toString().split(':');
return { username: auth[0], password: auth[1] };
});
/**
* Return subdomains as an array.
*
@@ -390,7 +444,18 @@ req.__defineGetter__('host', function(){
*/
req.__defineGetter__('fresh', function(){
return fresh(this.headers, this.res._headers);
var method = this.method;
var s = this.res.statusCode;
// GET or HEAD for weak freshness validation only
if ('GET' != method && 'HEAD' != method) return false;
// 2xx or 304 as per rfc2616 14.26
if ((s >= 200 && s < 300) || 304 == s) {
return fresh(this.headers, this.res._headers);
}
return false;
});
/**

View File

@@ -1,4 +1,3 @@
/**
* Module dependencies.
*/
@@ -13,6 +12,7 @@ var fs = require('fs')
, statusCodes = http.STATUS_CODES
, send = connect.static.send
, cookie = require('cookie')
, send = require('send')
, crc = require('crc')
, mime = connect.mime
, basename = path.basename
@@ -40,6 +40,27 @@ res.status = function(code){
return this;
};
/**
* Set Link header field with the given `links`.
*
* Examples:
*
* res.links({
* next: 'http://api.example.com/users?page=2',
* last: 'http://api.example.com/users?page=5'
* });
*
* @param {Object} links
* @return {ServerResponse}
* @api public
*/
res.links = function(links){
return this.set('Link', Object.keys(links).map(function(rel){
return '<' + links[rel] + '>; rel="' + rel + '"';
}).join(', '));
};
/**
* Send a response.
*
@@ -64,10 +85,15 @@ res.send = function(body){
// allow status / body
if (2 == arguments.length) {
this.statusCode = body;
body = arguments[1];
// res.send(body, status) backwards compat
if ('number' != typeof body && 'number' == typeof arguments[1]) {
this.statusCode = arguments[1];
} else {
this.statusCode = body;
body = arguments[1];
}
}
// convert string objects to primitives
if (body instanceof String) body = body.toString();
@@ -146,20 +172,28 @@ res.send = function(body){
res.json = function(obj){
// allow status / body
if (2 == arguments.length) {
this.statusCode = obj;
obj = arguments[1];
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
this.statusCode = arguments[1];
} else {
this.statusCode = obj;
obj = arguments[1];
}
}
// settings
var app = this.app
, jsonp = app.get('jsonp callback')
, replacer = app.get('json replacer')
, spaces = app.get('json spaces')
, body = JSON.stringify(obj, replacer, spaces)
, callback = this.req.query.callback;
, callback = this.req.query[app.get('jsonp callback name')];
// content-type
this.charset = this.charset || 'utf-8';
this.set('Content-Type', 'application/json');
// jsonp
if (callback && jsonp) {
this.set('Content-Type', 'text/javascript');
body = callback.replace(/[^[]\w$.]/g, '') + '(' + body + ');';
@@ -212,7 +246,8 @@ res.sendfile = function(path, options, fn){
var self = this
, req = self.req
, next = this.req.next
, options = options || {};
, options = options || {}
, done;
// support function as second arg
if ('function' == typeof options) {
@@ -220,30 +255,49 @@ res.sendfile = function(path, options, fn){
options = {};
}
// callback
options.callback = function(err){
if (err) {
// cast ENOENT
if ('ENOENT' == err.code) err = utils.error(404);
// socket errors
req.socket.on('error', error);
// ditch content-disposition to prevent funky responses
if (!self.headerSent) self.removeHeader('Content-Disposition');
// errors
function error(err) {
if (done) return;
done = true;
// woot! callback available
if (fn) return fn(err);
// clean up
cleanup();
if (!self.headerSent) self.removeHeader('Content-Disposition');
// lost in limbo if there's no callback
if (self.headerSent) return;
// callback available
if (fn) return fn(err);
return req.next(err);
}
// list in limbo if there's no callback
if (self.headerSent) return;
fn && fn();
};
// delegate
next(err);
}
// streaming
function stream() {
if (done) return;
cleanup();
if (fn) self.on('finish', fn);
}
// cleanup
function cleanup() {
req.socket.removeListener('error', error);
}
// transfer
options.path = encodeURIComponent(path);
send(this.req, this, next, options);
var file = send(req, path);
if (options.root) file.root(options.root);
file.maxage(options.maxAge || 0);
file.on('error', error);
file.on('directory', next);
file.on('stream', stream);
file.pipe(this);
this.on('finish', cleanup);
};
/**
@@ -346,21 +400,24 @@ res.type = function(type){
*
* By default Express passes an `Error`
* with a `.status` of 406 to `next(err)`
* if a match is not made, however you may
* provide an optional callback `fn` to
* be invoked instead.
* if a match is not made. If you provide
* a `.default` callback it will be invoked
* instead.
*
* @param {Object} obj
* @param {Function} fn
* @return {ServerResponse} for chaining
* @api public
*/
res.format = function(obj, fn){
res.format = function(obj){
var keys = Object.keys(obj)
, req = this.req
, next = req.next
, key = req.accepts(keys);
, next = req.next;
var fn = obj.default;
if (fn) delete obj.default;
var key = req.accepts(keys);
this.set('Vary', 'Accept');
@@ -541,11 +598,11 @@ res.redirect = function(url){
url = map[url] || url;
// relative
if (!~url.indexOf('://')) {
if (!~url.indexOf('://') && 0 != url.indexOf('//')) {
var path = app.path();
// relative to path
if (0 == url.indexOf('./') || 0 == url.indexOf('..')) {
if ('.' == url[0]) {
url = req.path + '/' + url;
// relative to mount-point
} else if ('/' != url[0]) {
@@ -554,7 +611,7 @@ res.redirect = function(url){
// Absolute
var host = req.get('Host');
url = req.protocol + '://' + host + url;
url = '//' + host + url;
}
// Support text/{plain,html} by default
@@ -564,11 +621,14 @@ res.redirect = function(url){
},
html: function(){
body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';
var u = utils.escape(url);
body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
},
default: function(){
body = '';
}
}, function(){
body = '';
})
});
// Respond
this.statusCode = status;
@@ -583,11 +643,6 @@ res.redirect = function(url){
* automatically, otherwise a response of _200_ and _text/html_ is given.
*
* Options:
*
* - `status` Response status code (`res.statusCode`)
* - `charset` Set the charset (`res.charset`)
*
* Reserved locals:
*
* - `cache` boolean hinting to the engine it should cache
* - `filename` filename of the view being rendered
@@ -609,41 +664,15 @@ res.render = function(view, options, fn){
fn = options, options = {};
}
function render() {
// merge res.locals
options.locals = self.locals;
// merge res.locals
options.locals = self.locals;
// default callback to respond
fn = fn || function(err, str){
if (err) return req.next(err);
self.send(str);
};
// default callback to respond
fn = fn || function(err, str){
if (err) return req.next(err);
self.send(str);
};
// render
app.render(view, options, fn);
}
// invoke view callbacks
var callbacks = app.viewCallbacks.concat(self.viewCallbacks)
, pending = callbacks.length
, len = pending
, done;
if (len) {
for (var i = 0; i < len; ++i) {
callbacks[i](req, self, function(err){
if (done) return;
if (err) {
req.next(err);
done = true;
return;
}
--pending || render();
});
}
} else {
render();
}
// render
app.render(view, options, fn);
};

View File

@@ -1,4 +1,3 @@
/**
* Module dependencies.
*/
@@ -6,7 +5,8 @@
var Route = require('./route')
, utils = require('../utils')
, debug = require('debug')('express:router')
, parse = require('connect').utils.parseUrl;
, parse = require('connect').utils.parseUrl
, methods = require('methods');
/**
* Expose `Router` constructor.
@@ -14,12 +14,6 @@ var Route = require('./route')
exports = module.exports = Router;
/**
* Expose HTTP methods.
*/
var methods = exports.methods = require('./methods');
/**
* Initialize a new `Router` with the given `options`.
*
@@ -108,7 +102,7 @@ Router.prototype._dispatch = function(req, res, next){
}
// match route
req.route = route = self.match(req, i);
req.route = route = self.matchRequest(req, i);
// implied OPTIONS
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
@@ -225,7 +219,7 @@ Router.prototype._optionsFor = function(path){
* @api private
*/
Router.prototype.match = function(req, i, head){
Router.prototype.matchRequest = function(req, i, head){
var method = req.method.toLowerCase()
, url = parse(req)
, path = url.pathname
@@ -235,7 +229,7 @@ Router.prototype.match = function(req, i, head){
// HEAD support
if (!head && 'head' == method) {
route = this.match(req, i, true);
route = this.matchRequest(req, i, true);
if (route) return route;
method = 'get';
}
@@ -254,6 +248,23 @@ Router.prototype.match = function(req, i, head){
}
};
/**
* Attempt to match a route for `method`
* and `url` with optional starting
* index of `i` defaulting to 0.
*
* @param {String} method
* @param {String} url
* @param {Number} i
* @return {Route}
* @api private
*/
Router.prototype.match = function(method, url, i, head){
var req = { method: method, url: url };
return this.matchRequest(req, i, head);
};
/**
* Route `method`, `path`, and one or more callbacks.
*

View File

@@ -1,30 +0,0 @@
/**
* HTTP methods supported by node.
*/
module.exports = [
'get'
, 'post'
, 'put'
, 'head'
, 'delete'
, 'options'
, 'trace'
, 'copy'
, 'lock'
, 'mkcol'
, 'move'
, 'propfind'
, 'proppatch'
, 'unlock'
, 'report'
, 'mkactivity'
, 'checkout'
, 'merge'
, 'm-search'
, 'notify'
, 'subscribe'
, 'unsubscribe'
, 'patch'
];

View File

@@ -23,18 +23,6 @@ exports.locals = function(obj){
return obj;
};
locals.use = function(fn){
if (3 == fn.length) {
obj.viewCallbacks.push(fn);
} else {
obj.viewCallbacks.push(function(req, res, done){
fn(req, res);
done();
});
}
return obj;
};
return locals;
};
@@ -264,8 +252,7 @@ exports.pathRegexp = function(path, keys, sensitive, strict) {
path = path
.concat(strict ? '' : '/?')
.replace(/\/\(/g, '(?:/')
.replace(/\+/g, '__plus__')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){
keys.push({ name: key, optional: !! optional });
slash = slash || '';
return ''
@@ -273,10 +260,10 @@ exports.pathRegexp = function(path, keys, sensitive, strict) {
+ '(?:'
+ (optional ? slash : '')
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
+ (optional || '');
+ (optional || '')
+ (star ? '(/*)?' : '');
})
.replace(/([\/.])/g, '\\$1')
.replace(/__plus__/g, '(.+)')
.replace(/\*/g, '(.*)');
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "3.0.0beta3",
"version": "3.0.0rc2",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
@@ -10,12 +10,15 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
],
"dependencies": {
"connect": "2.3.3",
"connect": "2.4.2",
"commander": "0.6.1",
"mkdirp": "0.3.2",
"cookie": "0.0.3",
"range-parser": "0.0.4",
"mkdirp": "0.3.3",
"cookie": "0.0.4",
"crc": "0.2.0",
"fresh": "0.0.1",
"fresh": "0.1.0",
"methods": "0.0.1",
"send": "0.0.3",
"debug": "*"
},
"devDependencies": {
@@ -26,10 +29,21 @@
"stylus": "*",
"should": "*",
"connect-redis": "*",
"github-flavored-markdown": "*"
"github-flavored-markdown": "*",
"supertest": "0.0.1"
},
"keywords": [
"express",
"framework",
"sinatra",
"web",
"rest",
"restful",
"router",
"app",
"api"
],
"publishConfig": { "tag": "3.0" },
"keywords": ["express", "framework", "sinatra", "web", "rest", "restful", "router"],
"repository": "git://github.com/visionmedia/express",
"main": "index",
"bin": { "express": "./bin/express" },

View File

@@ -12,27 +12,53 @@ describe('Router', function(){
app = express();
})
describe('.match(req, i)', function(){
describe('.match(method, url, i)', function(){
it('should match based on index', function(){
router.route('get', '/foo', function(){});
router.route('get', '/foob?', function(){});
router.route('get', '/bar', function(){});
var method = 'GET';
var url = '/foo?bar=baz';
var route = router.match(method, url, 0);
route.constructor.name.should.equal('Route');
route.method.should.equal('get');
route.path.should.equal('/foo');
var route = router.match(method, url, 1);
route.path.should.equal('/foob?');
var route = router.match(method, url, 2);
assert(!route);
url = '/bar';
var route = router.match(method, url);
route.path.should.equal('/bar');
})
})
describe('.matchRequest(req, i)', function(){
it('should match based on index', function(){
router.route('get', '/foo', function(){});
router.route('get', '/foob?', function(){});
router.route('get', '/bar', function(){});
var req = { method: 'GET', url: '/foo?bar=baz' };
var route = router.match(req, 0);
var route = router.matchRequest(req, 0);
route.constructor.name.should.equal('Route');
route.method.should.equal('get');
route.path.should.equal('/foo');
var route = router.match(req, 1);
var route = router.matchRequest(req, 1);
req._route_index.should.equal(1);
route.path.should.equal('/foob?');
var route = router.match(req, 2);
var route = router.matchRequest(req, 2);
assert(!route);
req.url = '/bar';
var route = router.match(req);
var route = router.matchRequest(req);
route.path.should.equal('/bar');
})
})

View File

@@ -1,8 +1,8 @@
var app = require('../../examples/auth/app')
, request = require('../support/http');
function redirects(to,fn){
return function(res){
function redirects(to, fn){
return function(err, res){
res.statusCode.should.equal(302)
res.headers.should.have.property('location').match(to);
fn()
@@ -14,80 +14,29 @@ function getCookie(res) {
}
describe('auth', function(){
var cookie;
describe('GET /',function(){
it('should redirect to /login', function(done){
request(app)
.get('/')
.end(redirects(/\/login$/,done))
.get('/')
.end(redirects(/\/login$/, done))
})
})
describe('GET /restricted (w/o cookie)',function(){
it('should redirect to /login', function(done){
request(app)
.get('/restricted')
.end(redirects(/\/login$/,done))
.get('/restricted')
.end(redirects(/\/login$/,done))
})
})
describe('POST /login', function(){
it('should fail without proper credentials', function(done){
request(app)
.post('/login')
.set('content-type','application/x-www-form-urlencoded')
.write('&username=not-tj&password=foobar')
.end(redirects(/\/login$/,done))
})
it('should authenticate', function(done){
request(app)
.post('/login')
.set('content-type', 'application/x-www-form-urlencoded')
.write('username=tj&password=foobar')
.end(function(res){
res.statusCode.should.equal(302);
cookie = getCookie(res);
request(app)
.get('/login')
.set('Cookie', cookie)
.end(function(res){
res.body.should.include('Authenticated as tj');
done();
})
})
})
})
describe('GET /restricted (w. cookie)',function(){
it('should respond with 200', function(done){
request(app)
.get('/restricted')
.set('Cookie', cookie)
.expect(200, done);
})
})
describe('GET /logout',function(){
it('should respond with 302 and clear cookie',function(done){
request(app)
.get('/logout')
.set('Cookie', cookie)
.end(function(res){
res.statusCode.should.equal(302);
res.headers.should.not.have.property('set-cookie')
done();
})
})
})
describe('GET /restricted (w. expired cookie)',function(){
it('should respond with 302',function(done){
request(app)
.get('/restricted')
.set('Cookie', cookie)
.expect(302, done)
.post('/login')
.type('urlencoded')
.send('username=not-tj&password=foobar')
.end(redirects(/\/login$/, done))
})
})
})

View File

@@ -7,20 +7,16 @@ describe('content-negotiation', function(){
it('should default to text/html', function(done){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>');
done();
})
.expect('<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>')
.end(done);
})
it('should accept to text/plain', function(done){
request(app)
.get('/')
.set('Accept', 'text/plain')
.end(function(res){
res.body.should.equal(' - Tobi\n - Loki\n - Jane\n');
done();
})
.expect(' - Tobi\n - Loki\n - Jane\n')
.end(done);
})
})
})

View File

@@ -1,3 +1,4 @@
var app = require('../../examples/cookies/app')
, request = require('../support/http');
@@ -5,29 +6,29 @@ describe('cookies', function(){
describe('GET /', function(){
it('should have a form', function(done){
request(app)
.get('/')
.expect(/<form/,done)
.get('/')
.expect(/<form/, done);
})
it('should respond with no cookies', function(done){
request(app)
.get('/')
.end(function(res){
res.headers.should.not.have.property('set-cookie')
done()
})
.get('/')
.end(function(err, res){
res.headers.should.not.have.property('set-cookie')
done()
})
})
})
describe('POST /', function(){
it('should set a cookie', function(done){
request(app)
.post('/')
.set('Content-Type','application/x-www-form-urlencoded')
.write('remember=1')
.end(function(res){
res.headers.should.have.property('set-cookie')
done()
})
.post('/')
.send({ remember: 1 })
.end(function(err, res){
res.headers.should.have.property('set-cookie')
done()
})
})
})
})

View File

@@ -1,3 +1,4 @@
var app = require('../../examples/downloads/app')
, request = require('../support/http');
@@ -5,28 +6,28 @@ describe('downloads', function(){
describe('GET /', function(){
it('should have a link to amazing.txt', function(done){
request(app)
.get('/')
.expect(/href="\/files\/amazing.txt"/,done)
.get('/')
.expect(/href="\/files\/amazing.txt"/, done)
})
})
describe('GET /files/amazing.txt', function(){
it('should have a download header', function(done){
request(app)
.get('/files/amazing.txt')
.end(function(res){
res.should.have.property('statusCode',200)
res.headers.should.have.property('content-disposition', 'attachment; filename="amazing.txt"')
done()
})
.get('/files/amazing.txt')
.end(function(err, res){
res.status.should.equal(200);
res.headers.should.have.property('content-disposition', 'attachment; filename="amazing.txt"')
done()
})
})
})
describe('GET /files/missing.txt', function(){
it('should respond with 404', function(done){
request(app)
.get('/files/missing.txt')
.expect(404,done)
.get('/files/missing.txt')
.expect(404, done)
})
})
})

View File

@@ -7,12 +7,12 @@ describe('ejs', function(){
it('should respond with html', function(done){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.status(200);
res.should.have.header('Content-Type', 'text/html; charset=utf-8');
res.body.should.include('<li>tobi tobi@learnboost.com</li>');
res.body.should.include('<li>loki loki@learnboost.com</li>');
res.body.should.include('<li>jane jane@learnboost.com</li>');
res.text.should.include('<li>tobi tobi@learnboost.com</li>');
res.text.should.include('<li>loki loki@learnboost.com</li>');
res.text.should.include('<li>jane jane@learnboost.com</li>');
done();
});
})

View File

@@ -37,7 +37,6 @@ describe('error-pages', function(){
})
})
describe('Accept: application/json',function(){
describe('GET /403', function(){
it('should respond with 403', function(done){
@@ -53,9 +52,8 @@ describe('error-pages', function(){
request(app)
.get('/404')
.set('Accept','application/json')
.end(function(res){
res.should.have.property('statusCode',200)
res.should.have.property('body',JSON.stringify({error:'Not found'}))
.end(function(err, res){
res.body.should.eql({ error: 'Not found' });
done()
})
})
@@ -65,7 +63,7 @@ describe('error-pages', function(){
it('should respond with 500', function(done){
request(app)
.get('/500')
.set('Accept','application/json')
.set('Accept', 'application/json')
.expect(500, done)
})
})
@@ -76,22 +74,19 @@ describe('error-pages', function(){
describe('GET /403', function(){
it('should respond with 403', function(done){
request(app)
.get('/403')
.set('Accept','text/plain')
.expect(403, done)
.get('/403')
.set('Accept','text/plain')
.expect(403, done)
})
})
describe('GET /404', function(){
it('should respond with 404', function(done){
request(app)
.get('/404')
.set('Accept','text/plain')
.end(function(res){
res.should.have.property('statusCode',200)
res.should.have.property('body','Not found')
done()
})
.get('/404')
.set('Accept', 'text/plain')
.expect(200)
.expect('Not found', done);
})
})

View File

@@ -1,42 +0,0 @@
var app = require('../../examples/multipart/app')
, request = require('../support/http')
, path = 'test/acceptance/fixtures/grey.png'
, fs = require('fs')
var logo = fs.readFileSync(path)
, boundary = '------expressmultipart';
describe('multipart', function(){
describe('GET /', function(){
it('should respond with a form', function(done){
request(app)
.get('/')
.expect(/<form/, done)
})
})
describe('POST /', function(){
it('should upload logo as multipart', function(done){
request(app)
.post('/')
.set('content-type','multipart/form-data; boundary='+boundary.slice(2))
.write(boundary + '\r\n')
.write('Content-Disposition: form-data; name="title"\r\n')
.write('\r\n')
.write('grey\r\n')
.write(boundary + '\r\n')
.write('Content-Disposition: form-data; name="image"; filename="grey.png"\r\n')
.write('Content-Type: image/png\r\n')
.write('\r\n')
.write(logo+'\r\n')
.write(boundary+'--\r\n')
.end(function(res){
res.body.should.match(/uploaded grey.png/)
res.body.should.match(/\(224 Kb\)/)
res.body.should.match(/as grey/)
done()
})
})
})
})

View File

@@ -7,7 +7,7 @@ describe('mvc', function(){
it('should redirect to /users', function(done){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.status(302);
res.headers.location.should.include('/users');
done();
@@ -19,11 +19,11 @@ describe('mvc', function(){
it('should display a list of users', function(done){
request(app)
.get('/users')
.end(function(res){
res.body.should.include('<h1>Users</h1>');
res.body.should.include('>TJ<');
res.body.should.include('>Guillermo<');
res.body.should.include('>Nathan<');
.end(function(err, res){
res.text.should.include('<h1>Users</h1>');
res.text.should.include('>TJ<');
res.text.should.include('>Guillermo<');
res.text.should.include('>Nathan<');
done();
})
})
@@ -34,8 +34,8 @@ describe('mvc', function(){
it('should display the user', function(done){
request(app)
.get('/user/0')
.end(function(res){
res.body.should.include('<h1>TJ <a href="/user/0/edit">edit');
.end(function(err, res){
res.text.should.include('<h1>TJ <a href="/user/0/edit">edit');
done();
})
})
@@ -43,10 +43,10 @@ describe('mvc', function(){
it('should display the users pets', function(done){
request(app)
.get('/user/0')
.end(function(res){
res.body.should.include('/pet/0">Tobi');
res.body.should.include('/pet/1">Loki');
res.body.should.include('/pet/2">Jane');
.end(function(err, res){
res.text.should.include('/pet/0">Tobi');
res.text.should.include('/pet/1">Loki');
res.text.should.include('/pet/2">Jane');
done();
})
})
@@ -65,9 +65,9 @@ describe('mvc', function(){
it('should display the edit form', function(done){
request(app)
.get('/user/1/edit')
.end(function(res){
res.body.should.include('<h1>Guillermo</h1>');
res.body.should.include('value="put"');
.end(function(err, res){
res.text.should.include('<h1>Guillermo</h1>');
res.text.should.include('value="put"');
done();
})
})
@@ -77,13 +77,12 @@ describe('mvc', function(){
it('should update the user', function(done){
request(app)
.put('/user/1')
.set('Content-Type', 'application/json')
.write('{"user":{"name":"Tobo"}}')
.end(function(res){
.send({ user: { name: 'Tobo' }})
.end(function(err, res){
request(app)
.get('/user/1/edit')
.end(function(res){
res.body.should.include('<h1>Tobo</h1>');
.end(function(err, res){
res.text.should.include('<h1>Tobo</h1>');
done();
})
})

View File

@@ -37,7 +37,7 @@ describe('resource', function(){
describe('DELETE /users/1', function(){
it('should respond with users 1 through 3', function(done){
request(app)
.delete('/users/1')
.del('/users/1')
.expect(/^destroyed/,done)
})
})

View File

@@ -0,0 +1,45 @@
var request = require('supertest')
, app = require('../../examples/route-map');
describe('route-map', function(){
describe('GET /users', function(){
it('should respond with users', function(done){
request(app)
.get('/users')
.expect('user list', done);
})
})
describe('DELETE /users', function(){
it('should delete users', function(done){
request(app)
.del('/users')
.expect('delete users', done);
})
})
describe('GET /users/:id', function(){
it('should get a user', function(done){
request(app)
.get('/users/12')
.expect('user 12', done);
})
})
describe('GET /users/:id/pets', function(){
it('should get a users pets', function(done){
request(app)
.get('/users/12/pets')
.expect('user 12\'s pets', done);
})
})
describe('GET /users/:id/pets/:pid', function(){
it('should get a users pet', function(done){
request(app)
.del('/users/12/pets/2')
.expect('delete 12\'s pet 2', done);
})
})
})

View File

@@ -24,9 +24,9 @@ describe('web-service', function(){
it('should respond users json', function(done){
request(app)
.get('/api/users?api-key=foo')
.end(function(res){
.end(function(err, res){
res.should.be.json;
res.body.should.equal('[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]');
res.text.should.equal('[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]');
done();
});
})
@@ -37,10 +37,10 @@ describe('web-service', function(){
it('should respond with 404 json', function(done){
request(app)
.get('/api/something?api-key=bar')
.end(function(res){
.end(function(err, res){
res.should.have.status(404);
res.should.be.json;
res.body.should.equal('{"error":"Lame, can\'t find that"}');
res.text.should.equal('{"error":"Lame, can\'t find that"}');
done();
});
})

View File

@@ -29,7 +29,7 @@ describe('app.all()', function(){
});
request(app)
.delete('/tobi')
.del('/tobi')
.expect(404, done);
})
})

View File

@@ -11,7 +11,7 @@ describe('app.del()', function(){
});
request(app)
.delete('/tobi')
.del('/tobi')
.expect('deleted tobi!', done);
})
})

View File

@@ -6,10 +6,10 @@ describe('app', function(){
describe('.locals(obj)', function(){
it('should merge locals', function(){
var app = express();
Object.keys(app.locals).should.eql(['use', 'settings']);
Object.keys(app.locals).should.eql(['settings']);
app.locals({ user: 'tobi', age: 1 });
app.locals({ age: 2 });
Object.keys(app.locals).should.eql(['use', 'settings', 'user', 'age']);
Object.keys(app.locals).should.eql(['settings', 'user', 'age']);
app.locals.user.should.equal('tobi');
app.locals.age.should.equal(2);
})

View File

@@ -1,92 +0,0 @@
var express = require('../')
, request = require('./support/http');
describe('app', function(){
describe('.locals.use(fn)', function(){
it('should run in parallel on res.render()', function(done){
var app = express();
var calls = [];
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.locals.use(function(req, res, done){
process.nextTick(function(){
calls.push('one');
res.locals.last = 'holowaychuk';
done();
});
});
app.locals.use(function(req, res, done){
process.nextTick(function(){
calls.push('two');
res.locals.species = 'ferret';
done();
});
});
app.use(function(req, res){
calls.push('use');
res.render('pet.jade');
});
request(app)
.get('/')
.end(function(res){
calls.should.eql(['use', 'one', 'two']);
res.body.should.equal('<p>tobi holowaychuk is a ferret</p>');
done();
})
})
describe('with arity < 3', function(){
it('should done() for you', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.locals.use(function(req, res){
res.locals.last = 'holowaychuk';
res.locals.species = 'ferret';
});
app.use(function(req, res){
res.render('pet.jade');
});
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi holowaychuk is a ferret</p>');
done();
})
})
})
it('should not override res.render() locals', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.locals.use(function(req, res){
res.locals.last = 'holowaychuk';
res.locals.species = 'ferret';
});
app.use(function(req, res){
res.render('pet.jade', { last: 'ibot' });
});
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi ibot is a ferret</p>');
done();
})
})
})
})

View File

@@ -12,12 +12,8 @@ describe('OPTIONS', function(){
request(app)
.options('/users')
.end(function(res){
res.body.should.equal('GET,PUT');
res.headers.should.have.property('content-type');
res.headers.should.have.property('allow', 'GET,PUT');
done();
});
.expect('GET,PUT')
.expect('Allow', 'GET,PUT', done);
})
})
@@ -35,10 +31,7 @@ describe('app.options()', function(){
request(app)
.options('/users')
.end(function(res){
res.body.should.equal('GET');
res.headers.should.have.property('allow', 'GET');
done();
});
.expect('GET')
.expect('Allow', 'GET', done);
})
})

View File

@@ -29,14 +29,11 @@ describe('app', function(){
request(app)
.get('/user/tj')
.end(function(res){
res.body.should.equal('tj');
.end(function(err, res){
res.text.should.equal('tj');
request(app)
.get('/user/123')
.end(function(res){
res.should.have.status(404);
done();
});
.expect(404, done);
});
})
@@ -67,15 +64,12 @@ describe('app', function(){
request(app)
.get('/user/123')
.end(function(res){
res.body.should.equal('123');
.end(function(err, res){
res.text.should.equal('123');
request(app)
.get('/post/123')
.end(function(res){
res.body.should.equal('123');
done();
})
.expect('123', done);
})
})
})
@@ -99,10 +93,7 @@ describe('app', function(){
request(app)
.get('/user/123')
.end(function(res){
res.body.should.equal('123');
done();
})
.expect('123', done);
})
})
})

View File

@@ -17,10 +17,7 @@ describe('app', function(){
request(app)
.get('/foo?name=tobi')
.end(function(res){
res.body.should.equal('name=tobi');
done();
});
.expect('name=tobi', done);
})
})
})

View File

@@ -17,10 +17,7 @@ describe('app', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('HEY');
done();
});
.expect('HEY', done);
})
it('should not be influenced by other app protos', function(done){
@@ -41,10 +38,7 @@ describe('app', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('HEY');
done();
});
.expect('HEY', done);
})
})
})

View File

@@ -1,11 +1,14 @@
var express = require('../')
, request = require('./support/http')
, assert = require('assert');
, assert = require('assert')
, methods = require('methods');
describe('app.router', function(){
describe('methods supported', function(){
express.methods.forEach(function(method){
methods.forEach(function(method){
it('should include ' + method.toUpperCase(), function(done){
if (method == 'delete') method = 'del';
var app = express();
var calls = [];
@@ -256,6 +259,23 @@ describe('app.router', function(){
})
})
it('should allow escaped regexp', function(done){
var app = express();
app.get('/user/\\d+', function(req, res){
res.end('woot');
});
request(app)
.get('/user/10')
.end(function(err, res){
res.statusCode.should.equal(200);
request(app)
.get('/user/tj')
.expect(404, done);
});
})
it('should allow literal "."', function(done){
var app = express();
@@ -271,30 +291,8 @@ describe('app.router', function(){
.expect('users from 1 to 50', done);
})
describe('+', function(){
it('should denote a greedy capture group', function(done){
var app = express();
app.get('/blog+', function(req, res){
res.end(req.params[0] || 'nothing');
});
request(app)
.get('/blog')
.expect(404, function(){
request(app)
.get('/blog/post')
.expect(200, function(){
request(app)
.get('/blog-admin')
.expect(200, done)
})
});
})
})
describe('*', function(){
it('should denote an optional greedy capture group', function(done){
it('should denote a greedy capture group', function(done){
var app = express();
app.get('/user/*.json', function(req, res){
@@ -349,6 +347,30 @@ describe('app.router', function(){
.expect('users/0.json', done);
})
it('should not be greedy immediately after param', function(done){
var app = express();
app.get('/user/:user*', function(req, res){
res.end(req.params.user);
});
request(app)
.get('/user/122')
.expect('122', done);
})
it('should eat everything after /', function(done){
var app = express();
app.get('/user/:user*', function(req, res){
res.end(req.params.user);
});
request(app)
.get('/user/122/aaa')
.expect('122', done);
})
it('should span multiple segments', function(done){
var app = express();

View File

@@ -17,13 +17,6 @@ describe('exports', function(){
express.mime.should.equal(require('connect').mime);
})
it('should expose HTTP methods', function(){
express.methods.should.be.an.instanceof(Array);
express.methods.should.include('get');
express.methods.should.include('put');
express.methods.should.include('post');
})
it('should expose Router', function(){
express.Router.should.be.a('function');
})

View File

@@ -13,10 +13,7 @@ describe('throw after .end()', function(){
request(app)
.get('/')
.end(function(res){
res.should.have.status(200);
res.body.should.equal('yay');
done();
});
.expect('yay')
.expect(200, done);
})
})

View File

@@ -29,10 +29,7 @@ describe('req', function(){
request(app)
.get('/')
.set('If-None-Match', '12345')
.end(function(res){
res.body.should.equal('false');
done();
});
.expect('false', done);
})
})
})

View File

@@ -16,10 +16,7 @@ describe('req', function(){
request(app)
.post('/')
.set('Content-Type', 'application/json')
.end(function(res){
res.body.should.equal('application/json');
done();
});
.expect('application/json', done);
})
it('should special-case Referer', function(done){
@@ -32,10 +29,7 @@ describe('req', function(){
request(app)
.post('/')
.set('Referrer', 'http://foobar.com')
.end(function(res){
res.body.should.equal('http://foobar.com');
done();
});
.expect('http://foobar.com', done);
})
})
})

View File

@@ -13,10 +13,7 @@ describe('req', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('tj');
done();
})
.expect('tj', done);
})
})
@@ -30,10 +27,7 @@ describe('req', function(){
request(app)
.get('/?name=tj')
.end(function(res){
res.body.should.equal('tj');
done();
})
.expect('tj', done);
})
it('should check req.body', function(done){
@@ -47,12 +41,8 @@ describe('req', function(){
request(app)
.post('/')
.set('Content-Type', 'application/json')
.write('{"name":"tj"}')
.end(function(res){
res.body.should.equal('tj');
done();
})
.send({ name: 'tj' })
.expect('tj', done);
})
it('should check req.params', function(done){
@@ -64,10 +54,7 @@ describe('req', function(){
request(app)
.get('/user/tj')
.end(function(res){
res.body.should.equal('undefinedtj');
done();
})
.expect('undefinedtj', done);
})
})
})

View File

@@ -13,10 +13,7 @@ describe('req', function(){
request(app)
.get('/login?redirect=/post/1/comments')
.end(function(res){
res.body.should.equal('/login');
done();
})
.expect('/login', done);
})
})
})

View File

@@ -13,10 +13,7 @@ describe('req', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('http');
done();
})
.expect('http', done);
})
describe('when "trust proxy" is enabled', function(){
@@ -32,10 +29,7 @@ describe('req', function(){
request(app)
.get('/')
.set('X-Forwarded-Proto', 'https')
.end(function(res){
res.body.should.equal('https');
done();
})
.expect('https', done);
})
it('should default to http', function(done){
@@ -49,10 +43,7 @@ describe('req', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('http');
done();
})
.expect('http', done);
})
})
@@ -67,10 +58,7 @@ describe('req', function(){
request(app)
.get('/')
.set('X-Forwarded-Proto', 'https')
.end(function(res){
res.body.should.equal('http');
done();
})
.expect('http', done);
})
})
})

31
test/req.range.js Normal file
View File

@@ -0,0 +1,31 @@
var express = require('../');
function req(ret) {
return {
get: function(){ return ret }
, __proto__: express.request
};
}
describe('req', function(){
describe('.range(size)', function(){
it('should return parsed ranges', function(){
var ret = [{ start: 0, end: 50 }, { start: 60, end: 100 }];
ret.type = 'bytes';
req('bytes=0-50,60-100').range(120).should.eql(ret);
})
it('should cap to the given size', function(){
var ret = [{ start: 0, end: 74 }];
ret.type = 'bytes';
req('bytes=0-100').range(75).should.eql(ret);
})
it('should have a .type', function(){
var ret = [{ start: 0, end: Infinity }];
ret.type = 'users';
req('users=0-').range(Infinity).should.eql(ret);
})
})
})

View File

@@ -29,10 +29,7 @@ describe('req', function(){
request(app)
.get('/')
.set('If-None-Match', '12345')
.end(function(res){
res.body.should.equal('true');
done();
});
.expect('true', done);
})
})
})

View File

@@ -15,10 +15,7 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'tobi.ferrets.example.com')
.end(function(res){
res.body.should.equal('["ferrets","tobi"]');
done();
})
.expect('["ferrets","tobi"]', done);
})
})
@@ -33,10 +30,7 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'example.com')
.end(function(res){
res.body.should.equal('[]');
done();
})
.expect('[]', done);
})
})
})

View File

@@ -13,10 +13,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-disposition', 'attachment');
done();
})
.expect('Content-Disposition', 'attachment', done);
})
})
@@ -31,10 +28,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-disposition', 'attachment; filename="image.png"');
done();
})
.expect('Content-Disposition', 'attachment; filename="image.png"', done);
})
it('should set the Content-Type', function(done){
@@ -47,10 +41,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'image/png');
done();
})
.expect('Content-Type', 'image/png', done);
})
})
})

View File

@@ -15,10 +15,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('text/x-foo; charset=utf-8');
done();
})
.expect("text/x-foo; charset=utf-8", done);
})
it('should take precedence over res.send() defaults', function(done){
@@ -31,10 +28,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'text/html; charset=whoop');
done();
})
.expect('Content-Type', 'text/html; charset=whoop', done);
})
})
})

View File

@@ -13,9 +13,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = 'sid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
res.headers['set-cookie'].should.eql([val]);
res.header['set-cookie'].should.eql([val]);
done();
})
})
@@ -31,9 +31,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = 'sid=; Path=/admin; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
res.headers['set-cookie'].should.eql([val]);
res.header['set-cookie'].should.eql([val]);
done();
})
})

View File

@@ -14,8 +14,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
var val = ['user=j:{%22name%22:%22tobi%22}; Path=/'];
.end(function(err, res){
var val = ['user=' + encodeURIComponent('j:{"name":"tobi"}') + '; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();
})
@@ -32,7 +32,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['name=tobi; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();
@@ -50,7 +50,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['name=tobi; Path=/', 'age=1; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();
@@ -69,7 +69,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['name=tobi; Path=/; HttpOnly; Secure'];
res.headers['set-cookie'].should.eql(val);
done();
@@ -87,7 +87,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers['set-cookie'][0].should.not.include('Thu, 01 Jan 1970 00:00:01 GMT');
done();
})
@@ -106,7 +106,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = res.headers['set-cookie'][0];
val = cookie.parse(val.split('.')[0]);
val.user.should.equal('j:{"name":"tobi"}');
@@ -127,7 +127,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['name=tobi.xJjV2iZ6EI7C8E5kzwbfA9PVLl1ZR07UTnuTgQQ4EnQ; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();

View File

@@ -14,10 +14,10 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="user.html"');
res.body.should.equal('<p>{{user.name}}</p>');
res.text.should.equal('<p>{{user.name}}</p>');
done();
});
})
@@ -33,7 +33,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="document"');
done();
@@ -52,7 +52,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="user.html"');
});
@@ -70,10 +70,49 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="document"');
});
})
})
describe('on failure', function(){
it('should invoke the callback', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.download('test/fixtures/foobar.html', function(err){
assert(404 == err.status);
assert('ENOENT' == err.code);
done();
});
});
request(app)
.get('/')
.end(function(){});
})
it('should remove Content-Disposition', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.download('test/fixtures/foobar.html', function(err){
res.end('failed');
});
});
request(app)
.get('/')
.expect('failed')
.end(function(err, res){
if (err) return done(err);
res.header.should.not.have.property('content-disposition');
done();
});
})
})
})

View File

@@ -44,6 +44,15 @@ app2.use(function(err, req, res, next){
res.send(err.status, 'Supports: ' + err.types.join(', '));
})
var app3 = express();
app3.use(function(req, res, next){
res.format({
text: function(){ res.send('hey') },
default: function(){ res.send('default') }
})
});
describe('req', function(){
describe('.format(obj)', function(){
describe('with canonicalized mime types', function(){
@@ -53,6 +62,15 @@ describe('req', function(){
describe('with extnames', function(){
test(app2);
})
describe('given .default', function(){
it('should be invoked instead of auto-responding', function(done){
request(app3)
.get('/')
.set('Accept: text/html')
.expect('default', done);
})
})
})
})
@@ -75,21 +93,15 @@ function test(app) {
request(app)
.get('/')
.set('Accept', 'text/html; q=.5, text/plain')
.end(function(res){
res.headers['content-type'].should.equal('text/plain');
res.body.should.equal('hey');
done();
});
.expect('Content-Type', 'text/plain')
.expect('hey', done);
})
it('should Vary: Accept', function(done){
request(app)
.get('/')
.set('Accept', 'text/html; q=.5, text/plain')
.end(function(res){
res.headers.vary.should.equal('Accept');
done();
});
.expect('Vary', 'Accept', done);
})
describe('when Accept is not present', function(){
@@ -105,11 +117,8 @@ function test(app) {
request(app)
.get('/')
.set('Accept', 'foo/bar')
.end(function(res){
res.should.have.status(406);
res.body.should.equal('Supports: text/plain, text/html, application/json');
done();
});
.expect('Supports: text/plain, text/html, application/json')
.expect(406, done)
})
})
}

View File

@@ -15,13 +15,30 @@ describe('res', function(){
request(app)
.get('/?callback=something')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.body.should.equal('something({"count":1});');
res.text.should.equal('something({"count":1});');
done();
})
})
it('should allow renaming callback', function(done){
var app = express();
app.set('jsonp callback name', 'clb');
app.use(function(req, res){
res.json({ count: 1 });
});
request(app)
.get('/?clb=something')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('something({"count":1});');
done();
})
})
it('should allow []', function(done){
var app = express();
@@ -31,9 +48,9 @@ describe('res', function(){
request(app)
.get('/?callback=callbacks[123]')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.body.should.equal('callbacks[123]({"count":1});');
res.text.should.equal('callbacks[123]({"count":1});');
done();
})
})
@@ -49,9 +66,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('null');
res.text.should.equal('null');
done();
})
})
@@ -67,9 +84,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('["foo","bar","baz"]');
res.text.should.equal('["foo","bar","baz"]');
done();
})
})
@@ -85,9 +102,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('{"name":"tobi"}');
res.text.should.equal('{"name":"tobi"}');
done();
})
})
@@ -109,8 +126,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('{"name":"tobi"}');
.end(function(err, res){
res.text.should.equal('{"name":"tobi"}');
done();
});
})
@@ -140,8 +157,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('{\n "name": "tobi",\n "age": 2\n}');
.end(function(err, res){
res.text.should.equal('{\n "name": "tobi",\n "age": 2\n}');
done();
});
})
@@ -158,10 +175,29 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(201);
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('{"id":1}');
res.text.should.equal('{"id":1}');
done();
})
})
})
describe('.json(object, status)', function(){
it('should respond with json and set the .statusCode for backwards compat', function(done){
var app = express();
app.use(function(req, res){
res.json({ id: 1 }, 201);
});
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(201);
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.text.should.equal('{"id":1}');
done();
})
})

19
test/res.links.js Normal file
View File

@@ -0,0 +1,19 @@
var express = require('../')
, res = express.response;
describe('res', function(){
describe('.links(obj)', function(){
it('should set Link header field', function(){
res.links({
next: 'http://api.example.com/users?page=2',
last: 'http://api.example.com/users?page=5'
});
res.get('link')
.should.equal(
'<http://api.example.com/users?page=2>; rel="next", '
+ '<http://api.example.com/users?page=5>; rel="last"');
})
})
})

View File

@@ -17,9 +17,28 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
done();
})
.expect(200, done);
})
})
it('should work when mounted', function(done){
var app = express();
var blog = express();
app.use(blog);
blog.use(function(req, res, next){
res.locals.foo = 'bar';
next();
});
app.use(function(req, res){
res.locals.foo.should.equal('bar');
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})

View File

@@ -1,104 +0,0 @@
var express = require('../')
, request = require('./support/http');
describe('res', function(){
describe('.locals.use(fn)', function(){
it('should run in parallel on res.render()', function(done){
var app = express();
var calls = [];
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.use(function(req, res, next){
res.locals.use(function(req, res, done){
process.nextTick(function(){
calls.push('one');
res.locals.last = 'holowaychuk';
done();
});
});
next();
});
app.use(function(req, res, next){
res.locals.use(function(req, res, done){
process.nextTick(function(){
calls.push('two');
res.locals.species = 'ferret';
done();
});
});
next();
});
app.use(function(req, res){
calls.push('render');
res.render('pet.jade');
});
request(app)
.get('/')
.end(function(res){
calls.should.eql(['render', 'one', 'two']);
res.body.should.equal('<p>tobi holowaychuk is a ferret</p>');
done();
})
})
describe('with arity < 3', function(){
it('should done() for you', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.use(function(req, res, next){
res.locals.use(function(req, res){
res.locals.last = 'holowaychuk';
res.locals.species = 'ferret';
});
next();
});
app.use(function(req, res){
res.render('pet.jade');
});
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi holowaychuk is a ferret</p>');
done();
})
})
})
it('should not override res.render() locals', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.use(function(req, res, next){
res.locals.use(function(req, res){
res.locals.last = 'holowaychuk';
res.locals.species = 'ferret';
});
next();
});
app.use(function(req, res){
res.render('pet.jade', { last: 'ibot' });
});
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi ibot is a ferret</p>');
done();
})
})
})
})

View File

@@ -4,26 +4,6 @@ var express = require('../')
describe('res', function(){
describe('.redirect(url)', function(){
it('should respect X-Forwarded-Proto when "trust proxy" is enabled', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.redirect('/login');
});
request(app)
.get('/')
.set('Host', 'example.com')
.set('X-Forwarded-Proto', 'https')
.end(function(res){
res.statusCode.should.equal(302);
res.headers.should.have.property('location', 'https://example.com/login');
done();
})
})
it('should default to a 302 redirect', function(done){
var app = express();
@@ -33,15 +13,34 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(302);
res.headers.should.have.property('location', 'http://google.com');
done();
})
})
describe('with leading //', function(){
it('should pass through scheme-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.redirect('//cuteoverload.com');
});
request(app)
.get('/')
.set('Host', 'example.com')
.end(function(err, res){
res.headers.should.have.property('location', '//cuteoverload.com');
done();
})
})
})
describe('with leading /', function(){
it('should construct host-relative urls', function(done){
it('should construct scheme-relative urls', function(done){
var app = express();
app.use(function(req, res){
@@ -51,8 +50,8 @@ describe('res', function(){
request(app)
.get('/')
.set('Host', 'example.com')
.end(function(res){
res.headers.should.have.property('location', 'http://example.com/login');
.end(function(err, res){
res.headers.should.have.property('location', '//example.com/login');
done();
})
})
@@ -69,8 +68,8 @@ describe('res', function(){
request(app)
.get('/post/1')
.set('Host', 'example.com')
.end(function(res){
res.headers.should.have.property('location', 'http://example.com/post/1/./edit');
.end(function(err, res){
res.headers.should.have.property('location', '//example.com/post/1/./edit');
done();
})
})
@@ -87,8 +86,8 @@ describe('res', function(){
request(app)
.get('/post/1')
.set('Host', 'example.com')
.end(function(res){
res.headers.should.have.property('location', 'http://example.com/post/1/../new');
.end(function(err, res){
res.headers.should.have.property('location', '//example.com/post/1/../new');
done();
})
})
@@ -105,8 +104,8 @@ describe('res', function(){
request(app)
.get('/')
.set('Host', 'example.com')
.end(function(res){
res.headers.should.have.property('location', 'http://example.com/login');
.end(function(err, res){
res.headers.should.have.property('location', '//example.com/login');
done();
})
})
@@ -129,8 +128,8 @@ describe('res', function(){
request(app)
.get('/blog/admin')
.set('Host', 'example.com')
.end(function(res){
res.headers.should.have.property('location', 'http://example.com/blog/admin/login');
.end(function(err, res){
res.headers.should.have.property('location', '//example.com/blog/admin/login');
done();
})
})
@@ -150,8 +149,8 @@ describe('res', function(){
request(app)
.get('/blog')
.set('Host', 'example.com')
.end(function(res){
res.headers.should.have.property('location', 'http://example.com/blog/admin/login');
.end(function(err, res){
res.headers.should.have.property('location', '//example.com/blog/admin/login');
done();
})
})
@@ -171,8 +170,8 @@ describe('res', function(){
request(app)
.get('/blog')
.set('Host', 'example.com')
.end(function(res){
res.headers.should.have.property('location', 'http://example.com/admin/login');
.end(function(err, res){
res.headers.should.have.property('location', '//example.com/admin/login');
done();
})
})
@@ -190,7 +189,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(303);
res.headers.should.have.property('location', 'http://google.com');
done();
@@ -208,9 +207,9 @@ describe('res', function(){
request(app)
.head('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://google.com');
res.body.should.equal('');
res.text.should.equal('');
done();
})
})
@@ -227,9 +226,26 @@ describe('res', function(){
request(app)
.get('/')
.set('Accept', 'text/html')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://google.com');
res.body.should.equal('<p>Moved Temporarily. Redirecting to <a href="http://google.com">http://google.com</a></p>');
res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="http://google.com">http://google.com</a></p>');
done();
})
})
it('should escape the url', function(done){
var app = express();
app.use(function(req, res){
res.redirect('<lame>');
});
request(app)
.get('/')
.set('Host', 'http://example.com')
.set('Accept', 'text/html')
.end(function(err, res){
res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="//http://example.com/&lt;lame&gt;">//http://example.com/&lt;lame&gt;</a></p>');
done();
})
})
@@ -246,10 +262,10 @@ describe('res', function(){
request(app)
.get('/')
.set('Accept', 'text/plain, */*')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://google.com');
res.headers.should.have.property('content-length', '51');
res.body.should.equal('Moved Temporarily. Redirecting to http://google.com');
res.text.should.equal('Moved Temporarily. Redirecting to http://google.com');
done();
})
})
@@ -266,12 +282,12 @@ describe('res', function(){
request(app)
.get('/')
.set('Accept', 'foo/bar')
.end(function(res){
.end(function(err, res){
res.should.have.status(302);
res.headers.should.have.property('location', 'http://google.com');
res.headers.should.not.have.property('content-type');
res.headers.should.have.property('content-length', '0');
res.body.should.equal('');
res.text.should.equal('');
done();
})
})

View File

@@ -15,10 +15,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should support absolute paths with "view engine"', function(done){
@@ -33,10 +30,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should expose app.locals', function(done){
@@ -51,10 +45,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should support index.<engine>', function(done){
@@ -69,10 +60,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<h1>blog post</h1>');
done();
});
.expect('<h1>blog post</h1>', done);
})
describe('when an error occurs', function(){
@@ -91,10 +79,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.match(/user is not defined/);
done();
});
.expect(/user is not defined/, done);
})
})
@@ -111,10 +96,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>This is an email</p>');
done();
});
.expect('<p>This is an email</p>', done);
})
})
})
@@ -133,10 +115,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should expose app.locals', function(done){
@@ -151,10 +130,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should expose res.locals', function(done){
@@ -169,10 +145,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should give precedence to res.locals over app.locals', function(done){
@@ -188,10 +161,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>jane</p>');
done();
});
.expect('<p>jane</p>', done);
})
it('should give precedence to res.render() locals over res.locals', function(done){
@@ -207,10 +177,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>jane</p>');
done();
});
.expect('<p>jane</p>', done);
})
it('should give precedence to res.render() locals over app.locals', function(done){
@@ -226,10 +193,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>jane</p>');
done();
});
.expect('<p>jane</p>', done);
})
})
@@ -249,10 +213,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>loki</p>');
done();
});
.expect('<p>loki</p>', done);
})
})
@@ -272,10 +233,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>loki</p>');
done();
});
.expect('<p>loki</p>', done);
})
describe('when an error occurs', function(){
@@ -292,12 +250,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.match(/is not defined/);
done();
});
.expect(/is not defined/, done);
})
})
})
})

View File

@@ -13,10 +13,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('');
done();
})
.expect('', done);
})
})
@@ -30,10 +27,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('');
done();
})
.expect('', done);
})
})
@@ -47,11 +41,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('Created');
res.statusCode.should.equal(201);
done();
})
.expect('Created')
.expect(201, done);
})
})
@@ -65,14 +56,26 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('Created :)');
res.statusCode.should.equal(201);
done();
})
.expect('Created :)')
.expect(201, done);
})
})
describe('.send(body, code)', function(){
it('should be supported for backwards compat', function(done){
var app = express();
app.use(function(req, res){
res.send('Bad!', 400);
});
request(app)
.get('/')
.expect('Bad!')
.expect(400, done);
})
})
describe('.send(String)', function(){
it('should send as html', function(done){
var app = express();
@@ -83,9 +86,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/html; charset=utf-8');
res.body.should.equal('<p>hey</p>');
res.text.should.equal('<p>hey</p>');
res.statusCode.should.equal(200);
done();
})
@@ -101,10 +104,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('etag', '-1498647312');
done();
})
.expect('ETag', '-1498647312')
.end(done);
})
it('should not override Content-Type', function(done){
@@ -116,12 +117,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'text/plain');
res.body.should.equal('hey');
res.statusCode.should.equal(200);
done();
})
.expect('Content-Type', 'text/plain')
.expect('hey')
.expect(200, done);
})
})
@@ -135,9 +133,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/html; charset=utf-8');
res.body.should.equal('<p>hey</p>');
res.text.should.equal('<p>hey</p>');
res.statusCode.should.equal(200);
done();
})
@@ -152,9 +150,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/plain');
res.body.should.equal('hey');
res.text.should.equal('hey');
res.statusCode.should.equal(200);
done();
})
@@ -171,9 +169,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/octet-stream');
res.body.should.equal('hello');
res.text.should.equal('hello');
res.statusCode.should.equal(200);
done();
})
@@ -189,10 +187,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('etag', '-1498647312');
done();
})
.expect('ETag', '-1498647312')
.end(done);
})
it('should not override Content-Type', function(done){
@@ -204,9 +200,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/plain');
res.body.should.equal('hey');
res.text.should.equal('hey');
res.statusCode.should.equal(200);
done();
})
@@ -223,9 +219,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('{"name":"tobi"}');
res.text.should.equal('{"name":"tobi"}');
done();
})
})
@@ -241,10 +237,7 @@ describe('res', function(){
request(app)
.head('/')
.end(function(res){
res.body.should.equal('');
done();
})
.expect('', done);
})
})
@@ -258,10 +251,10 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.not.have.property('content-type');
res.headers.should.not.have.property('content-length');
res.body.should.equal('');
res.text.should.equal('');
done();
})
})
@@ -277,16 +270,16 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.not.have.property('content-type');
res.headers.should.not.have.property('content-length');
res.body.should.equal('');
res.text.should.equal('');
done();
})
})
})
it('should always check freshness', function(done){
it('should always check regardless of length', function(done){
var app = express();
app.use(function(req, res, next){
@@ -313,4 +306,20 @@ describe('res', function(){
.set('If-None-Match', '-1498647312')
.expect(304, done);
})
it('should not perform freshness check unless 2xx or 304', function(done){
var app = express();
app.use(function(req, res, next){
res.status(500);
res.set('ETag', 'asdf');
res.send('hey');
});
request(app)
.get('/')
.set('If-None-Match', 'asdf')
.expect('hey')
.expect(500, done);
})
})

View File

@@ -6,23 +6,20 @@ var express = require('../')
describe('res', function(){
describe('.sendfile(path, fn)', function(){
it('should invoke the callback when complete', function(done){
var app = express()
, calls = 0;
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', function(err){
assert(!err);
++calls;
req.socket.listeners('error').should.have.length(1); // node's original handler
done();
});
});
request(app)
.get('/')
.end(function(res){
calls.should.equal(1);
res.statusCode.should.equal(200);
done();
});
.expect(200)
.end(function(){});
})
it('should utilize the same options as express.static()', function(done){
@@ -34,10 +31,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.should.have.header('Cache-Control', 'public, max-age=60');
done();
});
.expect('Cache-Control', 'public, max-age=60')
.end(done);
})
it('should invoke the callback on 404', function(done){
@@ -46,17 +41,17 @@ describe('res', function(){
app.use(function(req, res){
res.sendfile('test/fixtures/nope.html', function(err){
assert(!res.headerSent);
++calls;
assert(!res.headerSent);
res.send(err.message);
});
});
request(app)
.get('/')
.end(function(res){
calls.should.equal(1);
res.body.should.equal('Not Found');
.end(function(err, res){
assert(1 == calls, 'called too many times');
res.text.should.equal("ENOENT, stat 'test/fixtures/nope.html'");
res.statusCode.should.equal(200);
done();
});
@@ -72,10 +67,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.should.have.header('content-type', 'text/plain');
done();
});
.expect('Content-Type', 'text/plain')
.end(done);
})
it('should invoke the callback on 403', function(done){
@@ -92,12 +85,27 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('Forbidden');
res.statusCode.should.equal(200);
calls.should.equal(1);
done();
.expect('Forbidden')
.expect(200, done);
})
it('should invoke the callback on socket error', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', function(err){
assert(!res.headerSent);
req.socket.listeners('error').should.have.length(1); // node's original handler
done();
});
req.socket.emit('error', new Error('broken!'));
});
request(app)
.get('/')
.end(function(){});
})
})
@@ -112,8 +120,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>{{user.name}}</p>');
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
@@ -130,8 +138,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>{{user.name}}</p>');
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
@@ -146,8 +154,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>{{user.name}}</p>');
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
@@ -162,10 +170,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.statusCode.should.equal(403);
done();
});
.expect(403, done);
})
it('should allow ../ when "root" is set', function(done){
@@ -177,10 +182,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.statusCode.should.equal(200);
done();
});
.expect(200, done);
})
it('should disallow requesting out of "root"', function(done){
@@ -192,10 +194,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.statusCode.should.equal(403);
done();
});
.expect(403, done);
})
it('should next(404) when not found', function(done){
@@ -217,7 +216,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(404);
calls.should.equal(1);
done();
@@ -226,17 +225,16 @@ describe('res', function(){
describe('with non-GET', function(){
it('should still serve', function(done){
var app = express()
, calls = 0;
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile(__dirname + '/fixtures/name.txt');
});
app.use(function(req, res){
res.sendfile(__dirname + '/fixtures/name.txt');
});
request(app)
.get('/')
.expect('tobi', done);
request(app)
.get('/')
.expect('tobi', done);
})
})
})

View File

@@ -14,10 +14,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'text/x-foo');
done();
})
.expect('Content-Type', 'text/x-foo')
.end(done);
})
it('should coerce to a string', function(){
@@ -40,11 +38,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('x-foo', 'bar');
res.headers.should.have.property('x-bar', 'baz');
done();
})
.expect('X-Foo', 'bar')
.expect('X-Bar', 'baz')
.end(done);
})
it('should coerce to a string', function(){

View File

@@ -13,11 +13,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('Created');
res.statusCode.should.equal(201);
done();
})
.expect('Created')
.expect(201, done);
})
})
})

View File

@@ -13,10 +13,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'application/javascript');
done();
})
.expect('Content-Type', 'application/javascript', done);
})
it('should default to application/octet-stream', function(done){
@@ -28,10 +25,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'application/octet-stream');
done();
})
.expect('Content-Type', 'application/octet-stream', done);
})
it('should set the Content-Type with type/subtype', function(done){
@@ -44,10 +38,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'application/vnd.amazon.ebook');
done();
})
.expect('Content-Type', 'application/vnd.amazon.ebook', done);
})
})
})

View File

@@ -1,105 +1,2 @@
/**
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter
, methods = require('../../').methods
, http = require('http');
module.exports = request;
function request(app) {
return new Request(app);
}
function Request(app) {
var self = this;
this.data = [];
this.header = {};
this.app = app;
if (!this.server) {
this.server = http.Server(app);
this.server.listen(0, function(){
self.addr = self.server.address();
self.listening = true;
});
}
}
/**
* Inherit from `EventEmitter.prototype`.
*/
Request.prototype.__proto__ = EventEmitter.prototype;
methods.forEach(function(method){
Request.prototype[method] = function(path){
return this.request(method, path);
};
});
Request.prototype.set = function(field, val){
this.header[field] = val;
return this;
};
Request.prototype.write = function(data){
this.data.push(data);
return this;
};
Request.prototype.request = function(method, path){
this.method = method;
this.path = path;
return this;
};
Request.prototype.expect = function(body, fn){
this.end(function(res){
if ('number' == typeof body) {
res.statusCode.should.equal(body);
} else if (body instanceof RegExp) {
res.body.should.match(body);
} else {
res.body.should.equal(body);
}
fn();
});
};
Request.prototype.end = function(fn){
var self = this;
if (this.listening) {
var req = http.request({
method: this.method
, port: this.addr.port
, host: this.addr.address
, path: this.path
, headers: this.header
});
this.data.forEach(function(chunk){
req.write(chunk);
});
req.on('response', function(res){
var buf = '';
res.setEncoding('utf8');
res.on('data', function(chunk){ buf += chunk });
res.on('end', function(){
res.body = buf;
fn(res);
});
});
req.end();
} else {
this.server.on('listening', function(){
self.end(fn);
});
}
return this;
};
module.exports = require('supertest');