Comparar commits

..

1 Commits

Autor SHA1 Mensagem Data
Roman Shtylman b0da4b1a94 streaming view rendering
Allows for a view engine to return a stream upon being asked to render a
path. This allows for the engine to simply stream out data as it is
loaded from disk (or other IO location) without having to collect it all
first and then send it.

Fallback mode for all existing engines that call `fn` upon completion
takes over if no stream is returned. This works for both app.render and
res.render
2013-11-09 21:27:01 -05:00
77 arquivos alterados com 2429 adições e 1641 exclusões
+2 -1
Ver Arquivo
@@ -1,3 +1,4 @@
language: node_js
node_js:
- "0.10"
- "0.8"
- "0.10"
-45
Ver Arquivo
@@ -1,48 +1,3 @@
4.0.0 /
==================
* remove:
- express(1) - moved to [express-generator](https://github.com/expressjs/generator)
- `express.createServer()` - it has been deprecated for a long time. Use `express()`
- `app.configure` - use logic in your own app code
- `app.router` - is removed
- `req.accepted*` - use `req.accepts*()` instead
- `res.location` - relative URL resolution is removed
- all bundled middleware except `static`
* change:
- `app.route` -> `app.mountpath` when mounting an express app in another express app
- `json spaces` no longer enabled by default in development
- `req.accepts*` -> `req.accepts*s` - i.e. `req.acceptsEncoding` -> `req.acceptsEncodings`
- `req.params` is now an object instead of an array
- `res.locals` is no longer a function. It is a plain js object. Treat it as such.
- `res.headerSent` -> `res.headersSent` to match node.js ServerResponse object
* refactor:
- `req.accepts*` with [accepts](https://github.com/expressjs/accepts)
- `req.is` with [type-is](https://github.com/expressjs/type-is)
* add:
- `app.router()` - returns the app Router instance
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route
- Router & Route - public API
3.4.7 / 2013-12-10
==================
* update connect
3.4.6 / 2013-12-01
==================
* update connect (raw-body)
3.4.5 / 2013-11-27
==================
* update connect
* res.location: remove leading ./ #1802 @kapouer
* res.redirect: fix `res.redirect('toString') #1829 @michaelficarra
* res.send: always send ETag when content-length > 0
* router: add Router.all() method
3.4.4 / 2013-10-29
==================
+3 -3
Ver Arquivo
@@ -24,11 +24,11 @@ test-cov: lib-cov
lib-cov:
@jscoverage lib lib-cov
bench:
@$(MAKE) -C benchmarks
benchmark:
@./support/bench
clean:
rm -f coverage.html
rm -fr lib-cov
.PHONY: test test-unit test-acceptance bench clean
.PHONY: test test-unit test-acceptance benchmark clean
+8 -8
Ver Arquivo
@@ -50,14 +50,14 @@ app.listen(3000);
## Philosophy
The Express philosophy is to provide small, robust tooling for HTTP servers, making
The Express philosophy is to provide small, robust tooling for HTTP servers. Making
it a great solution for single page applications, web sites, hybrids, or public
HTTP APIs.
Built on Connect, you can use _only_ what you need, and nothing more. Applications
Built on Connect you can use _only_ what you need, and nothing more, applications
can be as big or as small as you like, even a single file. Express does
not force you to use any specific ORM or template engine. With support for over
14 template engines via [Consolidate.js](http://github.com/visionmedia/consolidate.js),
14 template engines via [Consolidate.js](http://github.com/visionmedia/consolidate.js)
you can quickly craft your perfect framework.
## More Information
@@ -72,27 +72,27 @@ app.listen(3000);
## Viewing Examples
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
Clone the Express repo, then install the dev dependencies to install all the example / test suite deps:
$ git clone git://github.com/visionmedia/express.git --depth 1
$ cd express
$ npm install
Then run whichever tests you want:
then run whichever tests you want:
$ node examples/content-negotiation
You can also view live examples here:
You can also view live examples here
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
## Running Tests
To run the test suite, first invoke the following command within the repo, installing the development dependencies:
To run the test suite first invoke the following command within the repo, installing the development dependencies:
$ npm install
Then run the tests:
then run the tests:
$ make test
-13
Ver Arquivo
@@ -1,13 +0,0 @@
all:
@./run 1 middleware
@./run 5 middleware
@./run 10 middleware
@./run 15 middleware
@./run 20 middleware
@./run 30 middleware
@./run 50 middleware
@./run 100 middleware
@echo
.PHONY: all
-23
Ver Arquivo
@@ -1,23 +0,0 @@
var http = require('http');
var express = require('..');
var app = express();
// number of middleware
var n = parseInt(process.env.MW || '1', 10);
console.log(' %s middleware', n);
while (n--) {
app.use(function(req, res, next){
next();
});
}
var body = new Buffer('Hello World');
app.use(function(req, res, next){
res.send(body);
});
app.listen(3333);
-16
Ver Arquivo
@@ -1,16 +0,0 @@
#!/usr/bin/env bash
echo
MW=$1 node $2 &
pid=$!
sleep 2
wrk 'http://localhost:3333/?foo[bar]=baz' \
-d 3 \
-c 50 \
-t 8 \
| grep 'Requests/sec' \
| awk '{ print " " $2 }'
kill $pid
Arquivo executável
+423
Ver Arquivo
@@ -0,0 +1,423 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander')
, mkdirp = require('mkdirp')
, pkg = require('../package.json')
, version = pkg.version
, os = require('os')
, fs = require('fs');
// CLI
program
.version(version)
.usage('[options] [dir]')
.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, --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);
// Path
var path = program.args.shift() || '.';
// end-of-line code
var eol = os.EOL
// Template engine
program.template = 'jade';
if (program.ejs) program.template = 'ejs';
if (program.jshtml) program.template = 'jshtml';
if (program.hogan) program.template = 'hjs';
/**
* Routes index template.
*/
var index = [
''
, '/*'
, ' * GET home page.'
, ' */'
, ''
, 'exports.index = function(req, res){'
, ' res.render(\'index\', { title: \'Express\' });'
, '};'
].join(eol);
/**
* Routes users template.
*/
var users = [
''
, '/*'
, ' * GET users listing.'
, ' */'
, ''
, 'exports.list = function(req, res){'
, ' res.send("respond with a resource");'
, '};'
].join(eol);
/**
* Jade layout template.
*/
var jadeLayout = [
'doctype 5'
, 'html'
, ' head'
, ' title= title'
, ' link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')'
, ' body'
, ' block content'
].join(eol);
/**
* Jade index template.
*/
var jadeIndex = [
'extends layout'
, ''
, 'block content'
, ' h1= title'
, ' p Welcome to #{title}'
].join(eol);
/**
* EJS index template.
*/
var ejsIndex = [
'<!DOCTYPE html>'
, '<html>'
, ' <head>'
, ' <title><%= title %></title>'
, ' <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
, ' </head>'
, ' <body>'
, ' <h1><%= title %></h1>'
, ' <p>Welcome to <%= title %></p>'
, ' </body>'
, '</html>'
].join(eol);
/**
* JSHTML layout template.
*/
var jshtmlLayout = [
'<!DOCTYPE html>'
, '<html>'
, ' <head>'
, ' <title> @write(title) </title>'
, ' <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
, ' </head>'
, ' <body>'
, ' @write(body)'
, ' </body>'
, '</html>'
].join(eol);
/**
* JSHTML index template.
*/
var jshtmlIndex = [
'<h1>@write(title)</h1>'
, '<p>Welcome to @write(title)</p>'
].join(eol);
/**
* Hogan.js index template.
*/
var hoganIndex = [
'<!DOCTYPE html>'
, '<html>'
, ' <head>'
, ' <title>{{ title }}</title>'
, ' <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
, ' </head>'
, ' <body>'
, ' <h1>{{ title }}</h1>'
, ' <p>Welcome to {{ title }}</p>'
, ' </body>'
, '</html>'
].join(eol);
/**
* Default css template.
*/
var css = [
'body {'
, ' padding: 50px;'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
, '}'
, ''
, 'a {'
, ' color: #00B7FF;'
, '}'
].join(eol);
/**
* Default less template.
*/
var less = [
'body {'
, ' padding: 50px;'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
, '}'
, ''
, 'a {'
, ' color: #00B7FF;'
, '}'
].join(eol);
/**
* Default stylus template.
*/
var stylus = [
'body'
, ' padding: 50px'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif'
, 'a'
, ' color: #00B7FF'
].join(eol);
/**
* App template.
*/
var app = [
''
, '/**'
, ' * Module dependencies.'
, ' */'
, ''
, 'var express = require(\'express\');'
, 'var routes = require(\'./routes\');'
, 'var user = require(\'./routes/user\');'
, 'var http = require(\'http\');'
, 'var path = require(\'path\');'
, ''
, 'var app = express();'
, ''
, '// all environments'
, 'app.set(\'port\', process.env.PORT || 3000);'
, 'app.set(\'views\', path.join(__dirname, \'views\'));'
, 'app.set(\'view engine\', \':TEMPLATE\');'
, 'app.use(express.favicon());'
, 'app.use(express.logger(\'dev\'));'
, 'app.use(express.json());'
, 'app.use(express.urlencoded());'
, 'app.use(express.methodOverride());{sess}'
, 'app.use(app.router);{css}'
, 'app.use(express.static(path.join(__dirname, \'public\')));'
, ''
, '// development only'
, 'if (\'development\' == app.get(\'env\')) {'
, ' app.use(express.errorHandler());'
, '}'
, ''
, 'app.get(\'/\', routes.index);'
, 'app.get(\'/users\', user.list);'
, ''
, 'http.createServer(app).listen(app.get(\'port\'), function(){'
, ' console.log(\'Express server listening on port \' + app.get(\'port\'));'
, '});'
, ''
].join(eol);
// Generate application
(function createApplication(path) {
emptyDirectory(path, function(empty){
if (empty || program.force) {
createApplicationAt(path);
} else {
program.confirm('destination is not empty, continue? ', function(ok){
if (ok) {
process.stdin.destroy();
createApplicationAt(path);
} else {
abort('aborting');
}
});
}
});
})(path);
/**
* Create application at the given directory `path`.
*
* @param {String} path
*/
function createApplicationAt(path) {
console.log();
process.on('exit', function(){
console.log();
console.log(' install dependencies:');
console.log(' $ cd %s && npm install', path);
console.log();
console.log(' run the app:');
console.log(' $ node app');
console.log();
});
mkdir(path, function(){
mkdir(path + '/public');
mkdir(path + '/public/javascripts');
mkdir(path + '/public/images');
mkdir(path + '/public/stylesheets', function(){
switch (program.css) {
case 'less':
write(path + '/public/stylesheets/style.less', less);
break;
case 'stylus':
write(path + '/public/stylesheets/style.styl', stylus);
break;
default:
write(path + '/public/stylesheets/style.css', css);
}
});
mkdir(path + '/routes', function(){
write(path + '/routes/index.js', index);
write(path + '/routes/user.js', users);
});
mkdir(path + '/views', function(){
switch (program.template) {
case 'ejs':
write(path + '/views/index.ejs', ejsIndex);
break;
case 'jade':
write(path + '/views/layout.jade', jadeLayout);
write(path + '/views/index.jade', jadeIndex);
break;
case 'jshtml':
write(path + '/views/layout.jshtml', jshtmlLayout);
write(path + '/views/index.jshtml', jshtmlIndex);
break;
case 'hjs':
write(path + '/views/index.hjs', hoganIndex);
break;
}
});
// CSS Engine support
switch (program.css) {
case 'less':
app = app.replace('{css}', eol + 'app.use(require(\'less-middleware\')({ src: path.join(__dirname, \'public\') }));');
break;
case 'stylus':
app = app.replace('{css}', eol + 'app.use(require(\'stylus\').middleware(path.join(__dirname, \'public\')));');
break;
default:
app = app.replace('{css}', '');
}
// Session support
app = app.replace('{sess}', program.sessions
? eol + 'app.use(express.cookieParser(\'your secret here\'));' + eol + 'app.use(express.session());'
: '');
// Template support
app = app.replace(':TEMPLATE', program.template);
// package.json
var pkg = {
name: 'application-name'
, version: '0.0.1'
, private: true
, scripts: { start: 'node app.js' }
, dependencies: {
express: version
}
}
if (program.template) pkg.dependencies[program.template] = '*';
// CSS Engine support
switch (program.css) {
case 'less':
pkg.dependencies['less-middleware'] = '*';
break;
default:
if (program.css) {
pkg.dependencies[program.css] = '*';
}
}
write(path + '/package.json', JSON.stringify(pkg, null, 2));
write(path + '/app.js', app);
});
}
/**
* Check if the given directory `path` is empty.
*
* @param {String} path
* @param {Function} fn
*/
function emptyDirectory(path, fn) {
fs.readdir(path, function(err, files){
if (err && 'ENOENT' != err.code) throw err;
fn(!files || !files.length);
});
}
/**
* echo str > path.
*
* @param {String} path
* @param {String} str
*/
function write(path, str) {
fs.writeFile(path, str);
console.log(' \x1b[36mcreate\x1b[0m : ' + path);
}
/**
* Mkdir -p.
*
* @param {String} path
* @param {Function} fn
*/
function mkdir(path, fn) {
mkdirp(path, 0755, function(err){
if (err) throw err;
console.log(' \033[36mcreate\033[0m : ' + path);
fn && fn();
});
}
/**
* Exit with the given `str`.
*
* @param {String} str
*/
function abort(str) {
console.error(str);
process.exit(1);
}
+4 -7
Ver Arquivo
@@ -3,10 +3,7 @@
*/
var express = require('../..')
, hash = require('./pass').hash
, bodyParser = require('body-parser')
, cookieParser = require('cookie-parser')
, session = require('express-session')
, hash = require('./pass').hash;
var app = module.exports = express();
@@ -17,9 +14,9 @@ app.set('views', __dirname + '/views');
// middleware
app.use(bodyParser());
app.use(cookieParser('shhhh, very secret'));
app.use(session());
app.use(express.bodyParser());
app.use(express.cookieParser('shhhh, very secret'));
app.use(express.session());
// Session-persisted message middleware
+3 -3
Ver Arquivo
@@ -32,7 +32,7 @@ var iterations = 12000;
exports.hash = function (pwd, salt, fn) {
if (3 == arguments.length) {
crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash){
fn(err, hash.toString('base64'));
fn(err, (new Buffer(hash, 'binary')).toString('base64'));
});
} else {
fn = salt;
@@ -41,8 +41,8 @@ exports.hash = function (pwd, salt, fn) {
salt = salt.toString('base64');
crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash){
if (err) return fn(err);
fn(null, salt, hash.toString('base64'));
fn(null, salt, (new Buffer(hash, 'binary')).toString('base64'));
});
});
}
};
};
+2 -3
Ver Arquivo
@@ -1,6 +1,5 @@
var express = require('../..')
, logger = require('morgan')
, app = express();
app.set('views', __dirname);
@@ -15,11 +14,11 @@ while (n--) {
pets.push({ name: 'Jane', age: 6, species: 'ferret' });
}
app.use(logger('dev'));
app.use(express.logger('dev'));
app.get('/', function(req, res){
res.render('pets', { pets: pets });
});
app.listen(3000);
console.log('Express listening on port 3000');
console.log('Express listening on port 3000');
+3 -3
Ver Arquivo
@@ -28,10 +28,10 @@ app.get('/', function(req, res){
// this to add a layer of abstraction
// and make things a bit more declarative:
function format(path) {
var obj = require(path);
function format(requestHandlerName) {
var requestHandler = require(requestHandlerName);
return function(req, res){
res.format(obj);
res.format(requestHandler);
}
}
+4 -6
Ver Arquivo
@@ -4,19 +4,17 @@
*/
var express = require('../../');
var favicon = require('static-favicon');
var cookie-parser = require('cookie-parser');
var app = module.exports = express();
// ignore GET /favicon.ico
app.use(favicon());
app.use(express.favicon());
// pass a secret to cookieParser() for signed cookies
app.use(cookieParser('manny is cool'));
app.use(express.cookieParser('manny is cool'));
// add req.session cookie support
app.use(cookieSession());
app.use(express.cookieSession());
// do something with the session
app.use(count);
@@ -31,4 +29,4 @@ function count(req, res) {
if (!module.parent) {
app.listen(3000);
console.log('Express server listening on port 3000');
}
}
+7 -11
Ver Arquivo
@@ -4,11 +4,7 @@
*/
var express = require('../../')
, app = module.exports = express()
, favicon = require('static-favicon')
, logger = require('morgan')
, cookieParser = require('cookie-parser')
, bodyParser = require('body-parser')
, app = module.exports = express();
// add favicon() before logger() so
@@ -16,20 +12,20 @@ var express = require('../../')
// logged, because this middleware
// reponds to /favicon.ico and does not
// call next()
app.use(favicon());
app.use(express.favicon());
// custom log format
if ('test' != process.env.NODE_ENV)
app.use(logger(':method :url'));
app.use(express.logger(':method :url'));
// parses request cookies, populating
// req.cookies and req.signedCookies
// when the secret is passed, used
// when the secret is passed, used
// for signing the cookies.
app.use(cookieParser('my secret here'));
app.use(express.cookieParser('my secret here'));
// parses json, x-www-form-urlencoded, and multipart/form-data
app.use(bodyParser());
app.use(express.bodyParser());
app.get('/', function(req, res){
if (req.cookies.remember) {
@@ -55,4 +51,4 @@ app.post('/', function(req, res){
if (!module.parent){
app.listen(3000);
console.log('Express started on port 3000');
}
}
+2 -4
Ver Arquivo
@@ -3,9 +3,7 @@
*/
var express = require('../..')
, logger = require('morgan')
, app = express()
, bodyParser = require('body-parser')
, api = express();
// app middleware
@@ -14,8 +12,8 @@ app.use(express.static(__dirname + '/public'));
// api middleware
api.use(logger('dev'));
api.use(bodyParser());
api.use(express.logger('dev'));
api.use(express.bodyParser());
/**
* CORS support.
+35 -31
Ver Arquivo
@@ -4,8 +4,6 @@
var express = require('../../')
, app = module.exports = express()
, logger = require('morgan')
, favicon = require('static-favicon')
, silent = 'test' == process.env.NODE_ENV;
// general config
@@ -23,36 +21,18 @@ if ('production' == app.settings.env) {
app.disable('verbose errors');
}
app.use(favicon());
app.use(express.favicon());
silent || app.use(logger('dev'));
silent || app.use(express.logger('dev'));
// Routes
// "app.router" positions our routes
// above the middleware defined below,
// this means that Express will attempt
// to match & call routes _before_ continuing
// on, at which point we assume it's a 404 because
// no route has handled the request.
app.get('/', function(req, res){
res.render('index.jade');
});
app.get('/404', function(req, res, next){
// trigger a 404 since no other middleware
// will match /404 after this one, and we're not
// responding here
next();
});
app.get('/403', function(req, res, next){
// trigger a 403 error
var err = new Error('not allowed!');
err.status = 403;
next(err);
});
app.get('/500', function(req, res, next){
// trigger a generic (500) error
next(new Error('keyboard cat!'));
});
// Error handlers
app.use(app.router);
// Since this is the last non-error-handling
// middleware use()d, we assume 404, as nothing else
@@ -64,7 +44,7 @@ app.get('/500', function(req, res, next){
app.use(function(req, res, next){
res.status(404);
// respond with html page
if (req.accepts('html')) {
res.render('404', { url: req.url });
@@ -101,8 +81,32 @@ app.use(function(err, req, res, next){
res.render('500', { error: err });
});
// Routes
app.get('/', function(req, res){
res.render('index.jade');
});
app.get('/404', function(req, res, next){
// trigger a 404 since no other middleware
// will match /404 after this one, and we're not
// responding here
next();
});
app.get('/403', function(req, res, next){
// trigger a 403 error
var err = new Error('not allowed!');
err.status = 403;
next(err);
});
app.get('/500', function(req, res, next){
// trigger a generic (500) error
next(new Error('keyboard cat!'));
});
if (!module.parent) {
app.listen(3000);
silent || console.log('Express started on port 3000');
}
}
+9 -8
Ver Arquivo
@@ -4,11 +4,17 @@
*/
var express = require('../../')
, logger = require('morgan')
, app = module.exports = express()
, test = app.get('env') == 'test';
if (!test) app.use(logger('dev'));
if (!test) app.use(express.logger('dev'));
app.use(app.router);
// the error handler is strategically
// placed *below* the app.router; if it
// were above it would not receive errors
// from app.get() etc
app.use(error);
// error handling middleware have an arity of 4
// instead of the typical (req, res, next),
@@ -36,12 +42,7 @@ app.get('/next', function(req, res, next){
});
});
// the error handler is placed after routes
// if it were above it would not receive errors
// from app.get() etc
app.use(error);
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}
+2 -3
Ver Arquivo
@@ -1,6 +1,5 @@
var express = require('../..')
, logger = require('morgan')
, app = express();
app.set('view engine', 'jade');
@@ -24,7 +23,7 @@ User.prototype.toJSON = function(){
}
};
app.use(logger('dev'));
app.use(express.logger('dev'));
// earlier on expose an object
// that we can tack properties on.
@@ -58,4 +57,4 @@ app.get('/user', function(req, res){
});
app.listen(3000);
console.log('app listening on port 3000');
console.log('app listening on port 3000');
+2 -2
Ver Arquivo
@@ -1,5 +1,5 @@
doctype html
!!! 5
html
include header
body
block content
block content
+7 -8
Ver Arquivo
@@ -1,8 +1,4 @@
var express = require('../..');
var logger = require('morgan');
var session = require('express-session');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var app = module.exports = express();
@@ -29,17 +25,20 @@ app.response.message = function(msg){
};
// log
if (!module.parent) app.use(logger('dev'));
if (!module.parent) app.use(express.logger('dev'));
// serve static files
app.use(express.static(__dirname + '/public'));
// session support
app.use(cookieParser('some secret here'));
app.use(session());
app.use(express.cookieParser('some secret here'));
app.use(express.session());
// parse request bodies (req.body)
app.use(bodyParser());
app.use(express.bodyParser());
// support _method (PUT in forms etc)
app.use(express.methodOverride());
// expose the "messages" local variable when views are rendered
app.use(function(req, res, next){
+4 -6
Ver Arquivo
@@ -5,9 +5,6 @@
var express = require('../..')
, app = express()
, logger = require('morgan')
, cookieParser = require('cookie-parser')
, bodyParser = require('body-parser')
, site = require('./site')
, post = require('./post')
, user = require('./user');
@@ -16,9 +13,10 @@ var express = require('../..')
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');
app.use(logger('dev'));
app.use(cookieParser());
app.use(bodyParser());
app.use(express.logger('dev'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(__dirname + '/public'));
// General
+2
Ver Arquivo
@@ -7,6 +7,8 @@ var express = require('../..');
var app = express();
app.use(express.logger('dev'));
// Required by session() middleware
// pass the secret for signed cookies
// (required by session())
+2 -3
Ver Arquivo
@@ -1,10 +1,9 @@
var express = require('../..');
var logger = require('morgan');
var app = express();
// log requests
app.use(logger('dev'));
app.use(express.logger('dev'));
// express on its own has no notion
// of a "file". The express.static()
@@ -42,4 +41,4 @@ console.log('listening on port 3000');
console.log('try:');
console.log(' GET /hello.txt');
console.log(' GET /js/app.js');
console.log(' GET /css/style.css');
console.log(' GET /css/style.css');
+1 -2
Ver Arquivo
@@ -3,7 +3,6 @@
*/
var express = require('../..');
var logger = require('morgan');
/*
edit /etc/hosts:
@@ -17,7 +16,7 @@ edit /etc/hosts:
var main = express();
main.use(logger('dev'));
main.use(express.logger('dev'));
main.get('/', function(req, res){
res.send('Hello from main app!')
+1 -1
Ver Arquivo
@@ -1,4 +1,4 @@
doctype html
doctype 5
html
head
title= title
+25 -20
Ver Arquivo
@@ -40,6 +40,29 @@ app.use('/api', function(req, res, next){
next();
});
// position our routes above the error handling middleware,
// and below our API middleware, since we want the API validation
// to take place BEFORE our routes
app.use(app.router);
// middleware with an arity of 4 are considered
// error handling middleware. When you next(err)
// it will be passed through the defined middleware
// in order, but ONLY those with an arity of 4, ignoring
// regular middleware.
app.use(function(err, req, res, next){
// whatever you want here, feel free to populate
// properties on `err` to treat it differently in here.
res.send(err.status || 500, { error: err.message });
});
// our custom JSON 404 middleware. Since it's placed last
// it will be the last middleware called, if all others
// invoke next() and do not respond.
app.use(function(req, res){
res.send(404, { error: "Lame, can't find that" });
});
// map of valid api keys, typically mapped to
// account info with some sort of database like redis.
// api keys do _not_ serve as authentication, merely to
@@ -81,30 +104,12 @@ app.get('/api/repos', function(req, res, next){
app.get('/api/user/:name/repos', function(req, res, next){
var name = req.params.name
, user = userRepos[name];
if (user) res.send(user);
else next();
});
// middleware with an arity of 4 are considered
// error handling middleware. When you next(err)
// it will be passed through the defined middleware
// in order, but ONLY those with an arity of 4, ignoring
// regular middleware.
app.use(function(err, req, res, next){
// whatever you want here, feel free to populate
// properties on `err` to treat it differently in here.
res.send(err.status || 500, { error: err.message });
});
// our custom JSON 404 middleware. Since it's placed last
// it will be the last middleware called, if all others
// invoke next() and do not respond.
app.use(function(req, res){
res.send(404, { error: "Lame, can't find that" });
});
if (!module.parent) {
app.listen(3000);
console.log('Express server listening on port 3000');
}
}
+162 -150
Ver Arquivo
@@ -2,14 +2,14 @@
* Module dependencies.
*/
var mixin = require('utils-merge')
, escapeHtml = require('escape-html')
var connect = require('connect')
, Router = require('./router')
, methods = require('methods')
, middleware = require('./middleware/init')
, query = require('./middleware/query')
, middleware = require('./middleware')
, debug = require('debug')('express:application')
, locals = require('./utils').locals
, View = require('./view')
, utils = connect.utils
, http = require('http');
/**
@@ -45,11 +45,13 @@ app.defaultConfiguration = function(){
// default settings
this.enable('x-powered-by');
this.enable('etag');
var env = process.env.NODE_ENV || 'development';
this.set('env', env);
this.set('env', process.env.NODE_ENV || 'development');
this.set('subdomain offset', 2);
debug('booting in %s mode', this.get('env'));
debug('booting in %s mode', env);
// implicit middleware
this.use(connect.query());
this.use(middleware.init(this));
// inherit protos
this.on('mount', function(parent){
@@ -59,11 +61,18 @@ app.defaultConfiguration = function(){
this.settings.__proto__ = parent.settings;
});
// setup locals
this.locals = Object.create(null);
// router
this._router = new Router(this);
this.routes = this._router.map;
this.__defineGetter__('router', function(){
this._usedRouter = true;
this._router.caseSensitive = this.enabled('case sensitive routing');
this._router.strict = this.enabled('strict routing');
return this._router.middleware;
});
// top-most app is mounted at /
this.mountpath = '/';
// setup locals
this.locals = locals(this);
// default locals
this.locals.settings = this.settings;
@@ -73,94 +82,18 @@ app.defaultConfiguration = function(){
this.set('views', process.cwd() + '/views');
this.set('jsonp callback name', 'callback');
if (env === 'production') {
this.configure('development', function(){
this.set('json spaces', 2);
});
this.configure('production', function(){
this.enable('view cache');
}
Object.defineProperty(this, 'router', {
get: function() {
throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.');
}
});
};
/**
* lazily adds the base router if it has not yet been added.
*
* We cannot add the base router in the defaultConfiguration because
* it reads app settings which might be set after that has run.
*
* @api private
*/
app.lazyrouter = function() {
if (!this._router) {
this._router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
this._router.use(query());
this._router.use(middleware.init(this));
}
};
/**
* Dispatch a req, res pair into the application. Starts pipeline processing.
*
* If no _done_ callback is provided, then default error handlers will respond
* in the event of an error bubbling through the stack.
*
* @api private
*/
app.handle = function(req, res, done) {
var env = this.get('env');
this._router.handle(req, res, function(err) {
if (done) {
return done(err);
}
// unhandled error
if (err) {
// default to 500
if (res.statusCode < 400) res.statusCode = 500;
debug('default %s', res.statusCode);
// respect err.status
if (err.status) res.statusCode = err.status;
// production gets a basic error message
var msg = 'production' == env
? http.STATUS_CODES[res.statusCode]
: err.stack || err.toString();
msg = escapeHtml(msg);
// log to stderr in a non-test env
if ('test' != env) console.error(err.stack || err.toString());
if (res.headersSent) return req.socket.destroy();
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', Buffer.byteLength(msg));
if ('HEAD' == req.method) return res.end();
res.end(msg);
return;
}
// 404
debug('default 404');
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
if ('HEAD' == req.method) return res.end();
res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
});
};
/**
* Proxy `Router#use()` to add middleware to the app router.
* See Router#use() documentation for details.
*
* If the _fn_ parameter is an express app, then it will be
* mounted at the _route_ specified.
* Proxy `connect#use()` to apply settings to
* mounted applications.
*
* @param {String|Function|Server} route
* @param {Function|Server} fn
@@ -169,21 +102,20 @@ app.handle = function(req, res, done) {
*/
app.use = function(route, fn){
var mount_app;
var app;
// default route to '/'
if ('string' != typeof route) fn = route, route = '/';
// express app
if (fn.handle && fn.set) mount_app = fn;
if (fn.handle && fn.set) app = fn;
// restore .app property on req and res
if (mount_app) {
debug('.use app under %s', route);
mount_app.mountpath = route;
if (app) {
app.route = route;
fn = function(req, res, next) {
var orig = req.app;
mount_app.handle(req, res, function(err) {
app.handle(req, res, function(err){
req.__proto__ = orig.request;
res.__proto__ = orig.response;
next(err);
@@ -191,33 +123,17 @@ app.use = function(route, fn){
};
}
this.lazyrouter();
this._router.use(route, fn);
connect.proto.use.call(this, route, fn);
// mounted an app
if (mount_app) {
mount_app.parent = this;
mount_app.emit('mount', this);
if (app) {
app.parent = this;
app.emit('mount', this);
}
return this;
};
/**
* Proxy to the app `Router#route()`
* Returns a new `Route` instance for the _path_.
*
* Routes are isolated middleware stacks for specific paths.
* See the Route api docs for details.
*
* @api public
*/
app.route = function(path){
this.lazyrouter();
return this._router.route(path);
};
/**
* Register the given template engine callback `fn`
* as `ext`.
@@ -260,10 +176,30 @@ app.engine = function(ext, fn){
};
/**
* Proxy to `Router#param()` with one added api feature. The _name_ parameter
* can be an array of names.
* Map the given param placeholder `name`(s) to the given callback(s).
*
* See the Router#param() docs for more details.
* 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 same signature as middleware, the only difference
* 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'));
* }
* });
* });
*
* @param {String|Array} name
* @param {Function} fn
@@ -272,17 +208,27 @@ app.engine = function(ext, fn){
*/
app.param = function(name, fn){
var self = this;
self.lazyrouter();
var self = this
, fns = [].slice.call(arguments, 1);
// array
if (Array.isArray(name)) {
name.forEach(function(key) {
self.param(key, fn);
name.forEach(function(name){
fns.forEach(function(fn){
self.param(name, fn);
});
});
// param logic
} else if ('function' == typeof name) {
this._router.param(name);
// single
} else {
if (':' == name[0]) name = name.substr(1);
fns.forEach(function(fn){
self._router.param(name, fn);
});
return this;
}
self._router.param(name, fn);
return this;
};
@@ -296,7 +242,7 @@ app.param = function(name, fn){
* Mounted servers inherit their parent server's settings.
*
* @param {String} setting
* @param {*} [val]
* @param {String} val
* @return {Server} for chaining
* @api public
*/
@@ -326,7 +272,7 @@ app.set = function(setting, val){
app.path = function(){
return this.parent
? this.parent.path() + this.mountpath
? this.parent.path() + this.route
: '';
};
@@ -392,6 +338,60 @@ app.disable = function(setting){
return this.set(setting, false);
};
/**
* 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;
* }
*
* @param {String} env...
* @param {Function} fn
* @return {app} for chaining
* @api public
*/
app.configure = function(env, fn){
var envs = 'all'
, args = [].slice.call(arguments);
fn = args.pop();
if (args.length) envs = args;
if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
return this;
};
/**
* Delegate `.VERB(...)` calls to `router.VERB(...)`.
*/
@@ -400,17 +400,16 @@ methods.forEach(function(method){
app[method] = function(path){
if ('get' == method && 1 == arguments.length) return this.set(path);
this.lazyrouter();
// deprecated
if (Array.isArray(path)) {
console.trace('passing an array to app.VERB() is deprecated and will be removed in 4.0');
}
var route = this._router.route(path);
for (var i=1 ; i<arguments.length ; ++i) {
route[method](arguments[i]);
}
// if no router attached yet, attach the router
if (!this._usedRouter) this.use(this.router);
// setup route
this._router[method].apply(this._router, arguments);
return this;
};
});
@@ -426,16 +425,10 @@ methods.forEach(function(method){
*/
app.all = function(path){
this.lazyrouter();
var route = this._router.route(path);
var args = arguments;
methods.forEach(function(method){
for (var i=1 ; i<args.length ; ++i) {
route[method](args[i]);
}
});
app[method].apply(this, args);
}, this);
return this;
};
@@ -472,13 +465,13 @@ app.render = function(name, options, fn){
}
// merge app.locals
mixin(opts, this.locals);
utils.merge(opts, this.locals);
// merge options._locals
if (options._locals) mixin(opts, options._locals);
if (options._locals) utils.merge(opts, options._locals);
// merge options
mixin(opts, options);
utils.merge(opts, options);
// set .cache unless explicitly provided
opts.cache = null == opts.cache
@@ -497,7 +490,7 @@ app.render = function(name, options, fn){
});
if (!view.path) {
var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"');
var err = new Error('Failed to lookup view "' + name + '"');
err.view = view;
return fn(err);
}
@@ -508,7 +501,26 @@ app.render = function(name, options, fn){
// render
try {
view.render(opts, fn);
var view_stream = view.render(opts, fn);
// if the engine returned a stream AND user specified a function
// then we will capture data for user
if (view_stream && fn) {
var body = '';
view_stream.on('data', function(chunk) {
body += chunk;
});
view_stream.once('error', function(err) {
fn(err);
});
view_stream.once('end', function() {
fn(null, body);
});
}
return view_stream;
} catch (err) {
fn(err);
}
+41 -20
Ver Arquivo
@@ -2,19 +2,13 @@
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter;
var merge = require('merge-descriptors')
, mixin = require('utils-merge')
var proto = require('./application')
var connect = require('connect')
, proto = require('./application')
, Route = require('./router/route')
, Router = require('./router')
, req = require('./request')
, res = require('./response')
// monkey patch ServerResponse methods
require('./patch')
, utils = connect.utils;
/**
* Expose `createApplication()`.
@@ -22,6 +16,12 @@ require('./patch')
exports = module.exports = createApplication;
/**
* Expose mime.
*/
exports.mime = connect.mime;
/**
* Create an express application.
*
@@ -30,19 +30,41 @@ exports = module.exports = createApplication;
*/
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, proto);
mixin(app, EventEmitter.prototype);
var app = connect();
utils.merge(app, proto);
app.request = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };
app.init();
return app;
}
/**
* Expose connect.middleware as express.*
* for example `express.logger` etc.
*/
for (var key in connect.middleware) {
Object.defineProperty(
exports
, key
, Object.getOwnPropertyDescriptor(connect.middleware, key));
}
/**
* Error on createServer().
*/
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.
*/
@@ -58,8 +80,7 @@ exports.response = res;
exports.Route = Route;
exports.Router = Router;
/**
* Expose middleware
*/
// Error handler title
exports.errorHandler.title = 'Express';
exports.static = require('./middleware/static');
+8 -2
Ver Arquivo
@@ -1,3 +1,10 @@
/**
* Module dependencies.
*/
var utils = require('./utils');
/**
* Initialization middleware, exposing the
* request and response to eachother, as well
@@ -18,9 +25,8 @@ exports.init = function(app){
req.__proto__ = app.request;
res.__proto__ = app.response;
res.locals = res.locals || Object.create(null);
res.locals = res.locals || utils.locals(res);
next();
}
};
-35
Ver Arquivo
@@ -1,35 +0,0 @@
var qs = require('qs');
var parseUrl = require('../utils').parseUrl;
/**
* Query:
*
* Automatically parse the query-string when available,
* populating the `req.query` object using
* [qs](https://github.com/visionmedia/node-querystring).
*
* Examples:
*
* .use(connect.query())
* .use(function(req, res){
* res.end(JSON.stringify(req.query));
* });
*
* The `options` passed are provided to qs.parse function.
*
* @param {Object} options
* @return {Function}
* @api public
*/
module.exports = function query(options){
return function query(req, res, next){
if (!req.query) {
req.query = ~req.url.indexOf('?')
? qs.parse(parseUrl(req).query, options)
: {};
}
next();
};
};
-87
Ver Arquivo
@@ -1,87 +0,0 @@
/*!
* Connect - static
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var send = require('send')
, utils = require('../utils')
, parse = utils.parseUrl
, url = require('url');
/**
* Static:
*
* Static file server with the given `root` path.
*
* Examples:
*
* var oneDay = 86400000;
*
* connect()
* .use(connect.static(__dirname + '/public'))
*
* connect()
* .use(connect.static(__dirname + '/public', { maxAge: oneDay }))
*
* Options:
*
* - `maxAge` Browser cache maxAge in milliseconds. defaults to 0
* - `hidden` Allow transfer of hidden files. defaults to false
* - `redirect` Redirect to trailing "/" when the pathname is a dir. defaults to true
* - `index` Default file name, defaults to 'index.html'
*
* @param {String} root
* @param {Object} options
* @return {Function}
* @api public
*/
exports = module.exports = function(root, options){
options = options || {};
// root required
if (!root) throw new Error('static() root path required');
// default redirect
var redirect = false !== options.redirect;
return function staticMiddleware(req, res, next) {
if ('GET' != req.method && 'HEAD' != req.method) return next();
var originalUrl = url.parse(req.originalUrl);
var path = parse(req).pathname;
if (path == '/' && originalUrl.pathname[originalUrl.pathname.length - 1] != '/') {
return directory();
}
function directory() {
if (!redirect) return next();
var target;
originalUrl.pathname += '/';
target = url.format(originalUrl);
res.statusCode = 303;
res.setHeader('Location', target);
res.end('Redirecting to ' + utils.escape(target));
}
function error(err) {
if (404 == err.status) return next();
next(err);
}
send(req, path)
.maxage(options.maxAge || 0)
.root(root)
.index(options.index || 'index.html')
.hidden(options.hidden)
.on('error', error)
.on('directory', directory)
.pipe(res);
};
};
-54
Ver Arquivo
@@ -1,54 +0,0 @@
/*!
* Connect
* Copyright(c) 2011 TJ Holowaychuk
* MIT Licensed
*/
/**
* Module dependencies.
*/
var http = require('http');
var ServerResponse = http.ServerResponse;
// apply only once
if (ServerResponse.prototype._hasConnectPatch) {
return;
}
// original methods
var setHeader = ServerResponse.prototype.setHeader;
var writeHead = ServerResponse.prototype.writeHead;
/**
* Set header `field` to `val`, special-casing
* the `Set-Cookie` field for multiple support.
*
* @param {String} field
* @param {String} val
* @api public
*/
ServerResponse.prototype.setHeader = function(field, val){
var key = field.toLowerCase();
if ('content-type' == key && this.charset) {
val += '; charset=' + this.charset;
}
return setHeader.call(this, field, val);
};
ServerResponse.prototype.writeHead = function(statusCode, reasonPhrase, headers){
if (typeof reasonPhrase === 'object') headers = reasonPhrase;
if (typeof headers === 'object') {
Object.keys(headers).forEach(function(key){
this.setHeader(key, headers[key]);
}, this);
}
if (!this._emittedHeader) this.emit('header');
this._emittedHeader = true;
return writeHead.call(this, statusCode, reasonPhrase);
};
ServerResponse.prototype._hasConnectPatch = true;
+124 -28
Ver Arquivo
@@ -3,13 +3,13 @@
* Module dependencies.
*/
var accepts = require('accepts');
var typeis = require('type-is');
var http = require('http')
, utils = require('./utils')
, connect = require('connect')
, fresh = require('fresh')
, parseRange = require('range-parser')
, parse = utils.parseUrl
, parse = connect.utils.parseUrl
, mime = connect.mime;
/**
* Request prototype.
@@ -56,8 +56,6 @@ req.header = function(name){
};
/**
* To do: update docs.
*
* 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".
@@ -101,9 +99,9 @@ req.header = function(name){
* @api public
*/
req.accepts = function(){
var accept = accepts(this);
return accept.types.apply(accept, arguments);
req.accepts = function(type){
var args = arguments.length > 1 ? [].slice.apply(arguments) : type;
return utils.accepts(args, this.get('Accept'));
};
/**
@@ -114,15 +112,11 @@ req.accepts = function(){
* @api public
*/
req.acceptsEncoding = // backwards compatibility
req.acceptsEncodings = function(){
var accept = accepts(this);
return accept.encodings.apply(accept, arguments);
req.acceptsEncoding = function(encoding){
return !! ~this.acceptedEncodings.indexOf(encoding);
};
/**
* To do: update docs.
*
* Check if the given `charset` is acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
@@ -131,15 +125,14 @@ req.acceptsEncodings = function(){
* @api public
*/
req.acceptsCharset = // backwards compatibility
req.acceptsCharsets = function(){
var accept = accepts(this);
return accept.charsets.apply(accept, arguments);
req.acceptsCharset = function(charset){
var accepted = this.acceptedCharsets;
return accepted.length
? !! ~accepted.indexOf(charset)
: true;
};
/**
* To do: update docs.
*
* Check if the given `lang` is acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
@@ -148,10 +141,11 @@ req.acceptsCharsets = function(){
* @api public
*/
req.acceptsLanguage = // backwards compatibility
req.acceptsLanguages = function(lang){
var accept = accepts(this);
return accept.languages.apply(accept, arguments);
req.acceptsLanguage = function(lang){
var accepted = this.acceptedLanguages;
return accepted.length
? !! ~accepted.indexOf(lang)
: true;
};
/**
@@ -180,6 +174,98 @@ req.range = function(size){
return parseRange(size, range);
};
/**
* Return an array of encodings.
*
* Examples:
*
* ['gzip', 'deflate']
*
* @return {Array}
* @api public
*/
req.__defineGetter__('acceptedEncodings', function(){
var accept = this.get('Accept-Encoding');
return accept
? accept.trim().split(/ *, */)
: [];
});
/**
* Return an array of Accepted media types
* ordered from highest quality to lowest.
*
* Examples:
*
* [ { value: 'application/json',
* quality: 1,
* type: 'application',
* subtype: 'json' },
* { value: 'text/html',
* quality: 0.5,
* type: 'text',
* subtype: 'html' } ]
*
* @return {Array}
* @api public
*/
req.__defineGetter__('accepted', function(){
var accept = this.get('Accept');
return accept
? utils.parseAccept(accept)
: [];
});
/**
* Return an array of Accepted languages
* ordered from highest quality to lowest.
*
* Examples:
*
* Accept-Language: en;q=.5, en-us
* ['en-us', 'en']
*
* @return {Array}
* @api public
*/
req.__defineGetter__('acceptedLanguages', function(){
var accept = this.get('Accept-Language');
return accept
? utils
.parseParams(accept)
.map(function(obj){
return obj.value;
})
: [];
});
/**
* Return an array of Accepted charsets
* ordered from highest quality to lowest.
*
* Examples:
*
* Accept-Charset: iso-8859-5;q=.2, unicode-1-1;q=0.8
* ['unicode-1-1', 'iso-8859-5']
*
* @return {Array}
* @api public
*/
req.__defineGetter__('acceptedCharsets', function(){
var accept = this.get('Accept-Charset');
return accept
? utils
.parseParams(accept)
.map(function(obj){
return obj.value;
})
: [];
});
/**
* Return the value of param `name` when present or `defaultValue`.
*
@@ -189,7 +275,7 @@ req.range = function(size){
*
* To utilize request bodies, `req.body`
* should be an object. This can be done by using
* the `bodyParser()` middleware.
* the `connect.bodyParser()` middleware.
*
* @param {String} name
* @param {Mixed} [defaultValue]
@@ -233,9 +319,19 @@ req.param = function(name, defaultValue){
* @api public
*/
req.is = function(types){
if (!Array.isArray(types)) types = [].slice.call(arguments);
return typeis(this, types);
req.is = function(type){
var ct = this.get('Content-Type');
if (!ct) return false;
ct = ct.split(';')[0];
if (!~type.indexOf('/')) type = mime.lookup(type);
if (~type.indexOf('*')) {
type = type.split('/');
ct = ct.split('/');
if ('*' == type[0] && type[1] == ct[1]) return true;
if ('*' == type[1] && type[0] == ct[0]) return true;
return false;
}
return !! ~ct.indexOf(type);
};
/**
+91 -32
Ver Arquivo
@@ -4,8 +4,8 @@
var http = require('http')
, path = require('path')
, mixin = require('utils-merge')
, escapeHtml = require('escape-html')
, connect = require('connect')
, utils = connect.utils
, sign = require('cookie-signature').sign
, normalizeType = require('./utils').normalizeType
, normalizeTypes = require('./utils').normalizeTypes
@@ -13,10 +13,10 @@ var http = require('http')
, statusCodes = http.STATUS_CODES
, cookie = require('cookie')
, send = require('send')
, mime = connect.mime
, resolve = require('url').resolve
, basename = path.basename
, extname = path.extname
, mime = send.mime
, extname = path.extname;
/**
* Response prototype.
@@ -311,13 +311,13 @@ res.sendfile = function(path, options, fn){
// clean up
cleanup();
if (!self.headersSent) self.removeHeader('Content-Disposition');
if (!self.headerSent) self.removeHeader('Content-Disposition');
// callback available
if (fn) return fn(err);
// list in limbo if there's no callback
if (self.headersSent) return;
if (self.headerSent) return;
// delegate
next(err);
@@ -352,7 +352,7 @@ res.sendfile = function(path, options, fn){
* 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.headersSent` if you plan to respond.
* ocurred. Be sure to check `res.headerSent` if you plan to respond.
*
* This method uses `res.sendfile()`.
*
@@ -557,7 +557,7 @@ res.get = function(field){
res.clearCookie = function(name, options){
var opts = { expires: new Date(1), path: '/' };
return this.cookie(name, '', options
? mixin(opts, options)
? utils.merge(opts, options)
: opts);
};
@@ -585,10 +585,10 @@ res.clearCookie = function(name, options){
*/
res.cookie = function(name, val, options){
options = mixin({}, options);
options = utils.merge({}, options);
var secret = this.req.secret;
var signed = options.signed;
if (signed && !secret) throw new Error('cookieParser("secret") required for signed cookies');
if (signed && !secret) throw new Error('connect.cookieParser("secret") required for signed cookies');
if ('number' == typeof val) val = val.toString();
if ('object' == typeof val) val = 'j:' + JSON.stringify(val);
if (signed) val = 's:' + sign(val, secret);
@@ -597,18 +597,7 @@ res.cookie = function(name, val, options){
options.maxAge /= 1000;
}
if (null == options.path) options.path = '/';
var headerVal = cookie.serialize(name, String(val), options);
// supports multiple 'res.cookie' calls by getting previous value
var prev = this.get('Set-Cookie');
if (prev) {
if (Array.isArray(prev)) {
headerVal = prev.concat(headerVal);
} else {
headerVal = [prev, headerVal];
}
}
this.set('Set-Cookie', headerVal);
this.set('Set-Cookie', cookie.serialize(name, String(val), options));
return this;
};
@@ -616,24 +605,57 @@ res.cookie = function(name, val, options){
/**
* Set the location header to `url`.
*
* The given `url` can also be "back", which redirects
* 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.location('/foo/bar').;
* res.location('http://example.com');
* res.location('../login');
* res.location('../login'); // /blog/post/1 -> /blog/login
*
* Mounting:
*
* When an application is mounted and `res.location()`
* is given a path that does _not_ lead with "/" it becomes
* relative to the mount-point. For example if the application
* is mounted at "/blog", the following would become "/blog/login".
*
* res.location('login');
*
* While the leading slash would result in a location of "/login":
*
* res.location('/login');
*
* @param {String} url
* @api public
*/
res.location = function(url){
var req = this.req;
var app = this.app
, req = this.req
, path;
// "back" is an alias for the referrer
if ('back' == url) url = req.get('Referrer') || '/';
// setup redirect map
var map = { back: req.get('Referrer') || '/' };
// perform redirect
url = map[url] || url;
// relative
if (!~url.indexOf('://') && 0 != url.indexOf('//')) {
// relative to path
if ('.' == url[0]) {
path = req.originalUrl.split('?')[0];
path = path + ('/' == path[path.length - 1] ? '' : '/');
url = resolve(path, url);
// relative to mount-point
} else if ('/' != url[0]) {
path = app.path();
url = path + '/' + url;
}
}
// Respond
this.set('Location', url);
@@ -687,7 +709,7 @@ res.redirect = function(url){
},
html: function(){
var u = escapeHtml(url);
var u = utils.escape(url);
body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
},
@@ -770,12 +792,49 @@ res.render = function(view, options, fn){
// merge res.locals
options._locals = self.locals;
// default callback to respond
fn = fn || function(err, str){
// support for non streaming view rendering
// if renderer uses streams, this will not be called
// see below for how streams are handled
var respond = fn || function(err, str){
if (view_stream) return;
if (err) return req.next(err);
self.send(str);
};
// render
app.render(view, options, fn);
var view_stream = app.render(view, options, respond);
// old style, will have already called respond for us
if (!view_stream) {
return;
}
// user wants to handle sending themselves
if (fn) {
var body = '';
view_stream.on('data', function(chunk) {
body += chunk;
});
view_stream.once('error', function(err) {
fn(err);
});
view_stream.once('end', function() {
fn(null, body);
});
return;
}
// set response headers
self.statusCode = 200;
if (!self.get('Content-Type')) {
self.charset = self.charset || 'utf-8';
self.type('html');
}
self.setHeader('Transfer-Encoding', 'chunked');
// start streaming the response
view_stream.pipe(self);
};
+226 -315
Ver Arquivo
@@ -3,74 +3,47 @@
*/
var Route = require('./route')
, Layer = require('./layer')
, utils = require('../utils')
, methods = require('methods')
, debug = require('debug')('express:router')
, parseUrl = utils.parseUrl;
, parse = require('connect').utils.parseUrl;
/**
* Expose `Router` constructor.
*/
exports = module.exports = Router;
/**
* Initialize a new `Router` with the given `options`.
*
* @param {Object} options
* @return {Router} which is an callable function
* @api public
* @api private
*/
var proto = module.exports = function(options) {
function Router(options) {
options = options || {};
function router(req, res, next) {
router.handle(req, res, next);
var self = this;
this.map = {};
this.params = {};
this._params = [];
this.caseSensitive = options.caseSensitive;
this.strict = options.strict;
this.middleware = function router(req, res, next){
self._dispatch(req, res, next);
};
// mixin Router class functions
router.__proto__ = proto;
router.params = {};
router._params = [];
router.caseSensitive = options.caseSensitive;
router.strict = options.strict;
router.stack = [];
return router;
};
}
/**
* Map the given param placeholder `name`(s) to the given callback.
* Register a param callback `fn` for the given `name`.
*
* 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 same signature as middleware, the only difference
* 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.
*
* Just like in middleware, you must either respond to the request or call next
* to avoid stalling the request.
*
* app.param('user_id', function(req, res, next, id){
* User.find(id, function(err, user){
* if (err) {
* return next(err);
* } else if (!user) {
* return next(new Error('failed to load user'));
* }
* req.user = user;
* next();
* });
* });
*
* @param {String} name
* @param {String|Function} name
* @param {Function} fn
* @return {app} for chaining
* @return {Router} for chaining
* @api public
*/
proto.param = function(name, fn){
Router.prototype.param = function(name, fn){
// param logic
if ('function' == typeof name) {
this._params.push(name);
@@ -82,10 +55,6 @@ proto.param = function(name, fn){
, len = params.length
, ret;
if (name[0] === ':') {
name = name.substr(1);
}
for (var i = 0; i < len; ++i) {
if (ret = params[i](name, fn)) {
fn = ret;
@@ -103,298 +72,240 @@ proto.param = function(name, fn){
};
/**
* Dispatch a req, res into the router.
* Route dispatcher aka the route "middleware".
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Function} next
* @api private
*/
proto.handle = function(req, res, done) {
var self = this;
Router.prototype._dispatch = function(req, res, next){
var params = this.params
, self = this;
debug('dispatching %s %s', req.method, req.url);
debug('dispatching %s %s (%s)', req.method, req.url, req.originalUrl);
var method = req.method.toLowerCase();
// route dispatch
(function pass(i, err){
var paramCallbacks
, paramIndex = 0
, paramVal
, route
, keys
, key;
var search = 1 + req.url.indexOf('?');
var pathlength = search ? search - 1 : req.url.length;
var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://');
var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
var idx = 0;
var removed = '';
var slashAdded = false;
// store options for OPTIONS request
// only used if OPTIONS request
var options = [];
// middleware and routes
var stack = self.stack;
// for options requests, respond with a default if nothing else responds
if (method === 'options') {
var old = done;
done = function(err) {
if (err || options.length === 0) return old(err);
var body = options.join(',');
return res.set('Allow', body).send(body);
};
}
(function next(err) {
if (err === 'route') {
err = undefined;
// match next route
function nextRoute(err) {
pass(req._route_index + 1, err);
}
var layer = stack[idx++];
if (!layer) {
return done(err);
}
// match route
req.route = route = self.matchRequest(req, i);
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
// implied OPTIONS
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
req.url = protohost + removed + req.url.substr(protohost.length);
req.originalUrl = req.originalUrl || req.url;
removed = '';
// no route
if (!route) return next(err);
debug('matched %s %s', route.method, route.path);
try {
var path = parseUrl(req).pathname;
if (undefined == path) path = '/';
// we have a route
// start at param 0
req.params = route.params;
keys = route.keys;
i = 0;
if (!layer.match(path)) return next(err);
// param callbacks
function param(err) {
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
// route object and not middleware
var route = layer.route;
// if final route, then we support options
if (route) {
// we don't run any routs with error first
if (err) {
return next(err);
}
req.route = route;
// we can now dispatch to the route
if (method === 'options' && !route.methods['options']) {
options.push.apply(options, route._options());
}
}
req.params = layer.params;
// this should be done for the layer
return self.process_params(layer, req, res, function(err) {
if (err) {
return next(err);
}
if (route) {
return layer.handle(req, res, next);
}
trim_prefix();
});
return next(err);
function trim_prefix() {
var c = path[layer.path.length];
if (c && '/' != c && '.' != c) return next(err);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', removed, req.url);
removed = layer.path;
req.url = protohost + req.url.substr(protohost.length + removed.length);
// Ensure leading slash
if (!fqdn && '/' != req.url[0]) {
req.url = '/' + req.url;
slashAdded = true;
}
debug('%s %s : %s', layer.handle.name || 'anonymous', layer.path, req.originalUrl);
var arity = layer.handle.length;
if (err) {
if (arity === 4) {
layer.handle(err, req, res, next);
} else {
next(err);
}
} else if (arity < 4) {
layer.handle(req, res, next);
try {
if ('route' == err) {
nextRoute();
} else if (err) {
i = 0;
callbacks(err);
} else if (paramCallbacks && undefined !== paramVal) {
paramCallback();
} else if (key) {
param();
} else {
next(err);
i = 0;
callbacks();
}
} catch (err) {
param(err);
}
} catch (err) {
next(err);
};
param(err);
// single param callbacks
function paramCallback(err) {
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
}
})();
// invoke route callbacks
function callbacks(err) {
var fn = route.callbacks[i++];
try {
if ('route' == err) {
nextRoute();
} else if (err && fn) {
if (fn.length < 4) return callbacks(err);
fn(err, req, res, callbacks);
} else if (fn) {
if (fn.length < 4) return fn(req, res, callbacks);
callbacks();
} else {
nextRoute(err);
}
} catch (err) {
callbacks(err);
}
}
})(0);
};
/**
* Process any parameters for the route.
* Respond to __OPTIONS__ method.
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @api private
*/
proto.process_params = function(route, req, res, done) {
Router.prototype._options = function(req, res){
var path = parse(req).pathname
, body = this._optionsFor(path).join(',');
res.set('Allow', body).send(body);
};
/**
* Return an array of HTTP verbs or "options" for `path`.
*
* @param {String} path
* @return {Array}
* @api private
*/
Router.prototype._optionsFor = function(path){
var self = this;
var params = this.params;
// captured parameters from the route, keys and values
var keys = route.keys || [];
// fast track
if (keys.length === 0) {
return done();
}
var i = 0;
var paramIndex = 0;
var key;
var paramVal;
var paramCallbacks;
// process params in order
// param callbacks can be async
function param(err) {
if (err) {
return done(err);
return methods.filter(function(method){
var routes = self.map[method];
if (!routes || 'options' == method) return;
for (var i = 0, len = routes.length; i < len; ++i) {
if (routes[i].match(path)) return true;
}
if (i >= keys.length ) {
return done();
}
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
try {
if (paramCallbacks && undefined !== paramVal) {
return paramCallback();
} else if (key) {
return param();
}
} catch (err) {
done(err);
}
done();
};
// single param callbacks
function paramCallback(err) {
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
}
param();
};
/**
* Use the given middleware function, with optional path, defaulting to "/".
*
* Use (like `.all`) will run for any http METHOD, but it will not add
* handlers for those methods so OPTIONS requests will not consider `.use`
* functions even if they could respond.
*
* The other difference is that _route_ path is stripped and not visible
* to the handler function. The main effect of this feature is that mounted
* handlers can operate without any code changes regardless of the "prefix"
* pathname.
*
* @param {String|Function} route
* @param {Function} fn
* @return {app} for chaining
* @api public
*/
proto.use = function(route, fn){
// default route to '/'
if ('string' != typeof route) {
fn = route;
route = '/';
}
// strip trailing slash
if ('/' == route[route.length - 1]) {
route = route.slice(0, -1);
}
var layer = Layer(route, {
sensitive: this.caseSensitive,
strict: this.strict,
end: false
}, fn);
// add the middleware
debug('use %s %s', route || '/', fn.name || 'anonymous');
this.stack.push(layer);
return this;
};
/**
* Create a new Route for the given path.
*
* Each route contains a separate middleware stack and VERB handlers.
*
* See the Route api documentation for details on adding handlers
* and middleware to routes.
*
* @param {String} path
* @return {Route}
* @api public
*/
proto.route = function(path){
var route = new Route(path);
var layer = Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
return route;
};
/**
* Special-cased "all" method, applying the given route `path`,
* middleware, and callback to _every_ HTTP method.
*
* @param {String} path
* @param {Function} ...
* @return {app} for chaining
* @api public
*/
proto.all = function(path, fn) {
var route = this.route(path);
methods.forEach(function(method){
route[method](fn);
}).map(function(method){
return method.toUpperCase();
});
};
// create Router#VERB functions
/**
* Attempt to match a route for `req`
* with optional starting index of `i`
* defaulting to 0.
*
* @param {IncomingMessage} req
* @param {Number} i
* @return {Route}
* @api private
*/
Router.prototype.matchRequest = function(req, i, head){
var method = req.method.toLowerCase()
, url = parse(req)
, path = url.pathname
, routes = this.map
, i = i || 0
, route;
// HEAD support
if (!head && 'head' == method) {
route = this.matchRequest(req, i, true);
if (route) return route;
method = 'get';
}
// routes for this method
if (routes = routes[method]) {
// matching routes
for (var len = routes.length; i < len; ++i) {
route = routes[i];
if (route.match(path)) {
req._route_index = i;
return route;
}
}
}
};
/**
* 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.
*
* @param {String} method
* @param {String} path
* @param {Function} callback...
* @return {Router} for chaining
* @api private
*/
Router.prototype.route = function(method, path, callbacks){
var method = method.toLowerCase()
, callbacks = utils.flatten([].slice.call(arguments, 2));
// ensure path was given
if (!path) throw new Error('Router#' + method + '() requires a path');
// ensure all callbacks are functions
callbacks.forEach(function(fn){
if ('function' == typeof fn) return;
var type = {}.toString.call(fn);
var msg = '.' + method + '() requires callback functions but got a ' + type;
throw new Error(msg);
});
// create the route
debug('defined %s %s', method, path);
var route = new Route(method, path, callbacks, {
sensitive: this.caseSensitive,
strict: this.strict
});
// add it
(this.map[method] = this.map[method] || []).push(route);
return this;
};
methods.forEach(function(method){
proto[method] = function(path, fn){
var self = this;
self.route(path)[method](fn);
return self;
Router.prototype[method] = function(path){
var args = [method].concat([].slice.call(arguments));
this.route.apply(this, args);
return this;
};
});
-61
Ver Arquivo
@@ -1,61 +0,0 @@
var utils = require('../utils')
, debug = require('debug')('express:router:layer')
function Layer(path, options, fn) {
if (!(this instanceof Layer)) {
return new Layer(path, options, fn);
}
debug('new %s', path);
options = options || {};
this.path = path;
this.params = {};
this.regexp = utils.pathRegexp(path
, this.keys = []
, options.sensitive
, options.strict
, options.end);
this.handle = fn;
}
/**
* Check if this route matches `path`, if so
* populate `.params`.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
Layer.prototype.match = function(path){
var keys = this.keys
, params = this.params = {}
, m = this.regexp.exec(path)
, n = 0;
if (!m) return false;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
try {
var val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
} catch(e) {
var err = new Error("Failed to decode param '" + m[i] + "'");
err.status = 400;
throw err;
}
if (key) {
params[key.name] = val;
} else {
params[n++] = val;
}
}
return true;
};
module.exports = Layer;
+46 -138
Ver Arquivo
@@ -3,8 +3,7 @@
* Module dependencies.
*/
var debug = require('debug')('express:router:route')
, methods = require('methods')
var utils = require('../utils');
/**
* Expose `Route`.
@@ -13,152 +12,61 @@ var debug = require('debug')('express:router:route')
module.exports = Route;
/**
* Initialize `Route` with the given `path`,
* Initialize `Route` with the given HTTP `method`, `path`,
* and an array of `callbacks` and `options`.
*
* Options:
*
* - `sensitive` enable case-sensitive routes
* - `strict` enable strict matching for trailing slashes
*
* @param {String} method
* @param {String} path
* @param {Array} callbacks
* @param {Object} options.
* @api private
*/
function Route(path) {
debug('new %s', path);
function Route(method, path, callbacks, options) {
options = options || {};
this.path = path;
this.stack = undefined;
// route handlers for various http methods
this.methods = {};
this.method = method;
this.callbacks = callbacks;
this.regexp = utils.pathRegexp(path
, this.keys = []
, options.sensitive
, options.strict);
}
/**
* @return {Array} supported HTTP methods
* Check if this route matches `path`, if so
* populate `.params`.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
Route.prototype._options = function(){
return Object.keys(this.methods).map(function(method) {
return method.toUpperCase();
});
Route.prototype.match = function(path){
var keys = this.keys
, params = this.params = []
, m = this.regexp.exec(path);
if (!m) return false;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
var val = 'string' == typeof m[i]
? utils.decode(m[i])
: m[i];
if (key) {
params[key.name] = val;
} else {
params.push(val);
}
}
return true;
};
/**
* dispatch req, res into this route
*
* @api private
*/
Route.prototype.dispatch = function(req, res, done){
var self = this;
var method = req.method.toLowerCase();
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
req.route = self;
// single middleware route case
if (typeof this.stack === 'function') {
this.stack(req, res, done);
return;
}
var stack = self.stack;
if (!stack) {
return done();
}
var idx = 0;
(function next_layer(err) {
if (err && err === 'route') {
return done();
}
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {
return next_layer(err);
}
var arity = layer.handle.length;
if (err) {
if (arity < 4) {
return next_layer(err);
}
return layer.handle(err, req, res, next_layer);
}
if (arity > 3) {
return next_layer();
}
layer.handle(req, res, next_layer);
})();
};
/**
* Add a handler for all HTTP verbs to this route.
*
* Behaves just like middleware and can respond or call `next`
* to continue processing.
*
* You can use multiple `.all` call to add multiple handlers.
*
* function check_something(req, res, next){
* next();
* };
*
* function validate_user(req, res, next){
* next();
* };
*
* route
* .all(validate_user)
* .all(check_something)
* .get(function(req, res, next){
* res.send('hello world');
* });
*
* @param {function} handler
* @return {Route} for chaining
* @api public
*/
Route.prototype.all = function(fn){
if (typeof fn !== 'function') {
var type = {}.toString.call(fn);
var msg = 'Route.use() requires callback functions but got a ' + type;
throw new Error(msg);
}
if (!this.stack) {
this.stack = fn;
}
else if (typeof this.stack === 'function') {
this.stack = [{ handle: this.stack }, { handle: fn }];
}
else {
this.stack.push({ handle: fn });
}
return this;
};
methods.forEach(function(method){
Route.prototype[method] = function(fn){
debug('%s %s', method, this.path);
if (!this.methods[method]) {
this.methods[method] = true;
}
if (!this.stack) {
this.stack = [];
}
this.stack.push({ method: method, handle: fn })
return this;
};
});
+172 -23
Ver Arquivo
@@ -3,9 +3,8 @@
* Module dependencies.
*/
var mime = require('send').mime;
var crc32 = require('buffer-crc32');
var parse = require('url').parse;
var mime = require('connect').mime
, crc32 = require('buffer-crc32');
/**
* toString ref.
@@ -25,6 +24,25 @@ exports.etag = function(body){
return '"' + crc32.signed(body) + '"';
};
/**
* Make `locals()` bound to the given `obj`.
*
* This is used for `app.locals` and `res.locals`.
*
* @param {Object} obj
* @return {Function}
* @api private
*/
exports.locals = function(){
function locals(obj){
for (var key in obj) locals[key] = obj[key];
return obj;
};
return locals;
};
/**
* Check if `path` looks absolute.
*
@@ -92,6 +110,127 @@ exports.normalizeTypes = function(types){
return ret;
};
/**
* Return the acceptable type in `types`, if any.
*
* @param {Array} types
* @param {String} str
* @return {String}
* @api private
*/
exports.acceptsArray = function(types, str){
// accept anything when Accept is not present
if (!str) return types[0];
// parse
var accepted = exports.parseAccept(str)
, normalized = exports.normalizeTypes(types)
, len = accepted.length;
for (var i = 0; i < len; ++i) {
for (var j = 0, jlen = types.length; j < jlen; ++j) {
if (exports.accept(normalized[j], accepted[i])) {
return types[j];
}
}
}
};
/**
* Check if `type(s)` are acceptable based on
* the given `str`.
*
* @param {String|Array} type(s)
* @param {String} str
* @return {Boolean|String}
* @api private
*/
exports.accepts = function(type, str){
if ('string' == typeof type) type = type.split(/ *, */);
return exports.acceptsArray(type, str);
};
/**
* Check if `type` array is acceptable for `other`.
*
* @param {Object} type
* @param {Object} other
* @return {Boolean}
* @api private
*/
exports.accept = function(type, other){
var t = type.value.split('/');
return (t[0] == other.type || '*' == other.type)
&& (t[1] == other.subtype || '*' == other.subtype)
&& paramsEqual(type.params, other.params);
};
/**
* Check if accept params are equal.
*
* @param {Object} a
* @param {Object} b
* @return {Boolean}
* @api private
*/
function paramsEqual(a, b){
return !Object.keys(a).some(function(k) {
return a[k] != b[k];
});
}
/**
* Parse accept `str`, returning
* an array objects containing
* `.type` and `.subtype` along
* with the values provided by
* `parseQuality()`.
*
* @param {Type} name
* @return {Type}
* @api private
*/
exports.parseAccept = function(str){
return exports
.parseParams(str)
.map(function(obj){
var parts = obj.value.split('/');
obj.type = parts[0];
obj.subtype = parts[1];
return obj;
});
};
/**
* Parse quality `str`, returning an
* array of objects with `.value`,
* `.quality` and optional `.params`
*
* @param {String} str
* @return {Array}
* @api private
*/
exports.parseParams = function(str){
return str
.split(/ *, */)
.map(acceptParams)
.filter(function(obj){
return obj.quality;
})
.sort(function(a, b){
if (a.quality === b.quality) {
return a.originalIndex - b.originalIndex;
} else {
return b.quality - a.quality;
}
});
};
/**
* Parse accept params `str` returning an
@@ -119,6 +258,22 @@ function acceptParams(str, index) {
return ret;
}
/**
* Escape special characters in the given string of html.
*
* @param {String} html
* @return {String}
* @api private
*/
exports.escape = function(html) {
return String(html)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
};
/**
* Normalize the given path string,
* returning a regular expression.
@@ -132,12 +287,11 @@ function acceptParams(str, index) {
* @param {Array} keys
* @param {Boolean} sensitive
* @param {Boolean} strict
* @param {Boolean} end (whether to append $ to regex)
* @return {RegExp}
* @api private
*/
exports.pathRegexp = function(path, keys, sensitive, strict, end) {
exports.pathRegexp = function(path, keys, sensitive, strict) {
if (toString.call(path) == '[object RegExp]') return path;
if (Array.isArray(path)) path = '(' + path.join('|') + ')';
path = path
@@ -156,29 +310,24 @@ exports.pathRegexp = function(path, keys, sensitive, strict, end) {
})
.replace(/([\/.])/g, '\\$1')
.replace(/\*/g, '(.*)');
return new RegExp('^' + path + ((end) ? '$' : ''), sensitive ? '' : 'i');
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}
/**
* Parse the `req` url with memoization.
* Decodes a URI component. Returns
* the original string if the component
* is malformed.
*
* @param {ServerRequest} req
* @return {Object}
* @param {String} str
* @return {String}
* @api private
*/
exports.parseUrl = function(req){
var parsed = req._parsedUrl;
if (parsed && parsed.href == req.url) {
return parsed;
} else {
parsed = parse(req.url);
if (parsed.auth && !parsed.protocol && ~parsed.href.indexOf('//')) {
// This parses pathnames, and a strange pathname like //r@e should work
parsed = parse(req.url.replace(/@/g, '%40'));
}
return req._parsedUrl = parsed;
exports.decode = function(str) {
try {
return decodeURIComponent(str);
} catch (e) {
return str;
}
};
}
+1 -1
Ver Arquivo
@@ -73,5 +73,5 @@ View.prototype.lookup = function(path){
*/
View.prototype.render = function(options, fn){
this.engine(this.path, options, fn);
return this.engine(this.path, options, fn);
};
+22 -27
Ver Arquivo
@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "3.4.7",
"version": "3.4.4",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{
@@ -22,36 +22,28 @@
}
],
"dependencies": {
"accepts": "1.0.0",
"type-is": "1.0.0",
"range-parser": "1.0.0",
"connect": "2.11.0",
"commander": "1.3.2",
"range-parser": "0.0.4",
"mkdirp": "0.3.5",
"cookie": "0.1.0",
"buffer-crc32": "0.2.1",
"fresh": "0.2.2",
"fresh": "0.2.0",
"methods": "0.1.0",
"send": "0.2.0",
"cookie-signature": "1.0.3",
"merge-descriptors": "0.0.2",
"utils-merge": "1.0.0",
"escape-html": "1.0.1",
"qs": "0.6.6",
"send": "0.1.4",
"cookie-signature": "1.0.1",
"debug": ">= 0.7.3 < 1"
},
"devDependencies": {
"ejs": "~0.8.4",
"mocha": "~1.15.1",
"jade": "~0.30.0",
"hjs": "~0.0.6",
"stylus": "~0.40.0",
"should": "~2.1.1",
"connect-redis": "~1.4.5",
"marked": "0.2.10",
"supertest": "~0.8.1",
"body-parser": "1.0.0",
"cookie-parser": "1.0.1",
"static-favicon": "1.0.0",
"express-session": "1.0.1",
"morgan": "1.0.0"
"ejs": ">= 0.8.4 < 1",
"mocha": ">= 1.13.0 < 2",
"jade": "0.30.0",
"hjs": ">= 0.0.6 < 1",
"stylus": ">= 0.39.1 < 1",
"should": ">= 2.0.2 < 3",
"connect-redis": ">= 1.4.5 < 2",
"marked": ">= 0.2.9 < 1",
"supertest": ">= 0.8.1 < 1"
},
"keywords": [
"express",
@@ -65,12 +57,15 @@
"api"
],
"repository": "git://github.com/visionmedia/express",
"main": "index",
"bin": {
"express": "./bin/express"
},
"scripts": {
"prepublish": "npm prune",
"test": "make test"
},
"engines": {
"node": ">= 0.8.0"
},
"license": "MIT"
}
}
+1
Ver Arquivo
@@ -9,6 +9,7 @@ var app = express()
, blog = express()
, admin = express();
// app.use(express.logger('dev'))
blog.use('/admin', admin);
app.use('/blog', blog);
app.set('views', __dirname + '/views');
Arquivo executável
+32
Ver Arquivo
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
NODE_ENV=production node ./support/app &
pid=$!
bench() {
ab -n 5000 -c 50 -k -q http://127.0.0.1:8000$1 \
| grep "Requests per" \
| cut -d ' ' -f 7 \
| xargs echo "$2:"
}
bench_conditional() {
ab -n 5000 -c 50 -H "If-None-Match: $3" -k -q http://127.0.0.1:8000$1 \
| grep "Requests per" \
| cut -d ' ' -f 7 \
| xargs echo "$2:"
}
sleep .5
bench / "Hello World"
bench /blog "Mounted Hello World"
bench /blog/admin "Mounted 2 Hello World"
bench /middleware "Middleware"
bench /match "Router"
bench /render "Render"
bench /json "JSON tiny"
bench /json/15 "JSON small"
bench /json/50 "JSON medium"
bench /json/150 "JSON large"
kill -9 $pid
-144
Ver Arquivo
@@ -1,144 +0,0 @@
var express = require('../')
, Route = express.Route
, methods = require('methods')
, assert = require('assert');
describe('Route', function(){
describe('.all', function(){
it('should add handler', function(done){
var route = new Route('/foo');
route.all(function(req, res, next) {
assert.equal(req.a, 1);
assert.equal(res.b, 2);
next();
});
route.dispatch({ a:1, method: 'GET' }, { b:2 }, done);
})
it('should handle VERBS', function(done) {
var route = new Route('/foo');
var count = 0;
route.all(function(req, res, next) {
count++;
});
methods.forEach(function testMethod(method) {
route.dispatch({ method: method }, {});
});
assert.equal(count, methods.length);
done();
})
it('should stack', function(done) {
var route = new Route('/foo');
var count = 0;
route.all(function(req, res, next) {
count++;
next();
});
route.all(function(req, res, next) {
count++;
next();
});
route.dispatch({ method: 'GET' }, {}, function(err) {
assert.ifError(err);
count++;
});
assert.equal(count, 3);
done();
})
})
describe('.VERB', function(){
it('should support .get', function(done){
var route = new Route('');
var count = 0;
route.get(function(req, res, next) {
count++;
})
route.dispatch({ method: 'GET' }, {});
assert(count);
done();
})
it('should limit to just .VERB', function(done){
var route = new Route('');
route.get(function(req, res, next) {
assert(false);
done();
})
route.post(function(req, res, next) {
assert(true);
})
route.dispatch({ method: 'post' }, {});
done();
})
it('should allow fallthrough', function(done){
var route = new Route('');
var order = '';
route.get(function(req, res, next) {
order += 'a';
next();
})
route.all(function(req, res, next) {
order += 'b';
next();
});
route.get(function(req, res, next) {
order += 'c';
})
route.dispatch({ method: 'get' }, {});
assert.equal(order, 'abc');
done();
})
})
describe('errors', function(){
it('should handle errors via arity 4 functions', function(done){
var route = new Route('');
var order = '';
route.all(function(req, res, next){
next(new Error('foobar'));
});
route.all(function(req, res, next){
order += '0';
next();
});
route.all(function(err, req, res, next){
order += 'a';
next(err);
});
route.all(function(err, req, res, next){
assert.equal(err.message, 'foobar');
assert.equal(order, 'a');
done();
});
route.dispatch({ method: 'get' }, {});
})
})
})
+63 -116
Ver Arquivo
@@ -1,156 +1,103 @@
var express = require('../')
, Router = express.Router
, methods = require('methods')
, request = require('./support/http')
, assert = require('assert');
describe('Router', function(){
var router, app;
it('should return a function with router methods', function() {
var router = Router();
assert(typeof router == 'function');
beforeEach(function(){
router = new Router;
app = express();
})
var router = new Router();
assert(typeof router == '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(){});
assert(typeof router.get == 'function');
assert(typeof router.handle == 'function');
assert(typeof router.use == 'function');
});
var method = 'GET';
var url = '/foo?bar=baz';
it('should support .use of other routers', function(done) {
var router = Router();
var another = Router();
var route = router.match(method, url, 0);
route.constructor.name.should.equal('Route');
route.method.should.equal('get');
route.path.should.equal('/foo');
another.get('/bar', function(req, res) {
res.done();
});
router.use('/foo', another);
var route = router.match(method, url, 1);
route.path.should.equal('/foob?');
router.handle({ url: '/foo/bar', method: 'GET' }, { done: done });
});
var route = router.match(method, url, 2);
assert(!route);
describe('.handle', function(){
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.matchRequest(req, 0);
route.constructor.name.should.equal('Route');
route.method.should.equal('get');
route.path.should.equal('/foo');
var route = router.matchRequest(req, 1);
req._route_index.should.equal(1);
route.path.should.equal('/foob?');
var route = router.matchRequest(req, 2);
assert(!route);
req.url = '/bar';
var route = router.matchRequest(req);
route.path.should.equal('/bar');
})
})
describe('.middleware', function(){
it('should dispatch', function(done){
var router = new Router();
router.route('/foo').get(function(req, res){
router.route('get', '/foo', function(req, res){
res.send('foo');
});
var res = {
send: function(val) {
val.should.equal('foo');
done();
}
}
router.handle({ url: '/foo', method: 'GET' }, res);
app.use(router.middleware);
request(app)
.get('/foo')
.expect('foo', done);
})
})
describe('.multiple callbacks', function(){
it('should throw if a callback is null', function(){
assert.throws(function () {
var router = new Router();
router.route('/foo').all(null);
router.route('get', '/foo', null, function(){});
})
})
it('should throw if a callback is undefined', function(){
assert.throws(function () {
var router = new Router();
router.route('/foo').all(undefined);
router.route('get', '/foo', undefined, function(){});
})
})
it('should throw if a callback is not a function', function(){
assert.throws(function () {
var router = new Router();
router.route('/foo').all('not a function');
router.route('get', '/foo', 'not a function', function(){});
})
})
it('should not throw if all callbacks are functions', function(){
var router = new Router();
router.route('/foo').all(function(){}).all(function(){});
router.route('get', '/foo', function(){}, function(){});
})
})
describe('error', function(){
it('should skip non error middleware', function(done){
var router = new Router();
router.get('/foo', function(req, res, next){
next(new Error('foo'));
});
router.get('/bar', function(req, res, next){
next(new Error('bar'));
});
router.use(function(req, res, next){
assert(false);
});
router.use(function(err, req, res, next){
assert.equal(err.message, 'foo');
done();
});
router.handle({ url: '/foo', method: 'GET' }, {}, done);
});
})
describe('.all', function() {
it('should support using .all to capture all http verbs', function(done){
var router = new Router();
var count = 0;
router.all('/foo', function(){ count++; });
var url = '/foo?bar=baz';
methods.forEach(function testMethod(method) {
router.handle({ url: url, method: method }, {}, function() {});
});
assert.equal(count, methods.length);
done();
})
})
describe('.param', function() {
it('should call param function when routing VERBS', function(done) {
var router = new Router();
router.param('id', function(req, res, next, id) {
assert.equal(id, '123');
next();
});
router.get('/foo/:id/bar', function(req, res, next) {
assert.equal(req.params.id, '123');
next();
});
router.handle({ url: '/foo/123/bar', method: 'get' }, {}, done);
});
it('should call param function when routing middleware', function(done) {
var router = new Router();
router.param('id', function(req, res, next, id) {
assert.equal(id, '123');
next();
});
router.use('/foo/:id/bar', function(req, res, next) {
assert.equal(req.params.id, '123');
assert.equal(req.url, '/baz');
next();
});
router.handle({ url: '/foo/123/bar/baz', method: 'get' }, {}, done);
});
});
})
+3 -3
Ver Arquivo
@@ -18,7 +18,7 @@ describe('auth', function(){
it('should redirect to /login', function(done){
request(app)
.get('/')
.end(redirects(/login$/, done))
.end(redirects(/\/login$/, done))
})
})
@@ -26,7 +26,7 @@ describe('auth', function(){
it('should redirect to /login', function(done){
request(app)
.get('/restricted')
.end(redirects(/login$/,done))
.end(redirects(/\/login$/,done))
})
})
@@ -36,7 +36,7 @@ describe('auth', function(){
.post('/login')
.type('urlencoded')
.send('username=not-tj&password=foobar')
.end(redirects(/login$/, done))
.end(redirects(/\/login$/, done))
})
})
})
+1 -1
Ver Arquivo
@@ -7,7 +7,7 @@ describe('markdown', function(){
it('should respond with html', function(done){
request(app)
.get('/')
.expect(/<h1[^>]*>Markdown Example<\/h1>/,done)
.expect(/<h1>Markdown Example<\/h1>/,done)
})
})
+42
Ver Arquivo
@@ -1,5 +1,6 @@
var express = require('../')
, request = require('./support/http')
, fs = require('fs');
function render(path, options, fn) {
@@ -10,6 +11,10 @@ function render(path, options, fn) {
});
}
function streaming_render(path, options, fn) {
return fs.createReadStream(path, { encoding: 'utf-8' });
}
describe('app', function(){
describe('.engine(ext, fn)', function(){
it('should map a template engine', function(done){
@@ -76,5 +81,42 @@ describe('app', function(){
done();
})
})
it('should support streaming engines', function(done) {
var app = express();
app.set('views', __dirname + '/fixtures');
app.engine('.html', streaming_render);
app.set('view engine', '.html');
app.locals.user = { name: 'tobi' };
// using function should just call the function when done
app.render('user', function(err, str){
if (err) return done(err);
str.should.equal('<p>{{user.name}}</p>');
done();
})
});
it('should render a response using the streaming engine', function(done) {
var app = express();
app.set('views', __dirname + '/fixtures');
app.engine('.html', streaming_render);
app.set('view engine', '.html');
app.locals.user = { name: 'tobi' };
app.get('/', function(req, res) {
res.render('user');
});
request(app)
.get('/')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
res.header['transfer-encoding'].should.equal('chunked');
res.text.should.equal('<p>{{user.name}}</p>');
done();
});
});
})
})
+5 -17
Ver Arquivo
@@ -25,7 +25,7 @@ describe('app.parent', function(){
})
})
describe('app.mountpath', function(){
describe('app.route', function(){
it('should return the mounted path', function(){
var app = express()
, blog = express()
@@ -34,21 +34,9 @@ describe('app.mountpath', function(){
app.use('/blog', blog);
blog.use('/admin', blogAdmin);
app.mountpath.should.equal('/');
blog.mountpath.should.equal('/blog');
blogAdmin.mountpath.should.equal('/admin');
})
})
describe('app.router', function(){
it('should throw with notice', function(done){
var app = express()
try {
app.router;
} catch(err) {
done();
}
app.route.should.equal('/');
blog.route.should.equal('/blog');
blogAdmin.route.should.equal('/admin');
})
})
@@ -83,4 +71,4 @@ describe('in production', function(){
app.enabled('view cache').should.be.true;
process.env.NODE_ENV = 'test';
})
})
})
+2 -2
Ver Arquivo
@@ -7,8 +7,8 @@ describe('app', function(){
it('should merge locals', function(){
var app = express();
Object.keys(app.locals).should.eql(['settings']);
app.locals.user = 'tobi';
app.locals.age = 2;
app.locals({ user: 'tobi', age: 1 });
app.locals({ age: 2 });
Object.keys(app.locals).should.eql(['settings', 'user', 'age']);
app.locals.user.should.equal('tobi');
app.locals.age.should.equal(2);
+1 -25
Ver Arquivo
@@ -15,30 +15,6 @@ describe('OPTIONS', function(){
.expect('GET,PUT')
.expect('Allow', 'GET,PUT', done);
})
it('should not respond if the path is not defined', function(done){
var app = express();
app.get('/users', function(req, res){});
request(app)
.options('/other')
.expect(404, done);
})
it('should forward requests down the middleware chain', function(done){
var app = express();
var router = new express.Router();
router.get('/users', function(req, res){});
app.use(router);
app.get('/other', function(req, res){});
request(app)
.options('/other')
.expect('GET')
.expect('Allow', 'GET', done);
})
})
describe('app.options()', function(){
@@ -58,4 +34,4 @@ describe('app.options()', function(){
.expect('GET')
.expect('Allow', 'GET', done);
})
})
})
+1 -1
Ver Arquivo
@@ -59,7 +59,7 @@ describe('app', function(){
var app = express();
app.set('views', __dirname + '/fixtures');
app.render('rawr.jade', function(err){
err.message.should.equal('Failed to lookup view "rawr.jade" in views directory "' + __dirname + '/fixtures"');
err.message.should.equal('Failed to lookup view "rawr.jade"');
done();
});
})
-20
Ver Arquivo
@@ -1,20 +0,0 @@
var express = require('../')
, request = require('./support/http')
describe('app.route', function(){
it('should return a new route', function(done){
var app = express();
app.route('/foo')
.get(function(req, res) {
res.send('get');
})
.post(function(req, res) {
res.send('post');
});
request(app)
.post('/foo')
.expect('post', done);
});
});
+51 -49
Ver Arquivo
@@ -7,8 +7,6 @@ var express = require('../')
describe('app.router', function(){
describe('methods supported', function(){
methods.forEach(function(method){
if (method === 'connect') return;
it('should include ' + method.toUpperCase(), function(done){
if (method == 'delete') method = 'del';
var app = express();
@@ -29,54 +27,28 @@ describe('app.router', function(){
});
})
describe('decode querystring', function(){
it('should decode correct params', function(done){
var app = express();
it('should decode params', function(done){
var app = express();
app.get('/:name', function(req, res, next){
res.send(req.params.name);
});
app.get('/:name', function(req, res, next){
res.send(req.params.name);
});
request(app)
.get('/foo%2Fbar')
.expect('foo/bar', done);
})
request(app)
.get('/foo%2Fbar')
.expect('foo/bar', done);
})
it('should not accept params in malformed paths', function(done) {
var app = express();
it('should accept params in malformed paths', function(done) {
var app = express();
app.get('/:name', function(req, res, next){
res.send(req.params.name);
});
app.get('/:name', function(req, res, next){
res.send(req.params.name);
});
request(app)
.get('/%foobar')
.expect(400, done);
})
it('should not decode spaces', function(done) {
var app = express();
app.get('/:name', function(req, res, next){
res.send(req.params.name);
});
request(app)
.get('/foo+bar')
.expect('foo+bar', done);
})
it('should work with unicode', function(done) {
var app = express();
app.get('/:name', function(req, res, next){
res.send(req.params.name);
});
request(app)
.get('/%ce%b1')
.expect('\u03b1', done);
})
request(app)
.get('/%foobar')
.expect('%foobar', done);
})
it('should be .use()able', function(done){
@@ -89,6 +61,36 @@ describe('app.router', function(){
next();
});
app.use(app.router);
app.use(function(req, res, next){
calls.push('after');
res.end();
});
app.get('/', function(req, res, next){
calls.push('GET /')
next();
});
request(app)
.get('/')
.end(function(res){
calls.should.eql(['before', 'GET /', 'after'])
done();
})
})
it('should be auto .use()d on the first app.VERB() call', function(done){
var app = express();
var calls = [];
app.use(function(req, res, next){
calls.push('before');
next();
});
app.get('/', function(req, res, next){
calls.push('GET /')
next();
@@ -124,8 +126,8 @@ describe('app.router', function(){
var app = express();
app.get(/^\/user\/([0-9]+)\/(view|edit)?$/, function(req, res){
var id = req.params[0]
, op = req.params[1];
var id = req.params.shift()
, op = req.params.shift();
res.end(op + 'ing user ' + id);
});
@@ -300,8 +302,8 @@ describe('app.router', function(){
var app = express();
app.get('/api/*.*', function(req, res){
var resource = req.params[0]
, format = req.params[1];
var resource = req.params.shift()
, format = req.params.shift();
res.end(resource + ' as ' + format);
});
+48
Ver Arquivo
@@ -0,0 +1,48 @@
var express = require('../')
, assert = require('assert')
, request = require('./support/http');
describe('app.routes', function(){
it('should be initialized', function(){
var app = express();
app.routes.should.eql({});
})
it('should be populated with routes', function(){
var app = express();
app.get('/', function(req, res){});
app.get('/user/:id', function(req, res){});
var get = app.routes.get;
get.should.have.length(2);
get[0].path.should.equal('/');
get[0].method.should.equal('get');
get[0].regexp.toString().should.equal('/^\\/\\/?$/i');
get[1].path.should.equal('/user/:id');
get[1].method.should.equal('get');
})
it('should be mutable', function(done){
var app = express();
app.get('/', function(req, res){});
app.get('/user/:id', function(req, res){});
var get = app.routes.get;
get.should.have.length(2);
get[0].path.should.equal('/');
get[0].method.should.equal('get');
get[0].regexp.toString().should.equal('/^\\/\\/?$/i');
get.splice(1);
request(app)
.get('/user/12')
.expect(404, done);
})
})
+96
Ver Arquivo
@@ -0,0 +1,96 @@
var express = require('../');
describe('config', function(){
describe('.configure()', function(){
describe('when no env is given', function(){
it('should always execute', function(){
var app = express();
var calls = [];
app.configure(function(){
calls.push('all');
});
app.configure('test', function(){
calls.push('test');
});
app.configure('test', function(){
calls.push('test 2');
});
calls.should.eql(['all', 'test', 'test 2'])
})
})
describe('when an env is given', function(){
it('should only execute the matching env', function(){
var app = express();
var calls = [];
app.set('env', 'development');
app.configure('development', function(){
calls.push('dev');
});
app.configure('test', function(){
calls.push('test');
});
calls.should.eql(['dev']);
})
})
describe('when several envs are given', function(){
it('should execute when matching one', function(){
var app = express();
var calls = [];
app.set('env', 'development');
app.configure('development', function(){
calls.push('dev');
});
app.configure('test', 'development', function(){
calls.push('dev 2');
});
app.configure('development', 'test', function(){
calls.push('dev 3');
});
app.configure('test', function(){
calls.push('dev 3');
});
calls.should.eql(['dev', 'dev 2', 'dev 3']);
})
})
it('should execute in order as defined', function(){
var app = express();
var calls = [];
app.configure(function(){
calls.push('all');
});
app.configure('test', function(){
calls.push('test');
});
app.configure(function(){
calls.push('all 2');
});
app.configure('test', function(){
calls.push('test 2');
});
calls.should.eql(['all', 'test', 'all 2', 'test 2'])
})
})
})
+10
Ver Arquivo
@@ -4,6 +4,16 @@ var express = require('../')
, assert = require('assert');
describe('exports', function(){
it('should expose connect middleware', function(){
express.should.have.property('bodyParser');
express.should.have.property('session');
express.should.have.property('static');
})
it('should expose .mime', function(){
assert(express.mime == require('connect').mime, 'express.mime should be connect.mime');
})
it('should expose Router', function(){
express.Router.should.be.a.Function;
})
-1
Ver Arquivo
@@ -1 +0,0 @@
p= name
+37
Ver Arquivo
@@ -0,0 +1,37 @@
var express = require('../')
, request = require('./support/http');
describe('req', function(){
describe('.accepted', function(){
it('should return an array of accepted media types', function(done){
var app = express();
app.use(function(req, res){
req.accepted[0].value.should.equal('application/json');
req.accepted[1].value.should.equal('text/html');
res.end();
});
request(app)
.get('/')
.set('Accept', 'text/html;q=.5, application/json')
.expect(200, done);
})
describe('when Accept is not present', function(){
it('should default to []', function(done){
var app = express();
app.use(function(req, res){
req.accepted.should.have.length(0);
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})
})
})
+37
Ver Arquivo
@@ -0,0 +1,37 @@
var express = require('../')
, request = require('./support/http');
describe('req', function(){
describe('.acceptedCharsets', function(){
it('should return an array of accepted charsets', function(done){
var app = express();
app.use(function(req, res){
req.acceptedCharsets[0].should.equal('unicode-1-1');
req.acceptedCharsets[1].should.equal('iso-8859-5');
res.end();
});
request(app)
.get('/')
.set('Accept-Charset', 'iso-8859-5;q=.2, unicode-1-1;q=0.8')
.expect(200, done);
})
describe('when Accept-Charset is not present', function(){
it('should default to []', function(done){
var app = express();
app.use(function(req, res){
req.acceptedCharsets.should.have.length(0);
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})
})
})
+37
Ver Arquivo
@@ -0,0 +1,37 @@
var express = require('../')
, request = require('./support/http');
describe('req', function(){
describe('.acceptedEncodings', function(){
it('should return an array of accepted encodings', function(done){
var app = express();
app.use(function(req, res){
req.acceptedEncodings.should.eql(['gzip', 'deflate']);
res.end();
});
request(app)
.get('/')
.set('Accept-Encoding', ' gzip, deflate')
.expect(200, done);
})
describe('when Accept-Encoding is not present', function(){
it('should default to []', function(done){
var app = express();
app.use(function(req, res){
req.acceptedEncodings.should.have.length(0);
res.end();
});
request(app)
.get('/')
.set('Accept-Encoding', '')
.expect(200, done);
})
})
})
})
+37
Ver Arquivo
@@ -0,0 +1,37 @@
var express = require('../')
, request = require('./support/http');
describe('req', function(){
describe('.acceptedLanguages', function(){
it('should return an array of accepted languages', function(done){
var app = express();
app.use(function(req, res){
req.acceptedLanguages[0].should.equal('en-us');
req.acceptedLanguages[1].should.equal('en');
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
describe('when Accept-Language is not present', function(){
it('should default to []', function(done){
var app = express();
app.use(function(req, res){
req.acceptedLanguages.should.have.length(0);
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})
})
})
+13
Ver Arquivo
@@ -43,6 +43,19 @@ describe('req', function(){
})
})
it('should accept a comma-delimited list of types', function(done){
var app = express();
app.use(function(req, res, next){
res.end(req.accepts('json, html'));
});
request(app)
.get('/')
.set('Accept', 'text/html')
.expect('html', done);
})
it('should accept an argument list of type names', function(done){
var app = express();
+3 -3
Ver Arquivo
@@ -9,7 +9,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)
@@ -23,7 +23,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)
@@ -36,7 +36,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)
+9 -12
Ver Arquivo
@@ -4,11 +4,8 @@ var express = require('../')
function req(ct) {
var req = {
headers: {
'content-type': ct,
'transfer-encoding': 'chunked'
},
__proto__: express.request
headers: { 'content-type': ct }
, __proto__: express.request
};
return req;
@@ -18,7 +15,7 @@ describe('req.is()', function(){
it('should ignore charset', function(){
req('application/json; charset=utf-8')
.is('json')
.should.equal('json');
.should.be.true;
})
describe('when content-type is not present', function(){
@@ -33,7 +30,7 @@ describe('req.is()', function(){
it('should lookup the mime type', function(){
req('application/json')
.is('json')
.should.equal('json');
.should.be.true;
req('text/html')
.is('json')
@@ -45,7 +42,7 @@ describe('req.is()', function(){
it('should match', function(){
req('application/json')
.is('application/json')
.should.equal('application/json');
.should.be.true;
req('image/jpeg')
.is('application/json')
@@ -57,7 +54,7 @@ describe('req.is()', function(){
it('should match', function(){
req('application/json')
.is('*/json')
.should.equal('application/json');
.should.be.true;
req('image/jpeg')
.is('*/json')
@@ -68,7 +65,7 @@ describe('req.is()', function(){
it('should match', function(){
req('text/html; charset=utf-8')
.is('*/html')
.should.equal('text/html');
.should.be.true;
req('text/plain; charset=utf-8')
.is('*/html')
@@ -81,7 +78,7 @@ describe('req.is()', function(){
it('should match', function(){
req('image/png')
.is('image/*')
.should.equal('image/png');
.should.be.true;
req('text/html')
.is('image/*')
@@ -92,7 +89,7 @@ describe('req.is()', function(){
it('should match', function(){
req('text/html; charset=utf-8')
.is('text/*')
.should.equal('text/html');
.should.be.true;
req('something/html; charset=utf-8')
.is('text/*')
+2 -3
Ver Arquivo
@@ -1,7 +1,6 @@
var express = require('../')
, request = require('./support/http')
, bodyParser = require('body-parser')
, request = require('./support/http');
describe('req', function(){
describe('.param(name, default)', function(){
@@ -34,7 +33,7 @@ describe('req', function(){
it('should check req.body', function(done){
var app = express();
app.use(bodyParser());
app.use(express.bodyParser());
app.use(function(req, res){
res.end(req.param('name'));
+2
Ver Arquivo
@@ -8,11 +8,13 @@ describe('req', function(){
var app = express();
app.get('/user/:id/:op?', function(req, res, next){
req.route.method.should.equal('get');
req.route.path.should.equal('/user/:id/:op?');
next();
});
app.get('/user/:id/edit', function(req, res){
req.route.method.should.equal('get');
req.route.path.should.equal('/user/:id/edit');
res.end();
});
+2 -3
Ver Arquivo
@@ -1,14 +1,13 @@
var express = require('../')
, request = require('./support/http')
, cookieParser = require('cookie-parser')
, request = require('./support/http');
describe('req', function(){
describe('.signedCookies', function(){
it('should return a signed JSON cookie', function(done){
var app = express();
app.use(cookieParser('secret'));
app.use(express.cookieParser('secret'));
app.use(function(req, res){
if ('/set' == req.path) {
+6 -8
Ver Arquivo
@@ -1,9 +1,8 @@
var express = require('../')
, request = require('./support/http')
, mixin = require('utils-merge')
, cookie = require('cookie')
, cookieParser = require('cookie-parser')
, utils = require('connect').utils
, cookie = require('cookie');
describe('res', function(){
describe('.cookie(name, object)', function(){
@@ -47,14 +46,13 @@ describe('res', function(){
app.use(function(req, res){
res.cookie('name', 'tobi');
res.cookie('age', 1);
res.cookie('gender', '?');
res.end();
});
request(app)
.get('/')
.end(function(err, res){
var val = ['name=tobi; Path=/', 'age=1; Path=/', 'gender=%3F; Path=/'];
var val = ['name=tobi; Path=/', 'age=1; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();
})
@@ -116,7 +114,7 @@ describe('res', function(){
var app = express();
var options = { maxAge: 1000 };
var optionsCopy = mixin({}, options);
var optionsCopy = utils.merge({}, options);
app.use(function(req, res){
res.cookie('name', 'tobi', options)
@@ -136,7 +134,7 @@ describe('res', function(){
it('should generate a signed JSON cookie', function(done){
var app = express();
app.use(cookieParser('foo bar baz'));
app.use(express.cookieParser('foo bar baz'));
app.use(function(req, res){
res.cookie('user', { name: 'tobi' }, { signed: true }).end();
@@ -157,7 +155,7 @@ describe('res', function(){
it('should set a signed cookie', function(done){
var app = express();
app.use(cookieParser('foo bar baz'));
app.use(express.cookieParser('foo bar baz'));
app.use(function(req, res){
res.cookie('name', 'tobi', { signed: true }).end();
+10 -3
Ver Arquivo
@@ -52,7 +52,7 @@ describe('res', function(){
})
})
})
describe('when given an object', function(){
it('should respond with json', function(done){
var app = express();
@@ -95,7 +95,14 @@ describe('res', function(){
})
describe('"json spaces" setting', function(){
it('should be undefined by default', function(){
it('should default to 2 in development', function(){
process.env.NODE_ENV = 'development';
var app = express();
app.get('json spaces').should.equal(2);
process.env.NODE_ENV = 'test';
})
it('should be undefined otherwise', function(){
var app = express();
assert(undefined === app.get('json spaces'));
})
@@ -118,7 +125,7 @@ describe('res', function(){
})
})
})
describe('.json(status, object)', function(){
it('should respond with json and set the .statusCode', function(done){
var app = express();
+8 -1
Ver Arquivo
@@ -181,7 +181,14 @@ describe('res', function(){
})
describe('"json spaces" setting', function(){
it('should be undefined by default', function(){
it('should default to 2 in development', function(){
process.env.NODE_ENV = 'development';
var app = express();
app.get('json spaces').should.equal(2);
process.env.NODE_ENV = 'test';
})
it('should be undefined otherwise', function(){
var app = express();
assert(undefined === app.get('json spaces'));
})
+5 -2
Ver Arquivo
@@ -3,12 +3,15 @@ var express = require('../')
, request = require('./support/http');
describe('res', function(){
describe('.locals', function(){
it('should be empty by default', function(done){
describe('.locals(obj)', function(){
it('should merge locals', function(done){
var app = express();
app.use(function(req, res){
Object.keys(res.locals).should.eql([]);
res.locals({ user: 'tobi', age: 1 });
res.locals.user.should.equal('tobi');
res.locals.age.should.equal(1);
res.end();
});
+166
Ver Arquivo
@@ -18,5 +18,171 @@ describe('res', function(){
done();
})
})
describe('with leading //', function(){
it('should pass through scheme-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('//cuteoverload.com').end();
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('location', '//cuteoverload.com');
done();
})
})
})
describe('with leading /', function(){
it('should construct scheme-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('/login').end();
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('location', '/login');
done();
})
})
})
describe('with leading ./', function(){
it('should construct path-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('./edit').end();
});
request(app)
.get('/post/1')
.end(function(err, res){
res.headers.should.have.property('location', '/post/1/edit');
done();
})
})
})
describe('with leading ../', function(){
it('should construct path-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('../new').end();
});
request(app)
.get('/post/1')
.end(function(err, res){
res.headers.should.have.property('location', '/post/new');
done();
})
})
})
describe('with leading ./ and containing ..', function(){
it('should construct path-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('./skip/../../new').end();
});
request(app)
.get('/post/1')
.end(function(err, res){
res.headers.should.have.property('location', '/post/new');
done();
})
})
})
describe('without leading /', function(){
it('should construct mount-point relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('login').end();
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('location', '/login');
done();
})
})
})
describe('when mounted', function(){
describe('deeply', function(){
it('should respect the mount-point', function(done){
var app = express()
, blog = express()
, admin = express();
admin.use(function(req, res){
res.location('login').end();
});
app.use('/blog', blog);
blog.use('/admin', admin);
request(app)
.get('/blog/admin')
.end(function(err, res){
res.headers.should.have.property('location', '/blog/admin/login');
done();
})
})
})
describe('omitting leading /', function(){
it('should respect the mount-point', function(done){
var app = express()
, admin = express();
admin.use(function(req, res){
res.location('admin/login').end();
});
app.use('/blog', admin);
request(app)
.get('/blog')
.end(function(err, res){
res.headers.should.have.property('location', '/blog/admin/login');
done();
})
})
})
describe('providing leading /', function(){
it('should ignore mount-point', function(done){
var app = express()
, admin = express();
admin.use(function(req, res){
res.location('/admin/login').end();
});
app.use('/blog', admin);
request(app)
.get('/blog')
.end(function(err, res){
res.headers.should.have.property('location', '/admin/login');
done();
})
})
})
})
})
})
+73 -1
Ver Arquivo
@@ -105,7 +105,7 @@ describe('res', function(){
.set('Host', 'http://example.com')
.set('Accept', 'text/html')
.end(function(err, res){
res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="&lt;lame&gt;">&lt;lame&gt;</a></p>');
res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="/&lt;lame&gt;">/&lt;lame&gt;</a></p>');
done();
})
})
@@ -169,4 +169,76 @@ describe('res', function(){
})
})
})
describe('responses redirected to relative paths', function(){
function create(depth, parent) {
var app = express();
if (parent) {
parent.use('/depth' + depth, app);
}
app.get('/', function(req, res){
res.redirect('./index');
});
app.get('/index', function(req, res){
res.json({ depth: depth, content: 'index' });
});
return app;
}
var root = create(0);
var depth1 = create(1, root);
var depth2 = create(2, depth1);
var depth3 = create(3, depth2);
root.use('/depth2', depth2);
root.use('/depth3', depth3);
it('should not contain redundant leading slashes in the location header', function(done){
request(root)
.get('/')
.end(function(err, res){
res.headers.location.search(/^\/{2}/).should.equal(-1);
done();
})
})
it('should preserve context when redirecting nested applications at any depth', function(done){
request(root)
.get('/depth1')
.end(function(err, res){
res.headers.should.have.property('location', '/depth1/index');
request(root)
.get('/depth1/depth2')
.end(function(err, res){
res.headers.should.have.property('location', '/depth1/depth2/index');
request(root)
.get('/depth1/depth2/depth3')
.end(function(err, res){
res.headers.should.have.property('location', '/depth1/depth2/depth3/index');
done();
})
})
});
})
it('should redirect correctly for nested applications that have been remounted', function(done){
request(root)
.get('/depth2')
.end(function(err, res){
res.headers.should.have.property('location', '/depth2/index');
request(root)
.get('/depth3')
.end(function(err, res){
res.headers.should.have.property('location', '/depth3/index');
done();
})
})
})
})
})
-15
Ver Arquivo
@@ -47,21 +47,6 @@ describe('res', function(){
.get('/')
.expect('<p>tobi</p>', done);
})
it('should expose app.locals with `name` property', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.name = 'tobi';
app.use(function(req, res){
res.render('name.jade');
});
request(app)
.get('/')
.expect('<p>tobi</p>', done);
})
it('should support index.<engine>', function(done){
var app = express();
+1 -5
Ver Arquivo
@@ -14,7 +14,6 @@ describe('res', function(){
request(app)
.get('/')
.expect('Content-Length', '0')
.expect('', done);
})
})
@@ -29,10 +28,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('', function(req, res){
res.header.should.not.have.property('content-length');
done();
});
.expect('', done);
})
})
+3 -3
Ver Arquivo
@@ -42,7 +42,7 @@ describe('res', function(){
app.use(function(req, res){
res.sendfile('test/fixtures/nope.html', function(err){
++calls;
assert(!res.headersSent);
assert(!res.headerSent);
res.send(err.message);
});
});
@@ -77,7 +77,7 @@ describe('res', function(){
app.use(function(req, res){
res.sendfile('test/fixtures/foo/../user.html', function(err){
assert(!res.headersSent);
assert(!res.headerSent);
++calls;
res.send(err.message);
});
@@ -95,7 +95,7 @@ describe('res', function(){
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', function(err){
assert(!res.headersSent);
assert(!res.headerSent);
req.socket.listeners('error').should.have.length(1); // node's original handler
done();
});
+178
Ver Arquivo
@@ -41,3 +41,181 @@ describe('utils.flatten(arr)', function(){
.should.eql(['one', 'two', 'three', 'four', 'five']);
})
})
describe('utils.escape(html)', function(){
it('should escape html entities', function(){
utils.escape('<script>foo & "bar"')
.should.equal('&lt;script&gt;foo &amp; &quot;bar&quot;')
})
})
describe('utils.parseParams(str)', function(){
it('should default quality to 1', function(){
utils.parseParams('text/html')
.should.eql([{ value: 'text/html', quality: 1, params: {}, originalIndex: 0 }]);
})
it('should parse qvalues', function(){
utils.parseParams('text/html; q=0.5')
.should.eql([{ value: 'text/html', quality: 0.5, params: {}, originalIndex: 0 }]);
utils.parseParams('text/html; q=.2')
.should.eql([{ value: 'text/html', quality: 0.2, params: {}, originalIndex: 0 }]);
})
it('should parse accept parameters', function(){
utils.parseParams('application/json; ver=2.0')
.should.eql([{ value: 'application/json', quality: 1, params: {ver: "2.0"}, originalIndex: 0 }]);
utils.parseParams('text/html; q=0.5; level=2')
.should.eql([{ value: 'text/html', quality: 0.5, params: {level: "2"}, originalIndex: 0 }]);
utils.parseParams('text/html;q=.2;ver=beta')
.should.eql([{ value: 'text/html', quality: 0.2, params: {ver: "beta"}, originalIndex: 0 }]);
})
it('should work with messed up whitespace', function(){
utils.parseParams('text/html ; q = .2')
.should.eql([{ value: 'text/html', quality: 0.2, params: {}, originalIndex: 0 }]);
})
it('should work with multiples', function(){
var str = 'da, en;q=.5, en-gb;q=.8';
var arr = utils.parseParams(str);
arr[0].value.should.equal('da');
arr[1].value.should.equal('en-gb');
arr[2].value.should.equal('en');
})
it('should work with long lists', function(){
var str = 'en, nl, fr, de, ja, it, es, pt, pt-PT, da, fi, nb, sv, ko, zh-Hans, zh-Hant, ru, pl';
var arr = utils.parseParams(str).map(function(o){ return o.value });
arr.should.eql(str.split(', '));
})
it('should sort by quality', function(){
var str = 'text/plain;q=.2, application/json, text/html;q=0.5';
var arr = utils.parseParams(str);
arr[0].value.should.equal('application/json');
arr[1].value.should.equal('text/html');
arr[2].value.should.equal('text/plain');
})
it('should exclude those with a quality of 0', function(){
var str = 'text/plain;q=.2, application/json, text/html;q=0';
var arr = utils.parseParams(str);
arr.should.have.length(2);
})
})
describe('utils.parseAccept(str)', function(){
it('should provide .type', function(){
var arr = utils.parseAccept('text/html');
arr[0].type.should.equal('text');
})
it('should provide .subtype', function(){
var arr = utils.parseAccept('text/html');
arr[0].subtype.should.equal('html');
})
})
describe('utils.accepts(type, str)', function(){
describe('when a string is not given', function(){
it('should return the value', function(){
utils.accepts('text/html')
.should.equal('text/html');
})
})
describe('when a string is empty', function(){
it('should return the value', function(){
utils.accepts('text/html', '')
.should.equal('text/html');
})
})
describe('when */* is given', function(){
it('should return the value', function(){
utils.accepts('text/html', 'text/plain, */*')
.should.equal('text/html');
})
})
describe('when an array is given', function(){
it('should return the best match', function(){
utils.accepts(['html', 'json'], 'text/plain, application/json')
.should.equal('json');
utils.accepts(['html', 'application/json'], 'text/plain, application/json')
.should.equal('application/json');
utils.accepts(['text/html', 'application/json'], 'application/json;q=.5, text/html')
.should.equal('text/html');
})
})
describe('when a comma-delimited list is give', function(){
it('should behave like an array', function(){
utils.accepts('html, json', 'text/plain, application/json')
.should.equal('json');
utils.accepts('html, application/json', 'text/plain, application/json')
.should.equal('application/json');
utils.accepts('text/html, application/json', 'application/json;q=.5, text/html')
.should.equal('text/html');
})
})
describe('when accepting type/subtype', function(){
it('should return the value when present', function(){
utils.accepts('text/html', 'text/plain, text/html')
.should.equal('text/html');
})
it('should return undefined otherwise', function(){
assert(null == utils.accepts('text/html', 'text/plain, application/json'));
})
})
describe('when accepting */subtype', function(){
it('should return the value when present', function(){
utils.accepts('text/html', 'text/*')
.should.equal('text/html');
})
it('should return undefined otherwise', function(){
assert(null == utils.accepts('text/html', 'image/*'));
})
})
describe('when accepting type/*', function(){
it('should return the value when present', function(){
utils.accepts('text/html', '*/html')
.should.equal('text/html');
})
it('should return undefined otherwise', function(){
assert(null == utils.accepts('text/html', '*/json'));
})
})
describe('when an extension is given', function(){
it('should return the value when present', function(){
utils.accepts('html', 'text/html, application/json')
.should.equal('html');
})
it('should return undefined otherwise', function(){
assert(null == utils.accepts('html', 'text/plain, application/json'));
})
it('should support *', function(){
utils.accepts('html', 'text/*')
.should.equal('html');
utils.accepts('html', '*/html')
.should.equal('html');
})
})
})