Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

update docs

  • Loading branch information...
commit 66d45034361994bf97934414fd60f50172e93a17 1 parent 8d28eb6
@tj tj authored
Showing with 901 additions and 1,089 deletions.
  1. +2 −2 basicAuth.html
  2. +22 −0 cache.html
  3. +8 −9 compress.html
  4. +6 −3 connect.html
  5. +7 −8 cookieParser.html
  6. +43 −16 cookieSession.html
  7. +2 −2 csrf.html
  8. +2 −0  directory.html
  9. +2 −2 docs/basicAuth.html
  10. +8 −9 docs/compress.html
  11. +7 −6 docs/connect.html
  12. +8 −7 docs/cookieParser.html
  13. +48 −14 docs/cookieSession.html
  14. +2 −2 docs/csrf.html
  15. +2 −0  docs/directory.html
  16. +5 −4 docs/favicon.html
  17. +3 −9 docs/index.html
  18. +38 −19 docs/json.html
  19. +2 −14 docs/limit.html
  20. +7 −7 docs/logger.html
  21. +56 −39 docs/multipart.html
  22. +16 −2 docs/proto.html
  23. +6 −3 docs/query.html
  24. +51 −21 docs/session.html
  25. +25 −150 docs/static.html
  26. +92 −67 docs/staticCache.html
  27. +32 −18 docs/urlencoded.html
  28. +32 −142 docs/utils.html
  29. +6 −7 docs/vhost.html
  30. +5 −4 favicon.html
  31. +1 −8 index.html
  32. +38 −19 json.html
  33. +2 −14 limit.html
  34. +7 −7 logger.html
  35. +56 −39 multipart.html
  36. +13 −2 proto.html
  37. +6 −3 query.html
  38. +51 −21 session.html
  39. +25 −157 static.html
  40. +92 −67 staticCache.html
  41. +32 −18 urlencoded.html
  42. +33 −148 utils.html
