Permalink
Browse files

Merge branch 'master' of https://github.com/senchalabs/connect

  • Loading branch information...
2 parents 21b334b + 2be1587 commit c465b5ef35aad0fecbaf3d0f8917160642d9e4b1 @hunterloftis committed Mar 27, 2012
View
6 History.md
@@ -1,4 +1,10 @@
+2.0.3 / 2012-03-20
+==================
+
+ * Added: `cookieSession()` only sets cookie on change. Closes #442
+ * Added `connect:dispatcher` debug() probes
+
2.0.2 / 2012-03-04
==================
View
13 examples/cookieSession.js
@@ -4,8 +4,10 @@ var connect = require('../')
var app = connect()
.use(connect.logger('dev'))
+ .use(connect.bodyParser())
.use(connect.cookieParser('some secret'))
.use(connect.cookieSession())
+ .use(post)
.use(clear)
.use(counter);
@@ -17,10 +19,21 @@ function clear(req, res, next) {
res.end();
}
+function post(req, res, next) {
+ if ('POST' != req.method) return next();
+ req.session.name = req.body.name;
+ next();
+}
+
function counter(req, res) {
req.session.count = req.session.count || 0;
var n = req.session.count++;
+ var name = req.session.name || 'Enter your name';
res.end('<p>hits: ' + n + '</p>'
+ + '<form method="post">'
+ + '<p><input type="text" name="name" value="' + name + '" />'
+ + '<input type="submit" value="Save" /></p>'
+ + '</form>'
+ '<p><a href="/clear">clear session</a></p>');
}
View
2 examples/session.js
@@ -106,7 +106,7 @@ http.createServer(connect()
console.log('port 3003: conditional sessions');
// Session#reload() will update req.session
-// without altering .maxAge or .lastAccess
+// without altering .maxAge
// view the page several times, and see that the
// setInterval can still gain access to new
View
2 lib/connect.js
@@ -29,7 +29,7 @@ exports = module.exports = createServer;
* Framework version.
*/
-exports.version = '2.0.2';
+exports.version = '2.0.3';
/**
* Expose the prototype.
View
1 lib/index.js
@@ -50,5 +50,6 @@
*
* - list of [3rd-party](https://github.com/senchalabs/connect/wiki) middleware
* - GitHub [repository](http://github.com/senchalabs/connect)
+ * - [test documentation](https://github.com/senchalabs/connect/blob/gh-pages/tests.md)
*
*/
View
4 lib/middleware/cookieParser.js
@@ -48,7 +48,9 @@ module.exports = function cookieParser(secret){
req.cookies = utils.parseCookie(cookie);
if (secret) {
req.signedCookies = utils.parseSignedCookies(req.cookies, secret);
- req.signedCookies = utils.parseJSONCookies(req.signedCookies);
+ var obj = utils.parseJSONCookies(req.signedCookies);
+ req.signedCookies = obj.cookies;
+ req.cookieHashes = obj.hashes;
}
req.cookies = utils.parseJSONCookies(req.cookies);
} catch (err) {
View
14 lib/middleware/cookieSession.js
@@ -11,7 +11,8 @@
var utils = require('./../utils')
, Cookie = require('./session/cookie')
- , debug = require('debug')('connect:cookieSession');
+ , debug = require('debug')('connect:cookieSession')
+ , crc16 = require('crc').crc16;
// environment
@@ -69,12 +70,19 @@ module.exports = function cookieSession(options){
// only send secure cookies via https
if (cookie.secure && !secured) return debug('not secured');
- // set cookie
+ // serialize
debug('serializing %j', req.session);
var val = 'j:' + JSON.stringify(req.session);
+
+ // compare hashes
+ var originalHash = req.cookieHashes && req.cookieHashes[key];
+ var hash = crc16(val);
+ if (originalHash == hash) return debug('unmodified session');
+
+ // set-cookie
val = utils.sign(val, req.secret);
val = utils.serializeCookie(key, val, cookie);
- debug('cookie %j', cookie);
+ debug('set-cookie %j', cookie);
res.setHeader('Set-Cookie', val);
});
View
13 lib/middleware/json.js
@@ -18,20 +18,23 @@ var utils = require('../utils');
* Parse JSON request bodies, providing the
* parsed object as `req.body`.
*
+ * Options:
+ *
+ * - `strict` when `true` only objects and arrays are valid JSON
+ *
* @param {Object} options
* @return {Function}
* @api public
*/
exports = module.exports = function(options){
- options = options || {};
+ var options = options || {}
+ , strict = options.strict;
+
return function json(req, res, next) {
if (req._body) return next();
req.body = req.body || {};
- // ignore GET
- if ('GET' == req.method || 'HEAD' == req.method) return next();
-
// check Content-Type
if ('application/json' != utils.mime(req)) return next();
@@ -43,7 +46,7 @@ exports = module.exports = function(options){
req.setEncoding('utf8');
req.on('data', function(chunk){ buf += chunk });
req.on('end', function(){
- if ('{' != buf[0] && '[' != buf[0]) return next(utils.error(400));
+ if (strict && '{' != buf[0] && '[' != buf[0]) return next(utils.error(400));
try {
req.body = JSON.parse(buf);
next();
View
6 lib/middleware/session.js
@@ -134,7 +134,7 @@ var warning = 'Warning: connection.session() MemoryStore is not\n'
*
* ## Session#touch()
*
- * Updates the `.maxAge`, and `.lastAccess` properties. Typically this is
+ * Updates the `.maxAge` property. Typically this is
* not necessary to call, as the session middleware does this for you.
*
* ## Session#cookie
@@ -157,8 +157,8 @@ var warning = 'Warning: connection.session() MemoryStore is not\n'
*
* For example when `maxAge` is set to `60000` (one minute), and 30 seconds
* has elapsed it will return `30000` until the current request has completed,
- * at which time `req.session.touch()` is called to update `req.session.lastAccess`,
- * and reset `req.session.maxAge` to its original value.
+ * at which time `req.session.touch()` is called to reset `req.session.maxAge`
+ * to its original value.
*
* req.session.cookie.maxAge;
* // => 30000
View
34 lib/middleware/session/session.js
@@ -24,16 +24,11 @@ var utils = require('../../utils')
var Session = module.exports = function Session(req, data) {
Object.defineProperty(this, 'req', { value: req });
Object.defineProperty(this, 'id', { value: req.sessionID });
- if ('object' == typeof data) {
- utils.merge(this, data);
- } else {
- this.lastAccess = Date.now();
- }
+ if ('object' == typeof data) utils.merge(this, data);
};
/**
- * Update `.lastAccess` timestamp,
- * and reset `.cookie.maxAge` to prevent
+ * Update reset `.cookie.maxAge` to prevent
* the cookie from expiring when the
* session is still active.
*
@@ -42,21 +37,7 @@ var Session = module.exports = function Session(req, data) {
*/
Session.prototype.touch = function(){
- return this
- .resetLastAccess()
- .resetMaxAge();
-};
-
-/**
- * Update `.lastAccess` timestamp.
- *
- * @return {Session} for chaining
- * @api public
- */
-
-Session.prototype.resetLastAccess = function(){
- this.lastAccess = Date.now();
- return this;
+ return this.resetMaxAge();
};
/**
@@ -86,11 +67,10 @@ Session.prototype.save = function(fn){
/**
* Re-loads the session data _without_ altering
- * the maxAge or lastAccess properties. Invokes the
- * callback `fn(err)`, after which time if no exception
- * has occurred the `req.session` property will be
- * a new `Session` object, although representing the
- * same session.
+ * the maxAge properties. Invokes the callback `fn(err)`,
+ * after which time if no exception has occurred the
+ * `req.session` property will be a new `Session` object,
+ * although representing the same session.
*
* @param {Function} fn
* @return {Session} for chaining
View
5 lib/middleware/session/store.js
@@ -60,7 +60,7 @@ Store.prototype.load = function(sid, fn){
if (err) return fn(err);
if (!sess) return fn();
var req = { sessionID: sid, sessionStore: self };
- sess = self.createSession(req, sess, false);
+ sess = self.createSession(req, sess);
fn(null, sess);
});
};
@@ -74,14 +74,13 @@ Store.prototype.load = function(sid, fn){
* @api private
*/
-Store.prototype.createSession = function(req, sess, update){
+Store.prototype.createSession = function(req, sess){
var expires = sess.cookie.expires
, orig = sess.cookie.originalMaxAge
, update = null == update ? true : false;
sess.cookie = new Cookie(req, sess.cookie);
if ('string' == typeof expires) sess.cookie.expires = new Date(expires);
sess.cookie.originalMaxAge = orig;
req.session = new Session(req, sess);
- if (update) req.session.resetLastAccess();
return req.session;
};
View
3 lib/middleware/staticCache.js
@@ -81,6 +81,9 @@ module.exports = function staticCache(options){
// ignore larger files
if (!contentLength || contentLength > maxlen) return;
+ // don't cache partial files
+ if (headers['content-range']) return;
+
// dont cache items we shouldn't be
// TODO: real support for must-revalidate / no-cache
if ( cc['no-cache']
View
3 lib/middleware/urlencoded.js
@@ -30,9 +30,6 @@ exports = module.exports = function(options){
if (req._body) return next();
req.body = req.body || {};
- // ignore GET
- if ('GET' == req.method || 'HEAD' == req.method) return next();
-
// check Content-Type
if ('application/x-www-form-urlencoded' != utils.mime(req)) return next();
View
6 lib/proto.js
@@ -12,7 +12,8 @@
var http = require('http')
, parse = require('url').parse
- , utils = require('./utils');
+ , utils = require('./utils')
+ , debug = require('debug')('connect:dispatcher');
// prototype
@@ -122,6 +123,7 @@ app.handle = function(req, res, out) {
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;
@@ -139,6 +141,7 @@ app.handle = function(req, res, out) {
if ('HEAD' == req.method) return res.end();
res.end(msg);
} else {
+ debug('default 404');
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
if ('HEAD' == req.method) return res.end();
@@ -165,6 +168,7 @@ app.handle = function(req, res, out) {
// Ensure leading slash
if (!fqdn && '/' != req.url[0]) req.url = '/' + req.url;
+ debug('%s', layer.handle.name || 'anonymous');
var arity = layer.handle.length;
if (err) {
if (arity === 4) {
View
10 lib/utils.js
@@ -12,6 +12,7 @@
var http = require('http')
, crypto = require('crypto')
+ , crc16 = require('crc').crc16
, Path = require('path')
, fs = require('fs');
@@ -188,17 +189,24 @@ exports.parseSignedCookies = function(obj, secret){
*/
exports.parseJSONCookies = function(obj){
+ var hashes = {};
+
Object.keys(obj).forEach(function(key){
var val = obj[key];
if (0 == val.indexOf('j:')) {
try {
+ hashes[key] = crc16(val); // only crc json cookies for now
obj[key] = JSON.parse(val.slice(2));
} catch (err) {
// nothing
}
}
});
- return obj;
+
+ return {
+ cookies: obj,
+ hashes: hashes
+ };
};
/**
View
3 package.json
@@ -1,6 +1,6 @@
{
"name": "connect",
- "version": "2.0.2",
+ "version": "2.0.3",
"description": "High performance middleware framework",
"keywords": ["framework", "web", "middleware", "connect", "rack"],
"repository": "git://github.com/senchalabs/connect.git",
@@ -10,6 +10,7 @@
"qs": "0.4.2",
"mime": "1.2.4",
"formidable": "1.0.9",
+ "crc": "0.1.0",
"debug": "*"
},
"devDependencies": {
View
71 test/cookieSession.js
@@ -145,6 +145,77 @@ describe('connect.cookieSession()', function(){
})
})
+ describe('when modified', function(){
+ it('should set-cookie', function(done){
+ var n = 0;
+ var app = connect()
+ .use(connect.cookieParser('keyboard cat'))
+ .use(connect.cookieSession())
+ .use(function(req, res, next){
+ req.session.foo = ++n;
+ res.end();
+ });
+
+ app.request()
+ .get('/')
+ .end(function(res){
+ res.headers.should.have.property('set-cookie');
+
+ app.request()
+ .get('/')
+ .set('Cookie', sess(res))
+ .end(function(res){
+ res.headers.should.have.property('set-cookie');
+ done();
+ });
+ });
+ })
+ })
+
+ describe('when un-modified', function(){
+ it('should set-cookie only the initial time', function(done){
+ var modify;
+
+ var app = connect()
+ .use(connect.cookieParser('keyboard cat'))
+ .use(connect.cookieSession())
+ .use(function(req, res, next){
+ if (modify) req.session.foo = 'bar';
+ res.end();
+ });
+
+ app.request()
+ .get('/')
+ .end(function(res){
+ res.headers.should.have.property('set-cookie');
+ var cookie = sess(res);
+
+ app.request()
+ .get('/')
+ .set('Cookie', cookie)
+ .end(function(res){
+ res.headers.should.not.have.property('set-cookie');
+
+ app.request()
+ .get('/')
+ .set('Cookie', cookie)
+ .end(function(res){
+ res.headers.should.not.have.property('set-cookie');
+ modify = true;
+
+ app.request()
+ .get('/')
+ .set('Cookie', cookie)
+ .end(function(res){
+ res.headers.should.have.property('set-cookie');
+ done();
+ });
+ });
+ });
+ });
+ })
+ })
+
describe('.secure', function(){
it('should not set-cookie when insecure', function(done){
var app = connect()
View
65 test/json.js
@@ -18,17 +18,6 @@ app.use(function(err, req, res, next){
describe('connect.json()', function(){
should['default request body'](app);
- it('should ignore GET', function(done){
- app.request()
- .get('/')
- .set('Content-Type', 'application/json')
- .write('{"user":"tobi"}')
- .end(function(res){
- res.body.should.equal('{}');
- done();
- });
- })
-
it('should parse JSON', function(done){
app.request()
.post('/')
@@ -51,15 +40,22 @@ describe('connect.json()', function(){
});
})
- it('should 400 on primitives', function(done){
+ it('should 400 on malformed JSON', function(done){
+ var app = connect();
+ app.use(connect.json());
+
+ app.use(function(req, res){
+ res.end(JSON.stringify(req.body));
+ });
+
app.request()
.post('/')
.set('Content-Type', 'application/json')
- .write('"hello, tobi"')
+ .write('{"foo')
.expect(400, done);
})
- it('should 400 on malformed JSON', function(done){
+ it('should support all http methods', function(done){
var app = connect();
app.use(connect.json());
@@ -68,9 +64,44 @@ describe('connect.json()', function(){
});
app.request()
- .post('/')
+ .get('/')
.set('Content-Type', 'application/json')
- .write('{"foo')
- .expect(400, done);
+ .set('Content-Length', '["foo"]'.length)
+ .write('["foo"]')
+ .expect('["foo"]', done);
+ })
+
+ describe('by default', function(){
+ it('should parse primitives', function(done){
+ var app = connect();
+ app.use(connect.json());
+
+ app.use(function(req, res){
+ res.end(JSON.stringify(req.body));
+ });
+
+ app.request()
+ .post('/')
+ .set('Content-Type', 'application/json')
+ .write('true')
+ .expect('true', done);
+ })
+ })
+
+ describe('when strict', function(){
+ it('should 400 on primitives', function(done){
+ var app = connect();
+ app.use(connect.json({ strict: true }));
+
+ app.use(function(req, res){
+ res.end(JSON.stringify(req.body));
+ });
+
+ app.request()
+ .post('/')
+ .set('Content-Type', 'application/json')
+ .write('true')
+ .expect(400, done);
+ })
})
})
View
12 test/urlencoded.js
@@ -12,6 +12,18 @@ app.use(function(req, res){
describe('connect.urlencoded()', function(){
should['default request body'](app);
+
+ it('should support all http methods', function(done){
+ app.request()
+ .get('/')
+ .set('Content-Type', 'application/x-www-form-urlencoded')
+ .set('Content-Length', 'user=tobi'.length)
+ .write('user=tobi')
+ .end(function(res){
+ res.body.should.equal('{"user":"tobi"}');
+ done();
+ });
+ })
it('should parse x-www-form-urlencoded', function(done){
app.request()

0 comments on commit c465b5e

Please sign in to comment.