Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote-tracking branch 'upstream/master'

  • Loading branch information...
commit 58d9e635aa1974b90002c3e6fdc97f840ee44a49 2 parents 337e7c6 + 8fdd9e9
@pengyu authored
View
2  docs/guide.html
@@ -1451,7 +1451,7 @@ <h3 id="res.locals()">res.locals(obj)</h3>
<pre><code> res.local('foo', bar);
res.local('bar', baz);
- res.locals({ foo: bar, bar, baz });
+ res.locals({ foo: bar, bar: baz });
</code></pre>
<h3 id="app.set()">app.set(name[, val])</h3>
View
8 examples/content-negotiation/index.js
@@ -9,20 +9,20 @@ users.push({ name: 'Loki' });
users.push({ name: 'Jane' });
app.get('/', function(req, res){
- res.respondTo({
- 'text/html': function(){
+ res.format({
+ html: function(){
res.send('<ul>' + users.map(function(user){
return '<li>' + user.name + '</li>';
}).join('') + '</ul>');
},
- 'text/plain': function(){
+ text: function(){
res.send(users.map(function(user){
return ' - ' + user.name + '\n';
}).join(''));
},
- 'application/json': function(){
+ json: function(){
res.json(users);
}
})
View
30 examples/cookie-sessions/index.js
@@ -7,34 +7,16 @@ var express = require('../../');
var app = module.exports = express();
-/**
- * Cookie session middleware using the given cookie `name`.
- *
- * Here we simply check for a signed cookie of the same name,
- * then save the object as JSON on response, again as a signed cookie.
- */
-
-function cookieSessions(name) {
- return function(req, res, next) {
- req.session = req.signedCookies[name] || {};
-
- res.on('header', function(){
- res.signedCookie(name, req.session);
- });
-
- next();
- }
-}
+// pass a secret to cookieParser() for signed cookies
+app.use(express.cookieParser('manny is cool'));
-// for this we need cookie support! this will
-// populate req.{signedCookies,cookies}()
+// add req.session cookie support
+app.use(express.cookieSessions());
-app.use(express.cookieParser('manny is cool'));
-app.use(cookieSessions('sid'));
+// do something with the session
app.use(count);
-// do something with our session
-
+// custom middleware
function count(req, res) {
req.session.count = req.session.count || 0;
var n = req.session.count++;
View
14 examples/markdown/app.js → examples/markdown/index.js
@@ -14,19 +14,21 @@ var app = module.exports = express();
app.engine('md', function(path, options, fn){
fs.readFile(path, 'utf8', function(err, str){
if (err) return fn(err);
- try{
+ try {
var html = md(str);
html = html.replace(/\{([^}]+)\}/g, function(_, name){
- return options[name];
+ return options[name] || '';
})
- fn(null,html)
- } catch(e){
- fn(e)
+ fn(null, html);
+ } catch(err) {
+ fn(err);
}
- })
+ });
})
app.set('views', __dirname + '/views');
+
+// make it the default so we dont need .md
app.set('view engine', 'md');
app.get('/', function(req, res){
View
6 lib/application.js
@@ -177,7 +177,6 @@ app.use = function(route, fn){
};
}
- debug('use %s %s', route, fn.name || 'unnamed');
connect.proto.use.call(this, route, fn);
// mounted an app
@@ -207,6 +206,7 @@ app.use = function(route, fn){
*/
app.engine = function(ext, fn){
+ if ('function' != typeof fn) throw new Error('callback function required');
if ('.' != ext[0]) ext = '.' + ext;
this.engines[ext] = fn;
return this;
@@ -516,6 +516,10 @@ app.render = function(name, options, fn){
, engines: engines
});
+ if (!view.path) {
+ return fn(new Error('Failed to lookup view "' + name + '"'));
+ }
+
// prime the cache
if (opts.cache) cache[name] = view;
}
View
4 lib/middleware.js
@@ -16,7 +16,7 @@
*/
exports.init = function(app){
- return function(req, res, next){
+ return function expressInit(req, res, next){
var charset;
res.setHeader('X-Powered-By', 'Express');
req.app = res.app = app;
@@ -34,4 +34,4 @@ exports.init = function(app){
next();
}
-};
+};
View
61 lib/request.js
@@ -58,28 +58,44 @@ req.get = function(name){
};
/**
- * Check if the given `type` is acceptable,
- * otherwise you should respond with 406 "Not Acceptable".
+ * Check if the given `type(s)` is acceptable, returning
+ * the best match when true, otherwise `undefined`, in which
+ * case you should respond with 406 "Not Acceptable".
+ *
+ * The `type` value may be a single mime type string
+ * such as "application/json", the extension name
+ * such as "json", a comma-delimted list such as "json, html, text/plain",
+ * or an array `["json", "html", "text/plain"]`. When a list
+ * or array is given the _best_ match, if any is returned.
*
* Examples:
*
* // Accept: text/html
* req.accepts('html');
- * // => true
+ * // => "html"
*
- * // Accept: text/*; application/json
+ * // Accept: text/*, application/json
* req.accepts('html');
+ * // => "html"
* req.accepts('text/html');
- * req.accepts('text/plain');
+ * // => "text/html"
+ * req.accepts('json, text');
+ * // => "json"
* req.accepts('application/json');
- * // => true
+ * // => "application/json"
*
+ * // Accept: text/*, application/json
* req.accepts('image/png');
* req.accepts('png');
- * // => false
+ * // => undefined
*
- * @param {String} type
- * @return {Boolean}
+ * // Accept: text/*;q=.5, application/json
+ * req.accepts(['html', 'json']);
+ * req.accepts('html, json');
+ * // => "json"
+ *
+ * @param {String|Array} type(s)
+ * @return {String}
* @api public
*/
@@ -293,7 +309,7 @@ req.is = function(type){
req.__defineGetter__('protocol', function(trustProxy){
var trustProxy = this.app.settings['trust proxy'];
- return this.secure
+ return this.connection.encrypted
? 'https'
: trustProxy
? (this.get('X-Forwarded-Proto') || 'http')
@@ -301,14 +317,35 @@ req.__defineGetter__('protocol', function(trustProxy){
});
/**
- * Short-hand for `req.connection.encrypted`.
+ * Short-hand for:
+ *
+ * req.protocol == 'https'
*
* @return {Boolean}
* @api public
*/
req.__defineGetter__('secure', function(){
- return this.connection.encrypted;
+ return 'https' == this.protocol;
+});
+
+/**
+ * When "trust proxy" is `true`, parse
+ * the "X-Forwarded-For" ip address list.
+ *
+ * For example if the value were "client, proxy1, proxy2"
+ * you would receive the array `["proxy2", "proxy1", "client"]`
+ * where "proxy2" is the furthest down-stream.
+ *
+ * @return {Array}
+ * @api public
+ */
+
+req.__defineGetter__('ips', function(){
+ var val = this.get('X-Forwarded-For');
+ return val
+ ? val.split(/ *, */).reverse()
+ : [];
});
/**
View
41 lib/response.js
@@ -14,7 +14,8 @@ var fs = require('fs')
, path = require('path')
, connect = require('connect')
, utils = connect.utils
- , accept = require('./utils').accept
+ , normalizeType = require('./utils').normalizeType
+ , normalizeTypes = require('./utils').normalizeTypes
, statusCodes = http.STATUS_CODES
, send = connect.static.send
, mime = require('mime')
@@ -340,48 +341,17 @@ res.type = function(type){
res.format = function(obj){
var keys = Object.keys(obj)
- , types = []
, req = this.req
, next = req.next
- , accepted = req.accepted
- , acceptedlen = accepted.length
- , type
- , key;
-
- // normalize extnames -> mime
- if (acceptedlen) {
- for (var i = 0; i < keys.length; ++i) {
- types.push(~keys[i].indexOf('/')
- ? keys[i]
- : mime.lookup(keys[i]));
- }
- }
-
- // determine most acceptable format
- out:
- for (var i = 0; i < acceptedlen; ++i) {
- for (var j = 0, jlen = types.length; j < jlen; ++j) {
- if (accept(types[j].split('/'), accepted[i])) {
- key = keys[j];
- type = types[j];
- break out;
- }
- }
- }
-
- // default to the first
- if (!acceptedlen) {
- key = keys[0];
- type = types[0];
- }
+ , key = req.accepts(keys);
if (key) {
- this.set('Content-Type', type);
+ this.set('Content-Type', normalizeType(key));
obj[key](req, this, next);
} else {
var err = new Error('Not Acceptable');
err.status = 406;
- err.types = types;
+ err.types = normalizeTypes(keys);
next(err);
}
@@ -521,6 +491,7 @@ res.cookie = function(name, val, options){
* res.redirect('/foo/bar');
* res.redirect('http://example.com');
* res.redirect(301, 'http://example.com');
+ * res.redirect('../login'); // /blog/post/1 -> /blog/login
*
* Mounting:
*
View
52 lib/router/collection.js
@@ -1,52 +0,0 @@
-
-/*!
- * Express - router - Collection
- * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
- * MIT Licensed
- */
-
-/**
- * Expose `Collection`.
- */
-
-module.exports = Collection;
-
-/**
- * Initialize a new route `Collection`
- * with the given `router`.
- *
- * @param {Router} router
- * @api private
- */
-
-function Collection(router) {
- Object.defineProperty(this, 'router', { value: router });
-}
-
-/**
- * Inherit from `Array.prototype`.
- */
-
-Collection.prototype.__proto__ = Array.prototype;
-
-/**
- * Remove the routes in this collection.
- *
- * @return {Collection} of routes removed
- * @api public
- */
-
-Collection.prototype.remove = function(){
- var router = this.router
- , len = this.length
- , ret = new Collection(this.router);
-
- for (var i = 0; i < len; ++i) {
- if (router.remove(this[i])) {
- ret.push(this[i]);
- }
- }
-
- return ret;
-};
-
View
132 lib/router/index.js
@@ -9,7 +9,6 @@
*/
var Route = require('./route')
- , Collection = require('./collection')
, utils = require('../utils')
, debug = require('debug')('express:router')
, parse = require('url').parse;
@@ -36,7 +35,6 @@ var methods = exports.methods = require('./methods');
function Router(options) {
options = options || {};
var self = this;
- this.routes = new Collection;
this.map = {};
this.params = {};
this._params = [];
@@ -86,109 +84,6 @@ Router.prototype.param = function(name, fn){
};
/**
- * Return a `Collection` of all routes defined.
- *
- * @return {Collection}
- * @api public
- */
-
-Router.prototype.all = function(){
- return this.find(function(){
- return true;
- });
-};
-
-/**
- * Remove the given `route`, returns
- * a bool indicating if the route was present
- * or not.
- *
- * @param {Route} route
- * @return {Boolean}
- * @api public
- */
-
-Router.prototype.remove = function(route){
- var routes = this.map[route.method]
- , len = routes.length;
-
- // remove from array
- var i = this.routes.indexOf(route);
- this.routes.splice(i, 1);
-
- // remove from map
- for (var i = 0; i < len; ++i) {
- if (route == routes[i]) {
- routes.splice(i, 1);
- return true;
- }
- }
-};
-
-/**
- * Return routes with route paths matching `path`.
- *
- * @param {String} method
- * @param {String} path
- * @return {Collection}
- * @api public
- */
-
-Router.prototype.lookup = function(method, path){
- return this.find(function(route){
- return path == route.path
- && (route.method == method
- || method == 'all');
- });
-};
-
-// /**
-// * Return routes with regexps that match the given `url`.
-// *
-// * @param {String} method
-// * @param {String} url
-// * @return {Collection}
-// * @api public
-// */
-//
-// Router.prototype.match = function(method, url){
-// return this.find(function(route){
-// return route.match(url)
-// && (route.method == method
-// || method == 'all');
-// });
-// };
-
-/**
- * Find routes based on the return value of `fn`
- * which is invoked once per route.
- *
- * @param {Function} fn
- * @return {Collection}
- * @api public
- */
-
-Router.prototype.find = function(fn){
- var len = methods.length
- , ret = new Collection(this)
- , method
- , routes
- , route;
-
- for (var i = 0; i < len; ++i) {
- method = methods[i];
- routes = this.map[method];
- if (!routes) continue;
- for (var j = 0, jlen = routes.length; j < jlen; ++j) {
- route = routes[j];
- if (fn(route)) ret.push(route);
- }
- }
-
- return ret;
-};
-
-/**
* Route dispatcher aka the route "middleware".
*
* @param {IncomingMessage} req
@@ -342,9 +237,7 @@ Router.prototype.match = function(req, i, head){
, path = url.pathname
, routes = this.map
, i = i || 0
- , captures
- , route
- , keys;
+ , route;
// HEAD support
// TODO: clean this up
@@ -364,24 +257,7 @@ Router.prototype.match = function(req, i, head){
// matching routes
for (var len = routes.length; i < len; ++i) {
route = routes[i];
- if (captures = route.match(path)) {
- keys = route.keys;
- route.params = [];
-
- // params from capture groups
- for (var j = 1, jlen = captures.length; j < jlen; ++j) {
- var key = keys[j-1]
- , val = 'string' == typeof captures[j]
- ? decodeURIComponent(captures[j])
- : captures[j];
- if (key) {
- route.params[key.name] = val;
- } else {
- route.params.push(val);
- }
- }
-
- // all done
+ if (route.match(path)) {
req._route_index = i;
return route;
}
@@ -400,8 +276,7 @@ Router.prototype.match = function(req, i, head){
*/
Router.prototype.route = function(method, path, callbacks){
- var app = this.app
- , method = method.toLowerCase()
+ var method = method.toLowerCase()
, callbacks = utils.flatten([].slice.call(arguments, 2));
// ensure path was given
@@ -416,6 +291,5 @@ Router.prototype.route = function(method, path, callbacks){
// add it
(this.map[method] = this.map[method] || []).push(route);
- this.routes.push(route);
return this;
};
View
73 lib/router/route.js
@@ -1,3 +1,4 @@
+
/*!
* Express - router - Route
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
@@ -5,6 +6,12 @@
*/
/**
+ * Module dependencies.
+ */
+
+var utils = require('../utils');
+
+/**
* Expose `Route`.
*/
@@ -31,59 +38,41 @@ function Route(method, path, callbacks, options) {
this.path = path;
this.method = method;
this.callbacks = callbacks;
- this.regexp = normalize(path
+ this.regexp = utils.pathRegexp(path
, this.keys = []
, options.sensitive
, options.strict);
}
/**
- * Check if this route matches `path` and return captures made.
+ * Check if this route matches `path`, if so
+ * populate `.params`.
*
* @param {String} path
- * @return {Array}
+ * @return {Boolean}
* @api private
*/
Route.prototype.match = function(path){
- return this.regexp.exec(path);
-};
+ var keys = this.keys
+ , params = this.params = []
+ , m = this.regexp.exec(path);
-/**
- * Normalize the given path string,
- * returning a regular expression.
- *
- * An empty array should be passed,
- * which will contain the placeholder
- * key names. For example "/user/:id" will
- * then contain ["id"].
- *
- * @param {String|RegExp|Array} path
- * @param {Array} keys
- * @param {Boolean} sensitive
- * @param {Boolean} strict
- * @return {RegExp}
- * @api private
- */
+ if (!m) return false;
-function normalize(path, keys, sensitive, strict) {
- if (path instanceof RegExp) return path;
- if (path instanceof Array)
- path = '(' + path.join('|') + ')';
- path = path
- .concat(strict ? '' : '/?')
- .replace(/\/\(/g, '(?:/')
- .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
- keys.push({ name: key, optional: !! optional });
- slash = slash || '';
- return ''
- + (optional ? '' : slash)
- + '(?:'
- + (optional ? slash : '')
- + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
- + (optional || '');
- })
- .replace(/([\/.])/g, '\\$1')
- .replace(/\*/g, '(.*)');
- return new RegExp('^' + path + '$', sensitive ? '' : 'i');
-}
+ for (var i = 1, len = m.length; i < len; ++i) {
+ var key = keys[i - 1];
+
+ var val = 'string' == typeof m[i]
+ ? decodeURIComponent(m[i])
+ : m[i];
+
+ if (key) {
+ params[key.name] = val;
+ } else {
+ params.push(val);
+ }
+ }
+
+ return true;
+};
View
113 lib/utils.js
@@ -46,37 +46,77 @@ exports.flatten = function(arr, ret){
};
/**
- * Check if `type` is acceptable based on
- * the given `str`.
+ * Normalize the given `type`, for example "html" becomes "text/html".
*
* @param {String} type
- * @param {String} str
- * @return {Boolean}
+ * @return {String}
* @api private
*/
-exports.accepts = function(type, str){
- // accept anything when Accept is not present
- if (!str) return true;
+exports.normalizeType = function(type){
+ return ~type.indexOf('/') ? type : mime.lookup(type);
+};
+
+/**
+ * Normalize `types`, for example "html" becomes "text/html".
+ *
+ * @param {Array} types
+ * @return {Array}
+ * @api private
+ */
+
+exports.normalizeTypes = function(types){
+ var ret = [];
- // resolve mime
- if (!~type.indexOf('/')) type = mime.lookup(type);
+ for (var i = 0; i < types.length; ++i) {
+ ret.push(~types[i].indexOf('/')
+ ? types[i]
+ : mime.lookup(types[i]));
+ }
+
+ return ret;
+};
+
+/**
+ * Return the acceptable type in `types`, if any.
+ *
+ * @param {Array} types
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
- // split type/subtype
- type = type.split('/');
+exports.acceptsArray = function(types, str){
+ // accept anything when Accept is not present
+ if (!str) return types[0];
// parse
var accepted = exports.parseAccept(str)
- , len = accepted.length
- , obj
- , ok;
+ , normalized = exports.normalizeTypes(types)
+ , len = accepted.length;
for (var i = 0; i < len; ++i) {
- obj = accepted[i];
- if (exports.accept(type, obj)) return true;
+ for (var j = 0, jlen = types.length; j < jlen; ++j) {
+ if (exports.accept(normalized[j].split('/'), 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
+ */
- return false;
+exports.accepts = function(type, str){
+ if ('string' == typeof type) type = type.split(/ *, */);
+ return exports.acceptsArray(type, str);
};
/**
@@ -173,3 +213,42 @@ exports.escape = function(html) {
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
};
+
+/**
+ * Normalize the given path string,
+ * returning a regular expression.
+ *
+ * An empty array should be passed,
+ * which will contain the placeholder
+ * key names. For example "/user/:id" will
+ * then contain ["id"].
+ *
+ * @param {String|RegExp|Array} path
+ * @param {Array} keys
+ * @param {Boolean} sensitive
+ * @param {Boolean} strict
+ * @return {RegExp}
+ * @api private
+ */
+
+exports.pathRegexp = function(path, keys, sensitive, strict) {
+ if (path instanceof RegExp) return path;
+ if (path instanceof Array)
+ path = '(' + path.join('|') + ')';
+ path = path
+ .concat(strict ? '' : '/?')
+ .replace(/\/\(/g, '(?:/')
+ .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
+ keys.push({ name: key, optional: !! optional });
+ slash = slash || '';
+ return ''
+ + (optional ? '' : slash)
+ + '(?:'
+ + (optional ? slash : '')
+ + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
+ + (optional || '');
+ })
+ .replace(/([\/.])/g, '\\$1')
+ .replace(/\*/g, '(.*)');
+ return new RegExp('^' + path + '$', sensitive ? '' : 'i');
+}
View
2  lib/view.js
@@ -1,4 +1,3 @@
-
/*!
* Express - View
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
@@ -11,7 +10,6 @@
var path = require('path')
, utils = require('./utils')
- , fs = require('fs')
, dirname = path.dirname
, basename = path.basename
, extname = path.extname
View
2  package.json
@@ -10,7 +10,7 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
],
"dependencies": {
- "connect": "2.0.0",
+ "connect": "2.0.3",
"commander": "0.5.2",
"mime": "1.2.5",
"mkdirp": "0.3.0",
View
9 test/app.engine.js
@@ -25,7 +25,14 @@ describe('app', function(){
done();
})
})
-
+
+ it('should throw when the callback is missing', function(){
+ var app = express();
+ (function(){
+ app.engine('.html', null);
+ }).should.throw('callback function required');
+ })
+
it('should work without leading "."', function(done){
var app = express();
View
13 test/app.render.js
@@ -53,7 +53,18 @@ describe('app', function(){
done();
})
})
-
+
+ describe('when the file does not exist', function(){
+ it('should provide a helpful error', function(done){
+ var app = express();
+ app.set('views', __dirname + '/fixtures');
+ app.render('rawr.jade', function(err){
+ err.message.should.equal('Failed to lookup view "rawr.jade"');
+ done();
+ });
+ })
+ })
+
describe('when an error occurs', function(){
it('should invoke the callback', function(done){
var app = express();
View
44 test/app.router.js
@@ -24,6 +24,18 @@ describe('app.router', function(){
});
})
+ it('should decode params', function(done){
+ var app = express();
+
+ app.get('/:name', function(req, res, next){
+ res.send(req.params.name);
+ });
+
+ request(app)
+ .get('/foo%2Fbar')
+ .expect('foo/bar', done);
+ })
+
it('should be .use()able', function(done){
var app = express();
@@ -111,22 +123,22 @@ describe('app.router', function(){
})
describe('when given an array', function(){
- it('should match all paths in the array', function(done){
- var app = express();
-
- app.get(['/one', '/two'], function(req, res){
- res.end('works');
- });
-
- request(app)
- .get('/one')
- .expect('works', function() {
- request(app)
- .get('/two')
- .expect('works', done);
- });
- })
- })
+ it('should match all paths in the array', function(done){
+ var app = express();
+
+ app.get(['/one', '/two'], function(req, res){
+ res.end('works');
+ });
+
+ request(app)
+ .get('/one')
+ .expect('works', function() {
+ request(app)
+ .get('/two')
+ .expect('works', done);
+ });
+ })
+ })
describe('case sensitivity', function(){
it('should be disabled by default', function(done){
View
79 test/req.accepts.js
@@ -42,4 +42,83 @@ describe('req', function(){
.expect('no', done);
})
})
+
+ 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);
+ })
+
+ describe('.accept(types)', function(){
+ it('should return the first when Accept is not present', function(done){
+ var app = express();
+
+ app.use(function(req, res, next){
+ res.end(req.accepts(['json', 'html']));
+ });
+
+ request(app)
+ .get('/')
+ .expect('json', done);
+ })
+
+ it('should return the first acceptable type', 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 return false when no match is made', function(done){
+ var app = express();
+
+ app.use(function(req, res, next){
+ res.end(req.accepts(['text/html', 'application/json']) ? 'yup' : 'nope');
+ });
+
+ request(app)
+ .get('/')
+ .set('Accept', 'foo/bar, bar/baz')
+ .expect('nope', done);
+ })
+
+ it('should take quality into account', function(done){
+ var app = express();
+
+ app.use(function(req, res, next){
+ res.end(req.accepts(['text/html', 'application/json']));
+ });
+
+ request(app)
+ .get('/')
+ .set('Accept', '*/html; q=.5, application/json')
+ .expect('application/json', done);
+ })
+
+ it('should return the first acceptable type with canonical mime types', function(done){
+ var app = express();
+
+ app.use(function(req, res, next){
+ res.end(req.accepts(['application/json', 'text/html']));
+ });
+
+ request(app)
+ .get('/')
+ .set('Accept', '*/html')
+ .expect('text/html', done);
+ })
+ })
})
View
43 test/req.cookies.js
@@ -1,43 +0,0 @@
-
-var express = require('../')
- , request = require('./support/http');
-
-describe('req', function(){
- describe('.cookies', function(){
- it('should expose cookie data', function(done){
- var app = express();
-
- app.use(express.cookieParser());
-
- app.use(function(req, res){
- res.end(req.cookies.name + ' ' + req.cookies.species);
- });
-
- request(app)
- .get('/')
- .set('Cookie', 'name=tobi; species=ferret')
- .end(function(res){
- res.body.should.equal('tobi ferret');
- done();
- })
- })
-
- it('should parse JSON cookies', function(done){
- var app = express();
-
- app.use(express.cookieParser());
-
- app.use(function(req, res){
- res.end(req.cookies.user.name);
- });
-
- request(app)
- .get('/')
- .set('Cookie', 'user=j%3A%7B%22name%22%3A%22tobi%22%7D')
- .end(function(res){
- res.body.should.equal('tobi');
- done();
- })
- })
- })
-})
View
36 test/req.ips.js
@@ -0,0 +1,36 @@
+
+var express = require('../')
+ , request = require('./support/http');
+
+describe('req', function(){
+ describe('.ips', function(){
+ describe('when X-Forwarded-For is present', function(){
+ it('should return an array of the specified addresses', function(done){
+ var app = express();
+
+ app.use(function(req, res, next){
+ res.send(req.ips);
+ });
+
+ request(app)
+ .get('/')
+ .set('X-Forwarded-For', 'client, p1, p2')
+ .expect('["p2","p1","client"]', done);
+ })
+ })
+
+ describe('when X-Forwarded-For is not present', function(){
+ it('should return []', function(done){
+ var app = express();
+
+ app.use(function(req, res, next){
+ res.send(req.ips);
+ });
+
+ request(app)
+ .get('/')
+ .expect('[]', done);
+ })
+ })
+ })
+})
View
66 test/req.signedCookies.js
@@ -1,66 +0,0 @@
-
-var express = require('../')
- , request = require('./support/http');
-
-describe('req', function(){
- describe('.signedCookies', function(){
- it('should unsign cookies', function(done){
- var app = express();
-
- app.use(express.cookieParser('foo bar baz'));
-
- app.use(function(req, res){
- req.cookies.should.not.have.property('name');
- res.end(req.signedCookies.name);
- });
-
- request(app)
- .get('/')
- .set('Cookie', 'name=tobi.2HDdGQqJ6jQU1S9dagggYDPaxGE')
- .end(function(res){
- res.body.should.equal('tobi');
- done();
- })
- })
-
- it('should parse JSON cookies', function(done){
- var app = express();
-
- app.use(express.cookieParser('foo bar baz'));
-
- app.use(function(req, res){
- req.cookies.should.not.have.property('user');
- res.end(req.signedCookies.user.name);
- });
-
- request(app)
- .get('/')
- .set('Cookie', 'user=j%3A%7B%22name%22%3A%22tobi%22%7D.aEbp4PGZo63zMX%2FcIMSn2M9pvms')
- .end(function(res){
- res.body.should.equal('tobi');
- done();
- })
- })
-
- describe('when signature is invalid', function(){
- it('should unsign cookies', function(done){
- var app = express();
-
- app.use(express.cookieParser('foo bar baz'));
-
- app.use(function(req, res){
- req.signedCookies.should.not.have.property('name');
- res.end(req.cookies.name);
- });
-
- request(app)
- .get('/')
- .set('Cookie', 'name=tobi.2HDdGQqJ6jQU1S9dagasdfasdf')
- .end(function(res){
- res.body.should.equal('tobi.2HDdGQqJ6jQU1S9dagasdfasdf');
- done();
- })
- })
- })
- })
-})
View
1  test/res.format.js
@@ -26,6 +26,7 @@ app.use(function(req, res, next){
});
app.use(function(err, req, res, next){
+ if (!err.types) throw err;
res.send(err.status, 'Supports: ' + err.types.join(', '));
})
View
18 test/res.json.js
@@ -22,6 +22,24 @@ describe('res', function(){
})
})
})
+
+ describe('when given an array', function(){
+ it('should respond with json', function(done){
+ var app = express();
+
+ app.use(function(req, res){
+ res.json(['foo', 'bar', 'baz']);
+ });
+
+ request(app)
+ .get('/')
+ .end(function(res){
+ res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+ res.body.should.equal('["foo","bar","baz"]');
+ done();
+ })
+ })
+ })
describe('when given an object', function(){
it('should respond with json', function(done){
View
9 test/res.signedCookie.js
@@ -3,7 +3,7 @@ var express = require('../')
, request = require('./support/http');
describe('res', function(){
- describe('.signedCook(name, object)', function(){
+ describe('.signedCookie(name, object)', function(){
it('should generate a signed JSON cookie', function(done){
var app = express();
@@ -16,8 +16,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
- var val = ['user=j%3A%7B%22name%22%3A%22tobi%22%7D.aEbp4PGZo63zMX%2FcIMSn2M9pvms; path=/'];
- res.headers['set-cookie'].should.eql(val);
+ var val = res.headers['set-cookie'][0];
+ val = decodeURIComponent(val.split('.')[0]);
+ val.should.equal('user=j:{"name":"tobi"}');
done();
})
})
@@ -36,7 +37,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
- var val = ['name=tobi.2HDdGQqJ6jQU1S9dagggYDPaxGE; path=/'];
+ var val = ['name=tobi.xJjV2iZ6EI7C8E5kzwbfA9PVLl1ZR07UTnuTgQQ4EnQ; path=/'];
res.headers['set-cookie'].should.eql(val);
done();
})
View
80 test/utils.js
@@ -85,79 +85,101 @@ describe('utils.parseAccept(str)', function(){
describe('utils.accepts(type, str)', function(){
describe('when a string is not given', function(){
- it('should return true', function(){
+ it('should return the value', function(){
utils.accepts('text/html')
- .should.be.true;
+ .should.equal('text/html');
})
})
describe('when a string is empty', function(){
- it('should return true', function(){
+ it('should return the value', function(){
utils.accepts('text/html', '')
- .should.be.true;
+ .should.equal('text/html');
})
})
-
+
describe('when */* is given', function(){
- it('should return true', function(){
+ it('should return the value', function(){
utils.accepts('text/html', 'text/plain, */*')
- .should.be.true;
+ .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 true when present', function(){
+ it('should return the value when present', function(){
utils.accepts('text/html', 'text/plain, text/html')
- .should.be.true;
+ .should.equal('text/html');
})
- it('should return false otherwise', function(){
- utils.accepts('text/html', 'text/plain, application/json')
- .should.be.false;
+ it('should return undefined otherwise', function(){
+ assert(null == utils.accepts('text/html', 'text/plain, application/json'));
})
})
describe('when accepting */subtype', function(){
- it('should return true when present', function(){
+ it('should return the value when present', function(){
utils.accepts('text/html', 'text/*')
- .should.be.true;
+ .should.equal('text/html');
})
- it('should return false otherwise', function(){
- utils.accepts('text/html', 'image/*')
- .should.be.false;
+ it('should return undefined otherwise', function(){
+ assert(null == utils.accepts('text/html', 'image/*'));
})
})
describe('when accepting type/*', function(){
- it('should return true when present', function(){
+ it('should return the value when present', function(){
utils.accepts('text/html', '*/html')
- .should.be.true;
+ .should.equal('text/html');
})
- it('should return false otherwise', function(){
- utils.accepts('text/html', '*/json')
- .should.be.false;
+ it('should return undefined otherwise', function(){
+ assert(null == utils.accepts('text/html', '*/json'));
})
})
describe('when an extension is given', function(){
- it('should return true when present', function(){
+ it('should return the value when present', function(){
utils.accepts('html', 'text/html, application/json')
- .should.be.true;
+ .should.equal('html');
})
- it('should return false otherwise', function(){
- utils.accepts('html', 'text/plain, application/json')
- .should.be.false;
+ it('should return undefined otherwise', function(){
+ assert(null == utils.accepts('html', 'text/plain, application/json'));
})
it('should support *', function(){
utils.accepts('html', 'text/*')
- .should.be.true;
+ .should.equal('html');
utils.accepts('html', '*/html')
- .should.be.true;
+ .should.equal('html');
})
})
})
Please sign in to comment.
Something went wrong with that request. Please try again.