View
4 basicAuth.html
@@ -56,14 +56,14 @@
var pause = utils.pause(req);
callback(user, pass, function(err, user){
if (err || !user) return unauthorized(res, realm);
- req.user = user;
+ req.user = req.remoteUser = user;
next();
pause.resume();
});
// sync
} else {
if (callback(user, pass)) {
- req.user = user;
+ req.user = req.remoteUser = user;
next();
} else {
unauthorized(res, realm);
View
22 cache.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="module.exports" class="comment"><h2></h2><div class="description"><p>Expose <code>Cache</code>.</p></div><h3>Source</h3><pre><code>module.exports = Cache;</code></pre></div><div id="Cache" class="comment"><h2>Cache()</h2><div class="description"><p>LRU cache store.</p></div><ul class="tags"><li><em>Number</em> limit </li></ul><h3>Source</h3><pre><code>function Cache(limit) {
+ this.store = {};
+ this.keys = [];
+ this.limit = limit;
+}</code></pre></div><div id="Cache.prototype.touch" class="comment"><h2>Cache#touch()</h2><div class="description"><p>Touch <code>key</code>, promoting the object.</p></div><ul class="tags"><li><em>String</em> key </li><li><em>Number</em> i </li></ul><h3>Source</h3><pre><code>Cache.prototype.touch = function(key, i){
+ this.keys.splice(i,1);
+ this.keys.push(key);
+};</code></pre></div><div id="Cache.prototype.remove" class="comment"><h2>Cache#remove()</h2><div class="description"><p>Remove <code>key</code>.</p></div><ul class="tags"><li><em>String</em> key </li></ul><h3>Source</h3><pre><code>Cache.prototype.remove = function(key){
+ delete this.store[key];
+};</code></pre></div><div id="Cache.prototype.get" class="comment"><h2>Cache#get()</h2><div class="description"><p>Get the object stored for <code>key</code>.</p></div><ul class="tags"><li><em>String</em> key </li><li>returns <em>Array</em> </li></ul><h3>Source</h3><pre><code>Cache.prototype.get = function(key){
+ return this.store[key];
+};</code></pre></div><div id="Cache.prototype.add" class="comment"><h2>Cache#add()</h2><div class="description"><p>Add a cache <code>key</code>.</p></div><ul class="tags"><li><em>String</em> key </li><li>returns <em>Array</em> </li></ul><h3>Source</h3><pre><code>Cache.prototype.add = function(key){
+ // initialize store
+ var len = this.keys.push(key);
+
+ // limit reached, invalidate LRU
+ if (len > this.limit) this.remove(this.keys.shift());
+
+ var arr = this.store[key] = [];
+ arr.createdAt = new Date;
+ return arr;
+};</code></pre></div></div><ul id="menu"><li><a href="#module.exports"></a></li><li><a href="#Cache">Cache()</a></li><li><a href="#Cache.prototype.touch">Cache#touch()</a></li><li><a href="#Cache.prototype.remove">Cache#remove()</a></li><li><a href="#Cache.prototype.get">Cache#get()</a></li><li><a href="#Cache.prototype.add">Cache#add()</a></li></ul></body></html>
View
17 compress.html
@@ -2,21 +2,17 @@
gzip: zlib.createGzip
, deflate: zlib.createDeflate
};</code></pre></div><div id="exports.filter" class="comment"><h2>exports.filter()</h2><div class="description"><p>Default filter function.</p></div><h3>Source</h3><pre><code>exports.filter = function(req, res){
- var type = res.getHeader('Content-Type') || '';
- return type.match(/json|text|javascript/);
+ return /json|text|javascript/.test(res.getHeader('Content-Type'));
};</code></pre></div><div id="module.exports" class="comment"><h2></h2><div class="description"><h2>Compress</h2>
<p>Compress response data with gzip/deflate.</p>
<h2>Filter</h2>
-<p>A <code>filter</code> callback function may be passed to</p>
-
-<h2>replace the default logic of</h2>
+<p>A <code>filter</code> callback function may be passed to<br /> replace the default logic of:</p>
<pre><code>exports.filter = function(req, res){
- var type = res.getHeader('Content-Type') || '';
- return type.match(/json|text|javascript/);
+ return /json|text|javascript/.test(res.getHeader('Content-Type'));
};
</code></pre>
@@ -50,7 +46,7 @@
res.write = function(chunk, encoding){
if (!this.headerSent) this._implicitHeader();
return stream
- ? stream.write(chunk, encoding)
+ ? stream.write(new Buffer(chunk, encoding))
: write.call(res, chunk, encoding);
};
@@ -103,7 +99,10 @@
stream.on('end', function(){
end.call(res);
});
-
+
+ stream.on('drain', function() {
+ res.emit('drain');
+ });
});
next();
View
9 connect.html
@@ -1,9 +1,12 @@
-<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="exports.version" class="comment"><h2>exports.version</h2><div class="description"><p>Framework version.</p></div><h3>Source</h3><pre><code>exports.version = '2.0.3';</code></pre></div><div id="exports.proto" class="comment"><h2>exports.proto</h2><div class="description"><p>Expose the prototype.</p></div><h3>Source</h3><pre><code>exports.proto = proto;</code></pre></div><div id="exports.middleware" class="comment"><h2>exports.middleware</h2><div class="description"><p>Auto-load middleware getters.</p></div><h3>Source</h3><pre><code>exports.middleware = {};</code></pre></div><div id="exports.utils" class="comment"><h2>exports.utils</h2><div class="description"><p>Expose utilities.</p></div><h3>Source</h3><pre><code>exports.utils = utils;</code></pre></div><div id="createServer" class="comment"><h2>createServer()</h2><div class="description"><p>Create a new connect server.</p></div><ul class="tags"><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>function createServer() {
+<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="exports.version" class="comment"><h2>exports.version</h2><div class="description"><p>Framework version.</p></div><h3>Source</h3><pre><code>exports.version = '2.4.3';</code></pre></div><div id="exports.mime" class="comment"><h2>exports.mime</h2><div class="description"><p>Expose mime module.</p></div><h3>Source</h3><pre><code>exports.mime = require('./middleware/static').mime;</code></pre></div><div id="exports.proto" class="comment"><h2>exports.proto</h2><div class="description"><p>Expose the prototype.</p></div><h3>Source</h3><pre><code>exports.proto = proto;</code></pre></div><div id="exports.middleware" class="comment"><h2>exports.middleware</h2><div class="description"><p>Auto-load middleware getters.</p></div><h3>Source</h3><pre><code>exports.middleware = {};</code></pre></div><div id="exports.utils" class="comment"><h2>exports.utils</h2><div class="description"><p>Expose utilities.</p></div><h3>Source</h3><pre><code>exports.utils = utils;</code></pre></div><div id="createServer" class="comment"><h2>createServer()</h2><div class="description"><p>Create a new connect server.</p></div><ul class="tags"><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>function createServer() {
function app(req, res){ app.handle(req, res); }
utils.merge(app, proto);
utils.merge(app, EventEmitter.prototype);
app.route = '/';
- app.stack = [].slice.apply(arguments);
+ app.stack = [];
+ for (var i = 0; i < arguments.length; ++i) {
+ app.use(arguments[i]);
+ }
return app;
};</code></pre></div><div id="createServer.createServer" class="comment"><h2>createServer.createServer</h2><div class="description"><p>Support old <code>.createServer()</code> method.</p></div><h3>Source</h3><pre><code>createServer.createServer = createServer;</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><p>Auto-load bundled middleware with getters.</p></div><h3>Source</h3><pre><code>fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
if (!/\.js$/.test(filename)) return;
@@ -11,4 +14,4 @@
function load(){ return require('./middleware/' + name); }
exports.middleware.__defineGetter__(name, load);
exports.__defineGetter__(name, load);
-});</code></pre></div></div><ul id="menu"><li><a href="#exports.version">exports.version</a></li><li><a href="#exports.proto">exports.proto</a></li><li><a href="#exports.middleware">exports.middleware</a></li><li><a href="#exports.utils">exports.utils</a></li><li><a href="#createServer">createServer()</a></li><li><a href="#createServer.createServer">createServer.createServer</a></li><li><a href="#"></a></li></ul></body></html>
+});</code></pre></div></div><ul id="menu"><li><a href="#exports.version">exports.version</a></li><li><a href="#exports.mime">exports.mime</a></li><li><a href="#exports.proto">exports.proto</a></li><li><a href="#exports.middleware">exports.middleware</a></li><li><a href="#exports.utils">exports.utils</a></li><li><a href="#createServer">createServer()</a></li><li><a href="#createServer.createServer">createServer.createServer</a></li><li><a href="#"></a></li></ul></body></html>
View
15 cookieParser.html
@@ -1,31 +1,30 @@
<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="module.exports" class="comment"><h2></h2><div class="description"><h2>Cookie parser</h2>
-<p>Parse <em>Cookie</em> header and populate <code>req.cookies</code><br />with an object keyed by the cookie names. Optionally<br />you may enabled signed cookie support by passing<br />a <code>secret</code> string, which assigns <code>req.secret</code> so<br />it may be used by other middleware such as <code>session()</code>.</p>
+<p>Parse <em>Cookie</em> header and populate <code>req.cookies</code><br />with an object keyed by the cookie names. Optionally<br />you may enabled signed cookie support by passing<br />a <code>secret</code> string, which assigns <code>req.secret</code> so<br />it may be used by other middleware.</p>
<h2>Examples</h2>
<pre><code>connect()
- .use(connect.cookieParser('keyboard cat'))
+ .use(connect.cookieParser('optional secret string'))
.use(function(req, res, next){
res.end(JSON.stringify(req.cookies));
})
</code></pre></div><ul class="tags"><li><em>String</em> secret </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function cookieParser(secret){
return function cookieParser(req, res, next) {
- var cookie = req.headers.cookie;
if (req.cookies) return next();
+ var cookies = req.headers.cookie;
req.secret = secret;
req.cookies = {};
req.signedCookies = {};
-
- if (cookie) {
+
+ if (cookies) {
try {
- req.cookies = utils.parseCookie(cookie);
+ req.cookies = cookie.parse(cookies);
if (secret) {
req.signedCookies = utils.parseSignedCookies(req.cookies, secret);
var obj = utils.parseJSONCookies(req.signedCookies);
- req.signedCookies = obj.cookies;
- req.cookieHashes = obj.hashes;
+ req.signedCookies = obj;
}
req.cookies = utils.parseJSONCookies(req.cookies);
} catch (err) {
View
59 cookieSession.html
@@ -3,38 +3,67 @@
<p>Cookie session middleware.</p>
<pre><code> var app = connect();
- app.use(connect.cookieParser('tobo!'));
- app.use(connect.cookieSession({ cookie: { maxAge: 60 * 60 * 1000 }}));
+ app.use(connect.cookieParser());
+ app.use(connect.cookieSession({ secret: 'tobo!', cookie: { maxAge: 60 * 60 * 1000 }}));
</code></pre>
<h2>Options</h2>
<ul>
<li><code>key</code> cookie name defaulting to <code>connect.sess</code></li>
+<li><code>secret</code> prevents cookie tampering</li>
<li><code>cookie</code> session cookie settings, defaulting to <code>{ path: '/', httpOnly: true, maxAge: null }</code></li>
<li><code>proxy</code> trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")</li>
-</ul></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function cookieSession(options){
- // TODO: utilize Session/Cookie to unify API
- // TODO: only set-cookie on changes to the session data
+</ul>
+
+<h2>Clearing sessions</h2>
+
+<p>To clear the session simply set its value to <code>null</code>,<br /> <code>cookieSession()</code> will then respond with a 1970 Set-Cookie.</p>
+<pre><code>req.session = null;
+</code></pre></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function cookieSession(options){
+ // TODO: utilize Session/Cookie to unify API
var options = options || {}
, key = options.key || 'connect.sess'
- , cookie = options.cookie
, trustProxy = options.proxy;
return function cookieSession(req, res, next) {
- req.session = req.signedCookies[key] || {};
- req.session.cookie = new Cookie(req, cookie);
+
+ // req.secret is for backwards compatibility
+ var secret = options.secret || req.secret;
+ if (!secret) throw new Error('`secret` option required for cookie sessions');
+
+ // default session
+ req.session = {};
+ var cookie = req.session.cookie = new Cookie(options.cookie);
+
+ // pathname mismatch
+ if (0 != req.originalUrl.indexOf(cookie.path)) return next();
+
+ // cookieParser secret
+ if (!options.secret && req.secret) {
+ req.session = req.signedCookies[key] || {};
+ } else {
+ // TODO: refactor
+ var rawCookie = req.cookies[key];
+ if (rawCookie) {
+ var unsigned = utils.parseSignedCookie(rawCookie, secret);
+ if (unsigned) {
+ var originalHash = crc16(unsigned);
+ req.session = utils.parseJSONCookie(unsigned) || {};
+ }
+ }
+ }
res.on('header', function(){
// removed
if (!req.session) {
debug('clear session');
- res.setHeader('Set-Cookie', key + '=; expires=' + new Date(0).toUTCString());
+ cookie.expires = new Date(0);
+ res.setHeader('Set-Cookie', cookie.serialize(key, ''));
return;
}
- var cookie = req.session.cookie;
delete req.session.cookie;
// check security
@@ -49,14 +78,12 @@
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');
+ // compare hashes, no need to set-cookie if unchanged
+ if (originalHash == crc16(val)) return debug('unmodified session');
// set-cookie
- val = utils.sign(val, req.secret);
- val = utils.serializeCookie(key, val, cookie);
+ val = 's:' + utils.sign(val, secret);
+ val = cookie.serialize(key, val);
debug('set-cookie %j', cookie);
res.setHeader('Set-Cookie', val);
});
View
4 csrf.html
@@ -20,8 +20,8 @@
// generate CSRF token
var token = req.session._csrf || (req.session._csrf = utils.uid(24));
- // ignore GET & HEAD (for now)
- if ('GET' == req.method || 'HEAD' == req.method) return next();
+ // ignore these methods
+ if ('GET' == req.method || 'HEAD' == req.method || 'OPTIONS' == req.method) return next();
// determine value
var val = value(req);
View
2  directory.html
@@ -19,6 +19,8 @@
, root = normalize(root);
return function directory(req, res, next) {
+ if ('GET' != req.method && 'HEAD' != req.method) return next();
+
var accept = req.headers.accept || 'text/plain'
, url = parse(req.url)
, dir = decodeURIComponent(url.pathname)
View
4 docs/basicAuth.html
@@ -56,14 +56,14 @@
var pause = utils.pause(req);
callback(user, pass, function(err, user){
if (err || !user) return unauthorized(res, realm);
- req.user = user;
+ req.user = req.remoteUser = user;
next();
pause.resume();
});
// sync
} else {
if (callback(user, pass)) {
- req.user = user;
+ req.user = req.remoteUser = user;
next();
} else {
unauthorized(res, realm);
View
17 docs/compress.html
@@ -2,21 +2,17 @@
gzip: zlib.createGzip
, deflate: zlib.createDeflate
};</code></pre></div><div id="exports.filter" class="comment"><h2>exports.filter()</h2><div class="description"><p>Default filter function.</p></div><h3>Source</h3><pre><code>exports.filter = function(req, res){
- var type = res.getHeader('Content-Type') || '';
- return type.match(/json|text|javascript/);
+ return /json|text|javascript/.test(res.getHeader('Content-Type'));
};</code></pre></div><div id="module.exports" class="comment"><h2></h2><div class="description"><h2>Compress</h2>
<p>Compress response data with gzip/deflate.</p>
<h2>Filter</h2>
-<p>A <code>filter</code> callback function may be passed to</p>
-
-<h2>replace the default logic of</h2>
+<p>A <code>filter</code> callback function may be passed to<br /> replace the default logic of:</p>
<pre><code>exports.filter = function(req, res){
- var type = res.getHeader('Content-Type') || '';
- return type.match(/json|text|javascript/);
+ return /json|text|javascript/.test(res.getHeader('Content-Type'));
};
</code></pre>
@@ -50,7 +46,7 @@
res.write = function(chunk, encoding){
if (!this.headerSent) this._implicitHeader();
return stream
- ? stream.write(chunk, encoding)
+ ? stream.write(new Buffer(chunk, encoding))
: write.call(res, chunk, encoding);
};
@@ -103,7 +99,10 @@
stream.on('end', function(){
end.call(res);
});
-
+
+ stream.on('drain', function() {
+ res.emit('drain');
+ });
});
next();
View
13 docs/connect.html
@@ -1,16 +1,17 @@
-<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="exports.version" class="comment"><h2>exports.version</h2><div class="description"><p>Framework version.</p></div><h3>Source</h3><pre><code>exports.version = '2.0.0alpha1';</code></pre></div><div id="exports.proto" class="comment"><h2>exports.proto</h2><div class="description"><p>Expose the prototype.</p></div><h3>Source</h3><pre><code>exports.proto = proto;</code></pre></div><div id="exports.middleware" class="comment"><h2>exports.middleware</h2><div class="description"><p>Auto-load middleware getters.</p></div><h3>Source</h3><pre><code>exports.middleware = {};</code></pre></div><div id="exports.utils" class="comment"><h2>exports.utils</h2><div class="description"><p>Expose utilities.</p></div><h3>Source</h3><pre><code>exports.utils = utils;</code></pre></div><div id="createServer" class="comment"><h2>createServer()</h2><div class="description"><p>Create a new connect server.</p></div><ul class="tags"><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>function createServer() {
+<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="exports.version" class="comment"><h2>exports.version</h2><div class="description"><p>Framework version.</p></div><h3>Source</h3><pre><code>exports.version = '2.4.3';</code></pre></div><div id="exports.mime" class="comment"><h2>exports.mime</h2><div class="description"><p>Expose mime module.</p></div><h3>Source</h3><pre><code>exports.mime = require('./middleware/static').mime;</code></pre></div><div id="exports.proto" class="comment"><h2>exports.proto</h2><div class="description"><p>Expose the prototype.</p></div><h3>Source</h3><pre><code>exports.proto = proto;</code></pre></div><div id="exports.middleware" class="comment"><h2>exports.middleware</h2><div class="description"><p>Auto-load middleware getters.</p></div><h3>Source</h3><pre><code>exports.middleware = {};</code></pre></div><div id="exports.utils" class="comment"><h2>exports.utils</h2><div class="description"><p>Expose utilities.</p></div><h3>Source</h3><pre><code>exports.utils = utils;</code></pre></div><div id="createServer" class="comment"><h2>createServer()</h2><div class="description"><p>Create a new connect server.</p></div><ul class="tags"><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>function createServer() {
function app(req, res){ app.handle(req, res); }
utils.merge(app, proto);
utils.merge(app, EventEmitter.prototype);
app.route = '/';
- app.stack = [].slice.apply(arguments);
+ app.stack = [];
+ for (var i = 0; i < arguments.length; ++i) {
+ app.use(arguments[i]);
+ }
return app;
};</code></pre></div><div id="createServer.createServer" class="comment"><h2>createServer.createServer</h2><div class="description"><p>Support old <code>.createServer()</code> method.</p></div><h3>Source</h3><pre><code>createServer.createServer = createServer;</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><p>Auto-load bundled middleware with getters.</p></div><h3>Source</h3><pre><code>fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
if (!/\.js$/.test(filename)) return;
var name = basename(filename, '.js');
- function load(){
- return require('./middleware/' + name);
- }
+ function load(){ return require('./middleware/' + name); }
exports.middleware.__defineGetter__(name, load);
exports.__defineGetter__(name, load);
-});</code></pre></div></div><ul id="menu"><li><a href="#exports.version">exports.version</a></li><li><a href="#exports.proto">exports.proto</a></li><li><a href="#exports.middleware">exports.middleware</a></li><li><a href="#exports.utils">exports.utils</a></li><li><a href="#createServer">createServer()</a></li><li><a href="#createServer.createServer">createServer.createServer</a></li><li><a href="#"></a></li></ul></body></html>
+});</code></pre></div></div><ul id="menu"><li><a href="#exports.version">exports.version</a></li><li><a href="#exports.mime">exports.mime</a></li><li><a href="#exports.proto">exports.proto</a></li><li><a href="#exports.middleware">exports.middleware</a></li><li><a href="#exports.utils">exports.utils</a></li><li><a href="#createServer">createServer()</a></li><li><a href="#createServer.createServer">createServer.createServer</a></li><li><a href="#"></a></li></ul></body></html>
View
15 docs/cookieParser.html
@@ -1,29 +1,30 @@
<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="module.exports" class="comment"><h2></h2><div class="description"><h2>Cookie parser</h2>
-<p>Parse <em>Cookie</em> header and populate <code>req.cookies</code><br />with an object keyed by the cookie names. Optionally<br />you may enabled signed cookie support by passing<br />a <code>secret</code> string, which assigns <code>req.secret</code> so<br />it may be used by other middleware such as <code>session()</code>.</p>
+<p>Parse <em>Cookie</em> header and populate <code>req.cookies</code><br />with an object keyed by the cookie names. Optionally<br />you may enabled signed cookie support by passing<br />a <code>secret</code> string, which assigns <code>req.secret</code> so<br />it may be used by other middleware.</p>
<h2>Examples</h2>
<pre><code>connect()
- .use(connect.cookieParser('keyboard cat'))
+ .use(connect.cookieParser('optional secret string'))
.use(function(req, res, next){
res.end(JSON.stringify(req.cookies));
})
</code></pre></div><ul class="tags"><li><em>String</em> secret </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function cookieParser(secret){
return function cookieParser(req, res, next) {
- var cookie = req.headers.cookie;
if (req.cookies) return next();
+ var cookies = req.headers.cookie;
req.secret = secret;
req.cookies = {};
req.signedCookies = {};
-
- if (cookie) {
+
+ if (cookies) {
try {
- req.cookies = utils.parseCookie(cookie);
+ req.cookies = cookie.parse(cookies);
if (secret) {
req.signedCookies = utils.parseSignedCookies(req.cookies, secret);
- req.signedCookies = utils.parseJSONCookies(req.signedCookies);
+ var obj = utils.parseJSONCookies(req.signedCookies);
+ req.signedCookies = obj;
}
req.cookies = utils.parseJSONCookies(req.cookies);
} catch (err) {
View
62 docs/cookieSession.html
@@ -3,38 +3,67 @@
<p>Cookie session middleware.</p>
<pre><code> var app = connect();
- app.use(connect.cookieParser('tobo!'));
- app.use(connect.cookieSession({ cookie: { maxAge: 60 * 60 * 1000 }}));
+ app.use(connect.cookieParser());
+ app.use(connect.cookieSession({ secret: 'tobo!', cookie: { maxAge: 60 * 60 * 1000 }}));
</code></pre>
<h2>Options</h2>
<ul>
<li><code>key</code> cookie name defaulting to <code>connect.sess</code></li>
+<li><code>secret</code> prevents cookie tampering</li>
<li><code>cookie</code> session cookie settings, defaulting to <code>{ path: '/', httpOnly: true, maxAge: null }</code></li>
<li><code>proxy</code> trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")</li>
-</ul></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function cookieSession(options){
- // TODO: utilize Session/Cookie to unify API
- // TODO: only set-cookie on changes to the session data
+</ul>
+
+<h2>Clearing sessions</h2>
+
+<p>To clear the session simply set its value to <code>null</code>,<br /> <code>cookieSession()</code> will then respond with a 1970 Set-Cookie.</p>
+<pre><code>req.session = null;
+</code></pre></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function cookieSession(options){
+ // TODO: utilize Session/Cookie to unify API
var options = options || {}
, key = options.key || 'connect.sess'
- , cookie = options.cookie
, trustProxy = options.proxy;
return function cookieSession(req, res, next) {
- req.session = req.signedCookies[key] || {};
- req.session.cookie = new Cookie(req, cookie);
+
+ // req.secret is for backwards compatibility
+ var secret = options.secret || req.secret;
+ if (!secret) throw new Error('`secret` option required for cookie sessions');
+
+ // default session
+ req.session = {};
+ var cookie = req.session.cookie = new Cookie(options.cookie);
+
+ // pathname mismatch
+ if (0 != req.originalUrl.indexOf(cookie.path)) return next();
+
+ // cookieParser secret
+ if (!options.secret && req.secret) {
+ req.session = req.signedCookies[key] || {};
+ } else {
+ // TODO: refactor
+ var rawCookie = req.cookies[key];
+ if (rawCookie) {
+ var unsigned = utils.parseSignedCookie(rawCookie, secret);
+ if (unsigned) {
+ var originalHash = crc16(unsigned);
+ req.session = utils.parseJSONCookie(unsigned) || {};
+ }
+ }
+ }
res.on('header', function(){
// removed
if (!req.session) {
debug('clear session');
- res.setHeader('Set-Cookie', key + '=; expires=' + new Date(0).toUTCString());
+ cookie.expires = new Date(0);
+ res.setHeader('Set-Cookie', cookie.serialize(key, ''));
return;
}
- var cookie = req.session.cookie;
delete req.session.cookie;
// check security
@@ -45,12 +74,17 @@
// 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);
- val = utils.sign(val, req.secret);
- val = utils.serializeCookie(key, val, cookie);
- debug('cookie %j', cookie);
+
+ // compare hashes, no need to set-cookie if unchanged
+ if (originalHash == crc16(val)) return debug('unmodified session');
+
+ // set-cookie
+ val = 's:' + utils.sign(val, secret);
+ val = cookie.serialize(key, val);
+ debug('set-cookie %j', cookie);
res.setHeader('Set-Cookie', val);
});
View
4 docs/csrf.html
@@ -20,8 +20,8 @@
// generate CSRF token
var token = req.session._csrf || (req.session._csrf = utils.uid(24));
- // ignore GET & HEAD (for now)
- if ('GET' == req.method || 'HEAD' == req.method) return next();
+ // ignore these methods
+ if ('GET' == req.method || 'HEAD' == req.method || 'OPTIONS' == req.method) return next();
// determine value
var val = value(req);
View
2  docs/directory.html
@@ -19,6 +19,8 @@
, root = normalize(root);
return function directory(req, res, next) {
+ if ('GET' != req.method && 'HEAD' != req.method) return next();
+
var accept = req.headers.accept || 'text/plain'
, url = parse(req.url)
, dir = decodeURIComponent(url.pathname)
View
9 docs/favicon.html
@@ -10,27 +10,28 @@
<h2>Examples</h2>
-<h2>Serve default favicon</h2>
+<p>Serve default favicon:</p>
<pre><code>connect()
.use(connect.favicon())
</code></pre>
-<h2>Serve favicon before logging for brevity</h2>
+<p>Serve favicon before logging for brevity:</p>
<pre><code>connect()
.use(connect.favicon())
.use(connect.logger('dev'))
</code></pre>
-<h2>Serve custom favicon</h2>
+<p>Serve custom favicon:</p>
<pre><code>connect()
.use(connect.favicon('public/favicon.ico))
</code></pre></div><ul class="tags"><li><em>String</em> path </li><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function favicon(path, options){
var options = options || {}
, path = path || __dirname + '/../public/favicon.ico'
- , maxAge = options.maxAge || 86400000;
+ , maxAge = options.maxAge || 86400000
+ , icon; // favicon cache
return function favicon(req, res, next){
if ('/favicon.ico' == req.url) {
View
12 docs/index.html
@@ -18,6 +18,7 @@
<ul>
<li><a href="logger.html">logger</a> request logger with custom format support</li>
+<li><a href="timeout.html">timeout</a> request timeout support</li>
<li><a href="csrf.html">csrf</a> Cross-site request forgery protection</li>
<li><a href="compress.html">compress</a> Gzip compression middleware</li>
<li><a href="basicAuth.html">basicAuth</a> basic http authentication</li>
@@ -27,7 +28,7 @@
<li><a href="multipart.html">multipart</a> multipart/form-data parser</li>
<li><a href="cookieParser.html">cookieParser</a> cookie parser</li>
<li><a href="session.html">session</a> session management support with bundled MemoryStore</li>
-<li><a href="cookieSession.html">sessionSession</a> cookie-based session support</li>
+<li><a href="cookieSession.html">cookieSession</a> cookie-based session support</li>
<li><a href="methodOverride.html">methodOverride</a> faux HTTP method support</li>
<li><a href="responseTime.html">responseTime</a> calculates response-time and exposes via X-Response-Time</li>
<li><a href="staticCache.html">staticCache</a> memory cache layer for the static() middleware</li>
@@ -40,17 +41,10 @@
<li><a href="errorHandler.html">errorHandler</a> flexible error handler</li>
</ul>
-<h2>Internals</h2>
-
-<ul>
-<li>server <a href="proto.html">prototype</a></li>
-<li>connect <a href="utils.html">utilities</a></li>
-<li>node monkey <a href="patch.html">patches</a></li>
-</ul>
-
<h2>Links</h2>
<ul>
<li>list of <a href="https://github.com/senchalabs/connect/wiki">3rd-party</a> middleware</li>
<li>GitHub <a href="http://github.com/senchalabs/connect">repository</a></li>
+<li><a href="https://github.com/senchalabs/connect/blob/gh-pages/tests.md">test documentation</a></li>
</ul></div></div></div><ul id="menu"><li><a href="#"></a></li></ul></body></html>
View
57 docs/json.html
@@ -1,14 +1,29 @@
-<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="" class="comment"><h2></h2><div class="description"><h2>JSON</h2>
+<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="noop" class="comment"><h2>noop()</h2><div class="description"><p>noop middleware.</p></div><h3>Source</h3><pre><code>function noop(req, res, next) {
+ next();
+}</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><h2>JSON</h2>
+
+<p>Parse JSON request bodies, providing the<br />parsed object as <code>req.body</code>.</p>
+
+<h2>Options</h2>
+
+<ul>
+<li><code>strict</code> when <code>false</code> anything <code>JSON.parse()</code> accepts will be parsed</li>
+<li><code>reviver</code> used as the second "reviver" argument for JSON.parse</li>
+<li><code>limit</code> byte limit disabled by default</li>
+</ul></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>exports = module.exports = function(options){
+ var options = options || {}
+ , strict = options.strict === false
+ ? false
+ : true;
+
+ var limit = options.limit
+ ? _limit(options.limit)
+ : noop;
-<p>Parse JSON request bodies, providing the<br />parsed object as <code>req.body</code>.</p></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>exports = module.exports = function(options){
- options = options || {};
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();
@@ -16,18 +31,22 @@
req._body = true;
// parse
- var buf = '';
- req.setEncoding('utf8');
- req.on('data', function(chunk){ buf += chunk });
- req.on('end', function(){
- if ('{' != buf[0] && '[' != buf[0]) return next(utils.error(400));
- try {
- req.body = JSON.parse(buf);
- next();
- } catch (err){
- err.status = 400;
- next(err);
- }
+ limit(req, res, function(err){
+ if (err) return next(err);
+ var buf = '';
+ req.setEncoding('utf8');
+ req.on('data', function(chunk){ buf += chunk });
+ req.on('end', function(){
+ if (strict && '{' != buf[0] && '[' != buf[0]) return next(utils.error(400, 'invalid json'));
+ try {
+ req.body = JSON.parse(buf, options.reviver);
+ next();
+ } catch (err){
+ err.body = buf;
+ err.status = 400;
+ next(err);
+ }
+ });
});
}
-};</code></pre></div></div><ul id="menu"><li><a href="#"></a></li></ul></body></html>
+};</code></pre></div></div><ul id="menu"><li><a href="#noop">noop()</a></li><li><a href="#"></a></li></ul></body></html>
View
16 docs/limit.html
@@ -8,7 +8,7 @@
.use(connect.limit('5.5mb'))
.use(handleImageUpload)
</code></pre></div><ul class="tags"><li><em>Number | String</em> bytes </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function limit(bytes){
- if ('string' == typeof bytes) bytes = parse(bytes);
+ if ('string' == typeof bytes) bytes = utils.parseBytes(bytes);
if ('number' != typeof bytes) throw new Error('limit() bytes required');
return function limit(req, res, next){
var received = 0
@@ -31,16 +31,4 @@
next();
};
-};</code></pre></div><div id="parse" class="comment"><h2>parse()</h2><div class="description"><p>Parse byte <code>size</code> string.</p></div><ul class="tags"><li><em>String</em> size </li><li>returns <em>Number</em> </li></ul><h3>Source</h3><pre><code>function parse(size) {
- var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/)
- , n = parseFloat(parts[1])
- , type = parts[2];
-
- var map = {
- kb: 1024
- , mb: 1024 * 1024
- , gb: 1024 * 1024 * 1024
- };
-
- return map[type] * n;
-}</code></pre></div></div><ul id="menu"><li><a href="#module.exports"></a></li><li><a href="#parse">parse()</a></li></ul></body></html>
+};</code></pre></div></div><ul id="menu"><li><a href="#module.exports"></a></li></ul></body></html>
View
14 docs/logger.html
@@ -109,12 +109,6 @@
return function logger(req, res, next) {
req._startTime = new Date;
- // mount safety
- if (req._logging) return next();
-
- // flag as logging
- req._logging = true;
-
// immediate
if (immediate) {
var line = fmt(exports, req, res);
@@ -149,18 +143,24 @@
return this;
};</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><p>Default format.</p></div><h3>Source</h3><pre><code>exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><p>Short format.</p></div><h3>Source</h3><pre><code>exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><p>Tiny format.</p></div><h3>Source</h3><pre><code>exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><p>dev (colored)</p></div><h3>Source</h3><pre><code>exports.format('dev', function(tokens, req, res){
var status = res.statusCode
+ , len = parseInt(res.getHeader('Content-Length'), 10)
, color = 32;
if (status >= 500) color = 31
else if (status >= 400) color = 33
else if (status >= 300) color = 36;
+ len = isNaN(len)
+ ? ''
+ : len = ' - ' + bytes(len);
+
return '\033[90m' + req.method
+ ' ' + req.originalUrl + ' '
+ '\033[' + color + 'm' + res.statusCode
+ ' \033[90m'
+ (new Date - req._startTime)
- + 'ms\033[0m';
+ + 'ms' + len
+ + '\033[0m';
});</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><p>request url</p></div><h3>Source</h3><pre><code>exports.token('url', function(req){
return req.originalUrl;
});</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><p>request method</p></div><h3>Source</h3><pre><code>exports.token('method', function(req){
View
95 docs/multipart.html
@@ -1,4 +1,6 @@
-<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="" class="comment"><h2></h2><div class="description"><h2>Multipart</h2>
+<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="noop" class="comment"><h2>noop()</h2><div class="description"><p>noop middleware.</p></div><h3>Source</h3><pre><code>function noop(req, res, next) {
+ next();
+}</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><h2>Multipart</h2>
<p>Parse multipart/form-data request bodies,<br />providing the parsed object as <code>req.body</code><br />and <code>req.files</code>.</p>
@@ -7,8 +9,19 @@
<p>The options passed are merged with <a href="https://github.com/felixge/node-formidable">formidable</a>'s<br /> <code>IncomingForm</code> object, allowing you to configure the upload directory,<br /> size limits, etc. For example if you wish to change the upload dir do the following.</p>
<pre><code>app.use(connect.multipart({ uploadDir: path }));
-</code></pre></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>exports = module.exports = function(options){
+</code></pre>
+
+<h2>Options</h2>
+
+<ul>
+<li><code>limit</code> byte limit defaulting to none</li>
+</ul></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>exports = module.exports = function(options){
options = options || {};
+
+ var limit = options.limit
+ ? _limit(options.limit)
+ : noop;
+
return function multipart(req, res, next) {
if (req._body) return next();
req.body = req.body || {};
@@ -24,49 +37,53 @@
req._body = true;
// parse
- var form = new formidable.IncomingForm
- , data = {}
- , files = {}
- , done;
+ limit(req, res, function(err){
+ if (err) return next(err);
- Object.keys(options).forEach(function(key){
- form[key] = options[key];
- });
+ var form = new formidable.IncomingForm
+ , data = {}
+ , files = {}
+ , done;
- function ondata(name, val, data){
- if (Array.isArray(data[name])) {
- data[name].push(val);
- } else if (data[name]) {
- data[name] = [data[name], val];
- } else {
- data[name] = val;
- }
- }
+ Object.keys(options).forEach(function(key){
+ form[key] = options[key];
+ });
- form.on('field', function(name, val){
- ondata(name, val, data);
- });
+ function ondata(name, val, data){
+ if (Array.isArray(data[name])) {
+ data[name].push(val);
+ } else if (data[name]) {
+ data[name] = [data[name], val];
+ } else {
+ data[name] = val;
+ }
+ }
- form.on('file', function(name, val){
- ondata(name, val, files);
- });
+ form.on('field', function(name, val){
+ ondata(name, val, data);
+ });
- form.on('error', function(err){
- next(err);
- done = true;
- });
+ form.on('file', function(name, val){
+ ondata(name, val, files);
+ });
- form.on('end', function(){
- if (done) return;
- try {
- req.body = qs.parse(data);
- req.files = qs.parse(files);
- next();
- } catch (err) {
+ form.on('error', function(err){
next(err);
- }
- });
+ done = true;
+ });
+
+ form.on('end', function(){
+ if (done) return;
+ try {
+ req.body = qs.parse(data);
+ req.files = qs.parse(files);
+ next();
+ } catch (err) {
+ next(err);
+ }
+ });
- form.parse(req);
+ form.parse(req);
+ });
}
-};</code></pre></div></div><ul id="menu"><li><a href="#"></a></li></ul></body></html>
+};</code></pre></div></div><ul id="menu"><li><a href="#noop">noop()</a></li><li><a href="#"></a></li></ul></body></html>
View
18 docs/proto.html
@@ -49,6 +49,7 @@
}
// add the middleware
+ debug('use %s %s', route || '/', fn.name || 'anonymous');
this.stack.push({ route: route, handle: fn });
return this;
@@ -56,10 +57,17 @@
var stack = this.stack
, fqdn = ~req.url.indexOf('://')
, removed = ''
+ , slashAdded = false
, index = 0;
function next(err) {
var layer, path, status, c;
+
+ if (slashAdded) {
+ req.url = req.url.substr(1);
+ slashAdded = false;
+ }
+
req.url = removed + req.url;
req.originalUrl = req.originalUrl || req.url;
removed = '';
@@ -76,6 +84,7 @@
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;
@@ -93,6 +102,7 @@
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();
@@ -102,7 +112,7 @@
}
try {
- path = parse(req.url).pathname;
+ path = utils.parseUrl(req).pathname;
if (undefined == path) path = '/';
// skip this layer if the route doesn't match.
@@ -117,8 +127,12 @@
req.url = req.url.substr(removed.length);
// Ensure leading slash
- if (!fqdn && '/' != req.url[0]) req.url = '/' + req.url;
+ if (!fqdn && '/' != req.url[0]) {
+ req.url = '/' + req.url;
+ slashAdded = true;
+ }
+ debug('%s', layer.handle.name || 'anonymous');
var arity = layer.handle.length;
if (err) {
if (arity === 4) {
View
9 docs/query.html
@@ -13,9 +13,12 @@
<p>The <code>options</code> passed are provided to qs.parse function.</p></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function query(options){
return function query(req, res, next){
- req.query = ~req.url.indexOf('?')
- ? qs.parse(parse(req.url).query, options)
- : {};
+ if (!req.query) {
+ req.query = ~req.url.indexOf('?')
+ ? qs.parse(parse(req).query, options)
+ : {};
+ }
+
next();
};
};</code></pre></div></div><ul id="menu"><li><a href="#module.exports"></a></li></ul></body></html>
View
72 docs/session.html
@@ -3,7 +3,7 @@
exports.Session = Session;
exports.MemoryStore = MemoryStore;</code></pre></div><div id="warning" class="comment"><h2>warning</h2><div class="description"><p>Warning message for <code>MemoryStore</code> usage in production.</p></div><h3>Source</h3><pre><code>var warning = 'Warning: connection.session() MemoryStore is not\n'
+ 'designed for a production environment, as it will leak\n'
- + 'memory, and obviously only work within a single process.';</code></pre></div><div id="session" class="comment"><h2>session()</h2><div class="description"><h2>Session</h2>
+ + 'memory, and will not scale past a single process.';</code></pre></div><div id="session" class="comment"><h2>session()</h2><div class="description"><h2>Session</h2>
<p>Setup session store with the given <code>options</code>.</p>
@@ -12,8 +12,8 @@
<h2>Examples</h2>
<pre><code>connect()
- .use(connect.cookieParser('keyboard cat'))
- .use(connect.session({ key: 'sid', cookie: { secure: true }}))
+ .use(connect.cookieParser())
+ .use(connect.session({ secret: 'keyboard cat', key: 'sid', cookie: { secure: true }}))
</code></pre>
<h2>Options</h2>
@@ -21,6 +21,7 @@
<ul>
<li><code>key</code> cookie name defaulting to <code>connect.sid</code></li>
<li><code>store</code> session store instance</li>
+<li><code>secret</code> session cookie is signed with this secret to prevent tampering</li>
<li><code>cookie</code> session cookie settings, defaulting to <code>{ path: '/', httpOnly: true, maxAge: null }</code></li>
<li><code>proxy</code> trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")</li>
</ul>
@@ -35,8 +36,8 @@
<pre><code> connect()
.use(connect.favicon())
- .use(connect.cookieParser('keyboard cat'))
- .use(connect.session({ cookie: { maxAge: 60000 }}))
+ .use(connect.cookieParser())
+ .use(connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
.use(function(req, res, next){
var sess = req.session;
if (sess.views) {
@@ -91,7 +92,7 @@
<h2>Session#touch()</h2>
-<p>Updates the <code>.maxAge</code>, and <code>.lastAccess</code> properties. Typically this is<br /> not necessary to call, as the session middleware does this for you.</p>
+<p>Updates the <code>.maxAge</code> property. Typically this is<br /> not necessary to call, as the session middleware does this for you.</p>
<h2>Session#cookie</h2>
@@ -106,7 +107,7 @@
req.session.cookie.maxAge = hour;
</code></pre>
-<p>For example when <code>maxAge</code> is set to <code>60000</code> (one minute), and 30 seconds<br />has elapsed it will return <code>30000</code> until the current request has completed,<br />at which time <code>req.session.touch()</code> is called to update <code>req.session.lastAccess</code>,<br />and reset <code>req.session.maxAge</code> to its original value.</p>
+<p>For example when <code>maxAge</code> is set to <code>60000</code> (one minute), and 30 seconds<br />has elapsed it will return <code>30000</code> until the current request has completed,<br />at which time <code>req.session.touch()</code> is called to reset <code>req.session.maxAge</code><br />to its original value.</p>
<pre><code>req.session.cookie.maxAge;
// =&gt; 30000
@@ -133,7 +134,7 @@
var options = options || {}
, key = options.key || 'connect.sid'
, store = options.store || new MemoryStore
- , cookie = options.cookie
+ , cookie = options.cookie || {}
, trustProxy = options.proxy;
// notify user that this store is not
@@ -146,40 +147,65 @@
store.generate = function(req){
req.sessionID = utils.uid(24);
req.session = new Session(req);
- req.session.cookie = new Cookie(req, cookie);
+ req.session.cookie = new Cookie(cookie);
};
return function session(req, res, next) {
// self-awareness
if (req.session) return next();
+ // pathname mismatch
+ if (0 != req.originalUrl.indexOf(cookie.path || '/')) return next();
+
+ // backwards compatibility for signed cookies
+ // req.secret is passed from the cookie parser middleware
+ var secret = options.secret || req.secret;
+
// ensure secret is available or bail
- if (!req.secret) throw new Error('connect.cookieParser("secret") required for security when using sessions');
+ if (!secret) throw new Error('`secret` option required for sessions');
// parse url
- var url = parse(req.url)
+ var url = parse(req)
, path = url.pathname
- , sessionIsNew;
+ , originalHash;
// expose store
req.sessionStore = store;
+ // grab the session cookie value and check the signature
+ var rawCookie = req.cookies[key];
+
+ // get signedCookies for backwards compat with signed cookies
+ var unsignedCookie = req.signedCookies[key];
+
+ if (!unsignedCookie && rawCookie) {
+ unsignedCookie = utils.parseSignedCookie(rawCookie, secret);
+ }
+
// set-cookie
res.on('header', function(){
if (!req.session) return;
var cookie = req.session.cookie
, proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
, tls = req.connection.encrypted || (trustProxy && 'https' == proto)
- , secured = cookie.secure && tls;
-
- // browser-session cookies only set-cookie once
- if (null == cookie.expires && !sessionIsNew) return;
+ , secured = cookie.secure && tls
+ , isNew = unsignedCookie != req.sessionID;
// only send secure cookies via https
if (cookie.secure && !secured) return debug('not secured');
- debug('set %s to %s', key, req.sessionID);
- res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID));
+ // browser-session length cookie
+ if (null == cookie.expires) {
+ if (!isNew) return debug('already set browser-session cookie');
+ // compare hashes
+ } else if (originalHash == hash(req.session)) {
+ return debug('unmodified session');
+ }
+
+ var val = 's:' + utils.sign(req.sessionID, secret);
+ val = cookie.serialize(key, val);
+ debug('set-cookie %s', val);
+ res.setHeader('Set-Cookie', val);
});
// proxy end() to commit the session
@@ -197,12 +223,11 @@
// generate the session
function generate() {
- sessionIsNew = true;
store.generate(req);
}
// get the sessionID from the cookie
- req.sessionID = req.signedCookies[key];
+ req.sessionID = unsignedCookie;
// generate a session if the browser doesn't send a sessionID
if (!req.sessionID) {
@@ -241,8 +266,13 @@
} else {
debug('session found');
store.createSession(req, sess);
+ originalHash = hash(sess);
next();
}
});
};
-};</code></pre></div></div><ul id="menu"><li><a href="#"></a></li><li><a href="#exports.Store">exports.Store</a></li><li><a href="#warning">warning</a></li><li><a href="#session">session()</a></li></ul></body></html>
+};</code></pre></div><div id="hash" class="comment"><h2>hash()</h2><div class="description"><p>Hash the given <code>sess</code> object omitting changes<br />to <code>.cookie</code>.</p></div><ul class="tags"><li><em>Object</em> sess </li><li>returns <em>String</em> </li></ul><h3>Source</h3><pre><code>function hash(sess) {
+ return crc16(JSON.stringify(sess, function(key, val){
+ if ('cookie' != key) return val;
+ }));
+}</code></pre></div></div><ul id="menu"><li><a href="#"></a></li><li><a href="#exports.Store">exports.Store</a></li><li><a href="#warning">warning</a></li><li><a href="#session">session()</a></li><li><a href="#hash">hash()</a></li></ul></body></html>
View
175 docs/static.html
@@ -16,168 +16,43 @@
<h2>Options</h2>
<ul>
-<li><code>maxAge</code> Browser cache maxAge in milliseconds. defaults to 0</li>
-<li><code>hidden</code> Allow transfer of hidden files. defaults to false</li>
-<li><code>redirect</code> Redirect to trailing "/" when the pathname is a dir</li>
+<li><code>maxAge</code> Browser cache maxAge in milliseconds. defaults to 0</li>
+<li><code>hidden</code> Allow transfer of hidden files. defaults to false</li>
+<li><code>redirect</code> Redirect to trailing "/" when the pathname is a dir. defaults to true</li>
</ul></div><ul class="tags"><li><em>String</em> root </li><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>exports = module.exports = function static(root, options){
options = options || {};
// root required
if (!root) throw new Error('static() root path required');
- options.root = root;
- return function static(req, res, next) {
- options.path = req.url;
- options.getOnly = true;
- send(req, res, next, options);
- };
-};</code></pre></div><div id="exports.mime" class="comment"><h2>exports.mime</h2><div class="description"><p>Expose mime module.</p>
-
-<p>If you wish to extend the mime table use this<br />reference to the "mime" module in the npm registry.</p></div><h3>Source</h3><pre><code>exports.mime = mime;</code></pre></div><div id="decode" class="comment"><h2>decode()</h2><div class="description"><p>decodeURIComponent.</p>
-
-<p>Allows V8 to only deoptimize this fn instead of all<br />of send().</p></div><ul class="tags"><li><em>String</em> path </li></ul><h3>Source</h3><pre><code>function decode(path){
- try {
- return decodeURIComponent(path);
- } catch (err) {
- return err;
- }
-}</code></pre></div><div id="send" class="comment"><h2>send</h2><div class="description"><p>Attempt to tranfer the requested file to <code>res</code>.</p></div><ul class="tags"><li><em>ServerRequest</em> </li><li><em>ServerResponse</em> </li><li><em>Function</em> next </li><li><em>Object</em> options </li></ul><h3>Source</h3><pre><code>var send = exports.send = function(req, res, next, options){
- options = options || {};
- if (!options.path) throw new Error('path required');
-
- // setup
- var maxAge = options.maxAge || 0
- , ranges = req.headers.range
- , head = 'HEAD' == req.method
- , get = 'GET' == req.method
- , root = options.root ? normalize(options.root) : null
- , redirect = false === options.redirect ? false : true
- , getOnly = options.getOnly
- , fn = options.callback
- , hidden = options.hidden
- , done;
-
- // replace next() with callback when available
- if (fn) next = fn;
-
- // ignore non-GET requests
- if (getOnly && !get && !head) return next();
-
- // parse url
- var url = parse(options.path)
- , path = decode(url.pathname)
- , type;
-
- if (path instanceof URIError) return next(utils.error(400));
-
- // null byte(s)
- if (~path.indexOf('\0')) return next(utils.error(400));
-
- // when root is not given, consider .. malicious
- if (!root && ~path.indexOf('..')) return next(utils.error(403));
-
- // index.html support
- if (normalize('/') == path[path.length - 1]) path += 'index.html';
-
- // join / normalize from optional root dir
- path = normalize(join(root, path));
+ // default redirect
+ var redirect = false === options.redirect ? false : true;
- // malicious path
- if (root && 0 != path.indexOf(root)) return next(utils.error(403));
-
- // "hidden" file
- if (!hidden && '.' == basename(path)[0]) return next();
-
- fs.stat(path, function(err, stat){
- // mime type
- type = mime.lookup(path);
+ return function static(req, res, next) {
+ if ('GET' != req.method && 'HEAD' != req.method) return next();
+ var path = parse(req).pathname;
- // ignore ENOENT
- if (err) {
- if (fn) return fn(err);
- return ('ENOENT' == err.code || 'ENAMETOOLONG' == err.code)
- ? next()
- : next(err);
- // redirect directory in case index.html is present
- } else if (stat.isDirectory()) {
+ function directory() {
if (!redirect) return next();
+ var pathname = url.parse(req.originalUrl).pathname;
res.statusCode = 301;
- res.setHeader('Location', url.pathname + '/');
- res.end('Redirecting to ' + url.pathname + '/');
- return;
+ res.setHeader('Location', pathname + '/');
+ res.end('Redirecting to ' + utils.escape(pathname) + '/');
}
- // header fields
- if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString());
- if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000));
- if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString());
- if (!res.getHeader('Content-Type')) {
- var charset = mime.charsets.lookup(type);
- res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
+ function error(err) {
+ if (404 == err.status) return next();
+ next(err);
}
- res.setHeader('Accept-Ranges', 'bytes');
- // conditional GET support
- if (utils.conditionalGET(req)) {
- if (!utils.modified(req, res)) {
- req.emit('static');
- return utils.notModified(res);
- }
- }
-
- var opts = {}
- , len = stat.size;
-
- // we have a Range request
- if (ranges) {
- ranges = utils.parseRange(stat.size, ranges);
- // valid
- if (ranges) {
- // TODO: stream options
- // TODO: multiple support
- opts.start = ranges[0].start;
- opts.end = ranges[0].end;
- len = Math.min(len, opts.end - opts.start + 1);
- res.statusCode = 206;
- res.setHeader('Content-Range', 'bytes '
- + opts.start
- + '-'
- + opts.end
- + '/'
- + stat.size);
- // invalid range
- } else {
- return next(utils.error(416));
- }
- }
-
- res.setHeader('Content-Length', len);
-
- // transfer
- if (head) return res.end();
-
- // stream
- var stream = fs.createReadStream(path, opts);
- req.emit('static', stream);
- req.on('close', stream.destroy.bind(stream));
- stream.pipe(res);
+ send(req, path)
+ .maxage(options.maxAge || 0)
+ .root(root)
+ .hidden(options.hidden)
+ .on('error', error)
+ .on('directory', directory)
+ .pipe(res);
+ };
+};</code></pre></div><div id="exports.mime" class="comment"><h2>exports.mime</h2><div class="description"><p>Expose mime module.</p>
- // callback
- if (fn) {
- function callback(err) { done || fn(err); done = true }
- req.on('close', callback);
- req.socket.on('error', callback);
- stream.on('error', callback);
- stream.on('end', callback);
- } else {
- stream.on('error', function(err){
- if (res.headerSent) {
- console.error(err.stack);
- req.destroy();
- } else {
- next(err);
- }
- });
- }
- });
-};</code></pre></div></div><ul id="menu"><li><a href="#"></a></li><li><a href="#exports.mime">exports.mime</a></li><li><a href="#decode">decode()</a></li><li><a href="#send">send</a></li></ul></body></html>
+<p>If you wish to extend the mime table use this<br />reference to the "mime" module in the npm registry.</p></div><h3>Source</h3><pre><code>exports.mime = send.mime;</code></pre></div></div><ul id="menu"><li><a href="#"></a></li><li><a href="#exports.mime">exports.mime</a></li></ul></body></html>
View
159 docs/staticCache.html
@@ -18,27 +18,22 @@
<ul>
<li><code>maxObjects</code> max cache objects [128]</li>
<li><code>maxLength</code> max cache object length 256kb</li>
-</ul></div><ul class="tags"><li><em>Type</em> name </li><li>returns <em>Type</em> </li></ul><h3>Source</h3><pre><code>module.exports = function staticCache(options){
+</ul></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function staticCache(options){
var options = options || {}
, cache = new Cache(options.maxObjects || 128)
, maxlen = options.maxLength || 1024 * 256;
+ console.warn('connect.staticCache() is deprecated and will be removed in 3.0');
+ console.warn('use varnish or similar reverse proxy caches.');
+
return function staticCache(req, res, next){
- var path = url.parse(req.url).pathname
+ var key = cacheKey(req)
, ranges = req.headers.range
- , hit = cache.get(path)
- , hitCC
- , uaCC
- , header
- , age;
-
- function miss() {
- res.setHeader('X-Cache', 'MISS');
- next();
- }
+ , hasCookies = req.headers.cookie
+ , hit = cache.get(key);
// cache static
- // TODO: change from staticCache() -> static()
+ // TODO: change from staticCache() -> cache()
// and make this work for any request
req.on('static', function(stream){
var headers = res._headers
@@ -46,9 +41,18 @@
, contentLength = headers['content-length']
, hit;
+ // dont cache set-cookie responses
+ if (headers['set-cookie']) return hasCookies = true;
+
+ // dont cache when cookies are present
+ if (hasCookies) return;
+
// 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']
@@ -57,12 +61,12 @@
|| cc['must-revalidate']) return;
// if already in cache then validate
- if (hit = cache.get(path)){
+ if (hit = cache.get(key)){
if (headers.etag == hit[0].etag) {
hit[0].date = new Date;
return;
} else {
- cache.remove(path);
+ cache.remove(key);
}
}
@@ -70,8 +74,7 @@
if (null == stream) return;
// add the cache object
- var arr = cache.add(path);
- arr.push(headers);
+ var arr = [];
// store the chunks
stream.on('data', function(chunk){
@@ -80,63 +83,85 @@
// flag it as complete
stream.on('end', function(){
- arr.complete = true;
+ var cacheEntry = cache.add(key);
+ delete headers['x-cache']; // Clean up (TODO: others)
+ cacheEntry.push(200);
+ cacheEntry.push(headers);
+ cacheEntry.push.apply(cacheEntry, arr);
});
});
- // cache hit, doesnt support range requests
- if (hit && hit.complete && !ranges) {
- header = utils.merge({}, hit[0]);
- header.Age = age = (new Date - new Date(header.date)) / 1000 | 0;
- header.date = new Date().toUTCString();
-
- // parse cache-controls
- hitCC = utils.parseCacheControl(header['cache-control'] || '');
- uaCC = utils.parseCacheControl(req.headers['cache-control'] || '');
+ if (req.method == 'GET' || req.method == 'HEAD') {
+ if (ranges) {
+ next();
+ } else if (!hasCookies && hit && !mustRevalidate(req, hit)) {
+ res.setHeader('X-Cache', 'HIT');
+ respondFromCache(req, res, hit);
+ } else {
+ res.setHeader('X-Cache', 'MISS');
+ next();
+ }
+ } else {
+ next();
+ }
+ }
+};</code></pre></div><div id="respondFromCache" class="comment"><h2>respondFromCache()</h2><div class="description"><p>Respond with the provided cached value.<br />TODO: Assume 200 code, that's iffy.</p></div><ul class="tags"><li><em>Object</em> req </li><li><em>Object</em> res </li><li><em>Object</em> cacheEntry </li><li>returns <em>String</em> </li></ul><h3>Source</h3><pre><code>function respondFromCache(req, res, cacheEntry) {
+ var status = cacheEntry[0]
+ , headers = utils.merge({}, cacheEntry[1])
+ , content = cacheEntry.slice(2);
+
+ headers.age = (new Date - new Date(headers.date)) / 1000 || 0;
+
+ switch (req.method) {
+ case 'HEAD':
+ res.writeHead(status, headers);
+ res.end();
+ break;
+ case 'GET':
+ if (utils.conditionalGET(req) && fresh(req.headers, headers)) {
+ headers['content-length'] = 0;
+ res.writeHead(304, headers);
+ res.end();
+ } else {
+ res.writeHead(status, headers);
+
+ function write() {
+ while (content.length) {
+ if (false === res.write(content.shift())) {
+ res.once('drain', write);
+ return;
+ }
+ }
+ res.end();
+ }
- // check if we must revalidate(bypass)
- if (hitCC['no-cache'] || uaCC['no-cache']) return miss();
+ write();
+ }
+ break;
+ default:
+ // This should never happen.
+ res.writeHead(500, '');
+ res.end();
+ }
+}</code></pre></div><div id="mustRevalidate" class="comment"><h2>mustRevalidate()</h2><div class="description"><p>Determine whether or not a cached value must be revalidated.</p></div><ul class="tags"><li><em>Object</em> req </li><li><em>Object</em> cacheEntry </li><li>returns <em>String</em> </li></ul><h3>Source</h3><pre><code>function mustRevalidate(req, cacheEntry) {
+ var cacheHeaders = cacheEntry[1]
+ , reqCC = utils.parseCacheControl(req.headers['cache-control'] || '')
+ , cacheCC = utils.parseCacheControl(cacheHeaders['cache-control'] || '')
+ , cacheAge = (new Date - new Date(cacheHeaders.date)) / 1000 || 0;
- // check freshness of entity
- if (isStale(hitCC, age) || isStale(uaCC, age)) return miss();
+ if ( cacheCC['no-cache']
+ || cacheCC['must-revalidate']
+ || cacheCC['proxy-revalidate']) return true;
- // conditional GET support
- if (utils.conditionalGET(req)) {
- if (!utils.modified(req, res, header)) {
- header['content-length'] = 0;
- res.writeHead(304, header);
- return res.end();
- }
- }
+ if (reqCC['no-cache']) return true
- // HEAD support
- if ('HEAD' == req.method) {
- res.writeHead(200, header);
- return res.end();
- }
+ if (null != reqCC['max-age']) return reqCC['max-age'] < cacheAge;
- // respond with cache
- header['x-cache'] = 'HIT';
- res.writeHead(200, header);
-
- // backpressure
- function write(i) {
- var buf = hit[i];
- if (!buf) return res.end();
- if (false === res.write(buf)) {
- res.once('drain', function(){
- write(++i);
- });
- } else {
- write(++i);
- }
- }
+ if (null != cacheCC['max-age']) return cacheCC['max-age'] < cacheAge;
- return write(1);
- }
+ return false;
+}</code></pre></div><div id="cacheKey" class="comment"><h2>cacheKey()</h2><div class="description"><p>The key to use in the cache. For now, this is the URL path and query.</p>
- miss();
- }
-};</code></pre></div><div id="isStale" class="comment"><h2>isStale()</h2><div class="description"><p>Check if cache item is stale</p></div><ul class="tags"><li><em>Object</em> cc </li><li><em>Number</em> age </li><li>returns <em>Boolean</em> </li></ul><h3>Source</h3><pre><code>function isStale(cc, age) {
- return cc['max-age'] && cc['max-age'] <= age;
-}</code></pre></div></div><ul id="menu"><li><a href="#module.exports"></a></li><li><a href="#isStale">isStale()</a></li></ul></body></html>
+<p>'<a href='http://example.com?key=value'>http://example.com?key=value</a>' -> '/?key=value'</p></div><ul class="tags"><li><em>Object</em> req </li><li>returns <em>String</em> </li></ul><h3>Source</h3><pre><code>function cacheKey(req) {
+ return utils.parseUrl(req).path;
+}</code></pre></div></div><ul id="menu"><li><a href="#module.exports"></a></li><li><a href="#respondFromCache">respondFromCache()</a></li><li><a href="#mustRevalidate">mustRevalidate()</a></li><li><a href="#cacheKey">cacheKey()</a></li></ul></body></html>
View
50 docs/urlencoded.html
@@ -1,14 +1,24 @@
-<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="" class="comment"><h2></h2><div class="description"><h2>Urlencoded</h2>
+<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="noop" class="comment"><h2>noop()</h2><div class="description"><p>noop middleware.</p></div><h3>Source</h3><pre><code>function noop(req, res, next) {
+ next();
+}</code></pre></div><div id="" class="comment"><h2></h2><div class="description"><h2>Urlencoded</h2>
-<p>Parse x-ww-form-urlencoded request bodies,<br /> providing the parsed object as <code>req.body</code>.</p></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>exports = module.exports = function(options){
+<p>Parse x-ww-form-urlencoded request bodies,<br /> providing the parsed object as <code>req.body</code>.</p>
+
+<h2>Options</h2>
+
+<ul>
+<li><code>limit</code> byte limit disabled by default</li>
+</ul></div><ul class="tags"><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>exports = module.exports = function(options){
options = options || {};
+
+ var limit = options.limit
+ ? _limit(options.limit)
+ : noop;
+
return function urlencoded(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/x-www-form-urlencoded' != utils.mime(req)) return next();
@@ -16,18 +26,22 @@
req._body = true;
// parse
- var buf = '';
- req.setEncoding('utf8');
- req.on('data', function(chunk){ buf += chunk });
- req.on('end', function(){
- try {
- req.body = buf.length
- ? qs.parse(buf, options)
- : {};
- next();
- } catch (err){
- next(err);
- }
+ limit(req, res, function(err){
+ if (err) return next(err);
+ var buf = '';
+ req.setEncoding('utf8');
+ req.on('data', function(chunk){ buf += chunk });
+ req.on('end', function(){
+ try {
+ req.body = buf.length
+ ? qs.parse(buf, options)
+ : {};
+ next();
+ } catch (err){
+ err.body = buf;
+ next(err);
+ }
+ });
});
}
-};</code></pre></div></div><ul id="menu"><li><a href="#"></a></li></ul></body></html>
+};</code></pre></div></div><ul id="menu"><li><a href="#noop">noop()</a></li><li><a href="#"></a></li></ul></body></html>
View
174 docs/utils.html
@@ -1,8 +1,8 @@
<!DOCTYPE html><html><head><title>Connect - High quality middleware for node.js</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" href="style.css"><script src="jquery.js"></script><script src="docs.js"></script></head><body><div id="content"><h1>Connect</h1><div id="exports.mime" class="comment"><h2>exports.mime()</h2><div class="description"><p>Extract the mime type from the given request's<br /><em>Content-Type</em> header.</p></div><ul class="tags"><li><em>IncomingMessage</em> req </li><li>returns <em>String</em> </li></ul><h3>Source</h3><pre><code>exports.mime = function(req) {
var str = req.headers['content-type'] || '';
return str.split(';')[0];
-};</code></pre></div><div id="exports.error" class="comment"><h2>exports.error()</h2><div class="description"><p>Generate an <code>Error</code> from the given status <code>code</code>.</p></div><ul class="tags"><li><em>Number</em> code </li><li>returns <em>Error</em> </li></ul><h3>Source</h3><pre><code>exports.error = function(code){
- var err = new Error(http.STATUS_CODES[code]);
+};</code></pre></div><div id="exports.error" class="comment"><h2>exports.error()</h2><div class="description"><p>Generate an <code>Error</code> from the given status <code>code</code><br />and optional <code>msg</code>.</p></div><ul class="tags"><li><em>Number</em> code </li><li><em>String</em> msg </li><li>returns <em>Error</em> </li></ul><h3>Source</h3><pre><code>exports.error = function(code, msg){
+ var err = new Error(msg || http.STATUS_CODES[code]);
err.status = code;
return err;
};</code></pre></div><div id="exports.md5" class="comment"><h2>exports.md5()</h2><div class="description"><p>Return md5 hash of the given string and optional encoding,<br />defaulting to hex.</p>
@@ -44,141 +44,55 @@
.slice(0, len);
};</code></pre></div><div id="exports.sign" class="comment"><h2>exports.sign()</h2><div class="description"><p>Sign the given <code>val</code> with <code>secret</code>.</p></div><ul class="tags"><li><em>String</em> val </li><li><em>String</em> secret </li><li>returns <em>String</em> </li></ul><h3>Source</h3><pre><code>exports.sign = function(val, secret){
return val + '.' + crypto
- .createHmac('sha1', secret)
+ .createHmac('sha256', secret)
.update(val)
.digest('base64')
.replace(/=+$/, '');
};</code></pre></div><div id="exports.unsign" class="comment"><h2>exports.unsign()</h2><div class="description"><p>Unsign and decode the given <code>val</code> with <code>secret</code>,<br />returning <code>false</code> if the signature is invalid.</p></div><ul class="tags"><li><em>String</em> val </li><li><em>String</em> secret </li><li>returns <em>String | Boolean</em> </li></ul><h3>Source</h3><pre><code>exports.unsign = function(val, secret){
- var str = val.slice(0,val.lastIndexOf('.'));
+ var str = val.slice(0, val.lastIndexOf('.'));
return exports.sign(str, secret) == val
? str
: false;
};</code></pre></div><div id="exports.parseSignedCookies" class="comment"><h2>exports.parseSignedCookies()</h2><div class="description"><p>Parse signed cookies, returning an object<br />containing the decoded key/value pairs,<br />while removing the signed key from <code>obj</code>.</p></div><ul class="tags"><li><em>Object</em> obj </li><li>returns <em>Object</em> </li></ul><h3>Source</h3><pre><code>exports.parseSignedCookies = function(obj, secret){
var ret = {};
Object.keys(obj).forEach(function(key){
- var val = obj[key]
- , signed = exports.unsign(val, secret);
-
- if (signed) {
- ret[key] = signed;
- delete obj[key];
+ var val = obj[key];
+ if (0 == val.indexOf('s:')) {
+ val = exports.unsign(val.slice(2), secret);
+ if (val) {
+ ret[key] = val;
+ delete obj[key];
+ }
}
});
return ret;
+};</code></pre></div><div id="exports.parseSignedCookie" class="comment"><h2>exports.parseSignedCookie()</h2><div class="description"><p>Parse a signed cookie string, return the decoded value</p></div><ul class="tags"><li><em>String</em> str signed cookie string</li><li><em>String</em> secret </li><li>returns <em>String</em> decoded value</li></ul><h3>Source</h3><pre><code>exports.parseSignedCookie = function(str, secret){
+ return 0 == str.indexOf('s:')
+ ? exports.unsign(str.slice(2), secret)
+ : str;
};</code></pre></div><div id="exports.parseJSONCookies" class="comment"><h2>exports.parseJSONCookies()</h2><div class="description"><p>Parse JSON cookies.</p></div><ul class="tags"><li><em>Object</em> obj </li><li>returns <em>Object</em> </li></ul><h3>Source</h3><pre><code>exports.parseJSONCookies = function(obj){
Object.keys(obj).forEach(function(key){
var val = obj[key];
- if (0 == val.indexOf('j:')) {
- try {
- obj[key] = JSON.parse(val.slice(2));
- } catch (err) {
- // nothing
- }
- }
+ var res = exports.parseJSONCookie(val);
+ if (res) obj[key] = res;
});
return obj;
-};</code></pre></div><div id="exports.parseCookie" class="comment"><h2>exports.parseCookie()</h2><div class="description"><p>Parse the given cookie string into an object.</p></div><ul class="tags"><li><em>String</em> str </li><li>returns <em>Object</em> </li></ul><h3>Source</h3><pre><code>exports.parseCookie = function(str){
- var obj = {}
- , pairs = str.split(/[;,] */);
- for (var i = 0, len = pairs.length; i < len; ++i) {
- var pair = pairs[i]
- , eqlIndex = pair.indexOf('=')
- , key = pair.substr(0, eqlIndex).trim()
- , val = pair.substr(++eqlIndex, pair.length).trim();
-
- // quoted values
- if ('"' == val[0]) val = val.slice(1, -1);
-
- // only assign once
- if (undefined == obj[key]) {
- val = val.replace(/\+/g, ' ');
- try {
- obj[key] = decodeURIComponent(val);
- } catch (err) {
- if (err instanceof URIError) {
- obj[key] = val;
- } else {
- throw err;
- }
- }
+};</code></pre></div><div id="exports.parseJSONCookie" class="comment"><h2>exports.parseJSONCookie()</h2><div class="description"><p>Parse JSON cookie string</p></div><ul class="tags"><li><em>String</em> str </li><li>returns <em>Object</em> Parsed object or null if not json cookie</li></ul><h3>Source</h3><pre><code>exports.parseJSONCookie = function(str) {
+ if (0 == str.indexOf('j:')) {
+ try {
+ return JSON.parse(str.slice(2));
+ } catch (err) {
+ // no op
}
}
- return obj;
-};</code></pre></div><div id="exports.serializeCookie" class="comment"><h2>exports.serializeCookie()</h2><div class="description"><p>Serialize the given object into a cookie string.</p>
-
-<pre><code> utils.serializeCookie('name', 'tj', { httpOnly: true })
- // =&gt; "name=tj; httpOnly"
-</code></pre></div><ul class="tags"><li><em>String</em> name </li><li><em>String</em> val </li><li><em>Object</em> obj </li><li>returns <em>String</em> </li></ul><h3>Source</h3><pre><code>exports.serializeCookie = function(name, val, obj){
- var pairs = [name + '=' + encodeURIComponent(val)]
- , obj = obj || {};
-
- if (obj.domain) pairs.push('domain=' + obj.domain);
- if (obj.path) pairs.push('path=' + obj.path);
- if (obj.expires) pairs.push('expires=' + obj.expires.toUTCString());
- if (obj.httpOnly) pairs.push('httpOnly');
- if (obj.secure) pairs.push('secure');
-
- return pairs.join('; ');
-};</code></pre></div><div id="exports.pause" class="comment"><h2>exports.pause()</h2><div class="description"><p>Pause <code>data</code> and <code>end</code> events on the given <code>obj</code>.<br />Middleware performing async tasks <em>should</em> utilize<br />this utility (or similar), to re-emit data once<br />the async operation has completed, otherwise these<br />events may be lost.</p>
+}</code></pre></div><div id="exports.pause" class="comment"><h2>exports.pause</h2><div class="description"><p>Pause <code>data</code> and <code>end</code> events on the given <code>obj</code>.<br />Middleware performing async tasks <em>should</em> utilize<br />this utility (or similar), to re-emit data once<br />the async operation has completed, otherwise these<br />events may be lost.</p>
<pre><code> var pause = utils.pause(req);
fs.readFile(path, function(){
next();
pause.resume();
});
-</code></pre></div><ul class="tags"><li><em>Object</em> obj </li><li>returns <em>Object</em> </li></ul><h3>Source</h3><pre><code>exports.pause = function(obj){
- var onData
- , onEnd
- , events = [];
-
- // buffer data
- obj.on('data', onData = function(data, encoding){
- events.push(['data', data, encoding]);
- });
-
- // buffer end
- obj.on('end', onEnd = function(data, encoding){
- events.push(['end', data, encoding]);
- });
-
- return {
- end: function(){
- obj.removeListener('data', onData);
- obj.removeListener('end', onEnd);
- },
- resume: function(){
- this.end();
- for (var i = 0, len = events.length; i < len; ++i) {
- obj.emit.apply(obj, events[i]);
- }
- }
- };
-};</code></pre></div><div id="exports.modified" class="comment"><h2>exports.modified()</h2><div class="description"><p>Check <code>req</code> and <code>res</code> to see if it has been modified.</p></div><ul class="tags"><li><em>IncomingMessage</em> req </li><li><em>ServerResponse</em> res </li><li>returns <em>Boolean</em> </li></ul><h3>Source</h3><pre><code>exports.modified = function(req, res, headers) {
- var headers = headers || res._headers || {}
- , modifiedSince = req.headers['if-modified-since']
- , lastModified = headers['last-modified']
- , noneMatch = req.headers['if-none-match']
- , etag = headers['etag'];
-
- if (noneMatch) noneMatch = noneMatch.split(/ *, */);
-
- // check If-None-Match
- if (noneMatch && etag && ~noneMatch.indexOf(etag)) {
- return false;
- }
-
- // check If-Modified-Since
- if (modifiedSince && lastModified) {
- modifiedSince = new Date(modifiedSince);
- lastModified = new Date(lastModified);
- // Ignore invalid dates
- if (!isNaN(modifiedSince.getTime())) {
- if (lastModified <= modifiedSince) return false;
- }
- }
-
- return true;
-};</code></pre></div><div id="exports.removeContentHeaders" class="comment"><h2>exports.removeContentHeaders()</h2><div class="description"><p>Strip <code>Content-*</code> headers from <code>res</code>.</p></div><ul class="tags"><li><em>ServerResponse</em> res </li></ul><h3>Source</h3><pre><code>exports.removeContentHeaders = function(res){
+</code></pre></div><ul class="tags"><li><em>Object</em> obj </li><li>returns <em>Object</em> </li></ul><h3>Source</h3><pre><code>exports.pause = require('pause');</code></pre></div><div id="exports.removeContentHeaders" class="comment"><h2>exports.removeContentHeaders()</h2><div class="description"><p>Strip <code>Content-*</code> headers from <code>res</code>.</p></div><ul class="tags"><li><em>ServerResponse</em> res </li></ul><h3>Source</h3><pre><code>exports.removeContentHeaders = function(res){
Object.keys(res._headers).forEach(function(field){
if (0 == field.indexOf('content')) {
res.removeHeader(field);
@@ -197,28 +111,6 @@
res.end();
};</code></pre></div><div id="exports.etag" class="comment"><h2>exports.etag()</h2><div class="description"><p>Return an ETag in the form of <code>"&lt;size&gt;-&lt;mtime&gt;"</code><br />from the given <code>stat</code>.</p></div><ul class="tags"><li><em>Object</em> stat </li><li>returns <em>String</em> </li></ul><h3>Source</h3><pre><code>exports.etag = function(stat) {
return '"' + stat.size + '-' + Number(stat.mtime) + '"';
-};</code></pre></div><div id="exports.parseRange" class="comment"><h2>exports.parseRange()</h2><div class="description"><p>Parse "Range" header <code>str</code> relative to the given file <code>size</code>.</p></div><ul class="tags"><li><em>Number</em> size </li><li><em>String</em> str </li><li>returns <em>Array</em> </li></ul><h3>Source</h3><pre><code>exports.parseRange = function(size, str){
- var valid = true;
- var arr = str.substr(6).split(',').map(function(range){
- var range = range.split('-')
- , start = parseInt(range[0], 10)
- , end = parseInt(range[1], 10);
-
- // -500
- if (isNaN(start)) {
- start = size - end;
- end = size - 1;
- // 500-
- } else if (isNaN(end)) {
- end = size - 1;
- }
-
- // Invalid
- if (isNaN(start) || isNaN(end) || start > end) valid = false;
-
- return { start: start, end: end };
- });
- return valid ? arr : undefined;
};</code></pre></div><div id="exports.parseCacheControl" class="comment"><h2>exports.parseCacheControl()</h2><div class="description"><p>Parse the given Cache-Control <code>str</code>.</p></div><ul class="tags"><li><em>String</em> str </li><li>returns <em>Object</em> </li></ul><h3>Source</h3><pre><code>exports.parseCacheControl = function(str){
var directives = str.split(',')
, obj = {};
@@ -232,13 +124,11 @@
}
return obj;
-};</code></pre></div><div id="toArray" class="comment"><h2>toArray</h2><div class="description"><p>Convert array-like object to an <code>Array</code>.</p>
-
-<p>node-bench measured "16.5 times faster than Array.prototype.slice.call()"</p></div><ul class="tags"><li><em>Object</em> obj </li><li>returns <em>Array</em> </li></ul><h3>Source</h3><pre><code>var toArray = exports.toArray = function(obj){
- var len = obj.length
- , arr = new Array(len);
- for (var i = 0; i < len; ++i) {
- arr[i] = obj[i];
+};</code></pre></div><div id="exports.parseUrl" class="comment"><h2>exports.parseUrl()</h2><div class="description"><p>Parse the <code>req</code> url with memoization.</p></div><ul class="tags"><li><em>ServerRequest</em> req </li><li>returns <em>Object</em> </li></ul><h3>Source</h3><pre><code>exports.parseUrl = function(req){
+ var parsed = req._parsedUrl;
+ if (parsed && parsed.href == req.url) {
+ return parsed;
+ } else {
+ return req._parsedUrl = parse(req.url);
}
- return arr;
-};</code></pre></div></div><ul id="menu"><li><a href="#exports.mime">exports.mime()</a></li><li><a href="#exports.error">exports.error()</a></li><li><a href="#exports.md5">exports.md5()</a></li><li><a href="#exports.merge">exports.merge()</a></li><li><a href="#exports.escape">exports.escape()</a></li><li><a href="#exports.uid">exports.uid()</a></li><li><a href="#exports.sign">exports.sign()</a></li><li><a href="#exports.unsign">exports.unsign()</a></li><li><a href="#exports.parseSignedCookies">exports.parseSignedCookies()</a></li><li><a href="#exports.parseJSONCookies">exports.parseJSONCookies()</a></li><li><a href="#exports.parseCookie">exports.parseCookie()</a></li><li><a href="#exports.serializeCookie">exports.serializeCookie()</a></li><li><a href="#exports.pause">exports.pause()</a></li><li><a href="#exports.modified">exports.modified()</a></li><li><a href="#exports.removeContentHeaders">exports.removeContentHeaders()</a></li><li><a href="#exports.conditionalGET">exports.conditionalGET()</a></li><li><a href="#exports.unauthorized">exports.unauthorized()</a></li><li><a href="#exports.notModified">exports.notModified()</a></li><li><a href="#exports.etag">exports.etag()</a></li><li><a href="#exports.parseRange">exports.parseRange()</a></li><li><a href="#exports.parseCacheControl">exports.parseCacheControl()</a></li><li><a href="#toArray">toArray</a></li></ul></body></html>
+};</code></pre></div><div id="exports.parseBytes" class="comment"><h2>exports.parseBytes</h2><div class="description"><p>Parse byte <code>size</code> string.</p></div><ul class="tags"><li><em>String</em> size </li><li>returns <em>Number</em> </li></ul><h3>Source</h3><pre><code>exports.parseBytes = require('bytes');</code></pre></div></div><ul id="menu"><li><a href="#exports.mime">exports.mime()</a></li><li><a href="#exports.error">exports.error()</a></li><li><a href="#exports.md5">exports.md5()</a></li><li><a href="#exports.merge">exports.merge()</a></li><li><a href="#exports.escape">exports.escape()</a></li><li><a href="#exports.uid">exports.uid()</a></li><li><a href="#exports.sign">exports.sign()</a></li><li><a href="#exports.unsign">exports.unsign()</a></li><li><a href="#exports.parseSignedCookies">exports.parseSignedCookies()</a></li><li><a href="#exports.parseSignedCookie">exports.parseSignedCookie()</a></li><li><a href="#exports.parseJSONCookies">exports.parseJSONCookies()</a></li><li><a href="#exports.parseJSONCookie">exports.parseJSONCookie()</a></li><li><a href="#exports.pause">exports.pause</a></li><li><a href="#exports.removeContentHeaders">exports.removeContentHeaders()</a></li><li><a href="#exports.conditionalGET">exports.conditionalGET()</a></li><li><a href="#exports.unauthorized">exports.unauthorized()</a></li><li><a href="#exports.notModified">exports.notModified()</a></li><li><a href="#exports.etag">exports.etag()</a></li><li><a href="#exports.parseCacheControl">exports.parseCacheControl()</a></li><li><a href="#exports.parseUrl">exports.parseUrl()</a></li><li><a href="#exports.parseBytes">exports.parseBytes</a></li></ul></body></html>
View
13 docs/vhost.html
@@ -6,7 +6,9 @@
.use(connect.vhost('foo.com', fooApp))
.use(connect.vhost('bar.com', barApp))
.use(connect.vhost('*.com', mainApp))
-</code></pre></div><ul class="tags"><li><em>String</em> hostname </li><li><em>Server</em> server </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function vhost(hostname, server){
+</code></pre>
+
+<p>The <code>server</code> may be a Connect server or<br /> a regular Node <code>http.Server</code>. </p></div><ul class="tags"><li><em>String</em> hostname </li><li><em>Server</em> server </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function vhost(hostname, server){
if (!hostname) throw new Error('vhost hostname required');
if (!server) throw new Error('vhost server required');
var regexp = new RegExp('^' + hostname.replace(/[*]/g, '(.*?)') + '$', 'i');
@@ -14,11 +16,8 @@
return function vhost(req, res, next){
if (!req.headers.host) return next();
var host = req.headers.host.split(':')[0];
- if (req.subdomains = regexp.exec(host)) {
- req.subdomains = req.subdomains[0].split('.').slice(0, -1);
- server.emit('request', req, res);
- } else {
- next();
- }
+ if (!regexp.test(host)) return next();
+ if ('function' == typeof server) return server(req, res, next);
+ server.emit('request', req, res);
};
};</code></pre></div></div><ul id="menu"><li><a href="#module.exports"></a></li></ul></body></html>
View
9 favicon.html
@@ -10,27 +10,28 @@
<h2>Examples</h2>
-<h2>Serve default favicon</h2>
+<p>Serve default favicon:</p>
<pre><code>connect()
.use(connect.favicon())
</code></pre>
-<h2>Serve favicon before logging for brevity</h2>
+<p>Serve favicon before logging for brevity:</p>
<pre><code>connect()
.use(connect.favicon())
.use(connect.logger('dev'))
</code></pre>
-<h2>Serve custom favicon</h2>
+<p>Serve custom favicon:</p>
<pre><code>connect()
.use(connect.favicon('public/favicon.ico))
</code></pre></div><ul class="tags"><li><em>String</em> path </li><li><em>Object</em> options </li><li>returns <em>Function</em> </li></ul><h3>Source</h3><pre><code>module.exports = function favicon(path, options){
var options = options || {}
, path = path || __dirname + '/../public/favicon.ico'
- , maxAge = options.maxAge || 86400000;
+ , maxAge = options.maxAge || 86400000
+ , icon; // favicon cache
return function favicon(req, res, next){
if ('/favicon.ico' == req.url) {
View
9 index.html
@@ -18,6 +18,7 @@
<ul>
<li><a href="logger.html">logger</a> request logger with custom format support</li>
+<li><a href="timeout.html">timeout</a> request timeout support</li>
<li><a href="csrf.html">csrf</a> Cross-site request forgery protection</li>
<li><a href="compress.html">compress</a> Gzip compression middleware</li>