Added node modules
|
@ -1,5 +1,4 @@
|
|||
npm-debug.log
|
||||
node_modules
|
||||
*.swp
|
||||
*.swo
|
||||
data
|
||||
|
|
|
@ -24,11 +24,8 @@
|
|||
},
|
||||
|
||||
"storage": {
|
||||
"type": "redis",
|
||||
"host": "localhost",
|
||||
"port": 6379,
|
||||
"db": 2,
|
||||
"expire": 2592000
|
||||
"type": "file",
|
||||
"path": "./data"
|
||||
},
|
||||
|
||||
"documents": {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../mocha/bin/_mocha
|
|
@ -0,0 +1 @@
|
|||
../mocha/bin/mocha
|
|
@ -0,0 +1 @@
|
|||
../uglify-js/bin/uglifyjs
|
|
@ -0,0 +1,11 @@
|
|||
*.markdown
|
||||
*.md
|
||||
.git*
|
||||
Makefile
|
||||
benchmarks/
|
||||
docs/
|
||||
examples/
|
||||
install.sh
|
||||
support/
|
||||
test/
|
||||
.DS_Store
|
|
@ -0,0 +1,24 @@
|
|||
(The MIT License)
|
||||
|
||||
Copyright (c) 2010 Sencha Inc.
|
||||
Copyright (c) 2011 LearnBoost
|
||||
Copyright (c) 2011 TJ Holowaychuk
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
module.exports = require('./lib/connect');
|
|
@ -0,0 +1,81 @@
|
|||
|
||||
/*!
|
||||
* Connect - Cache
|
||||
* Copyright(c) 2011 Sencha Inc.
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Expose `Cache`.
|
||||
*/
|
||||
|
||||
module.exports = Cache;
|
||||
|
||||
/**
|
||||
* LRU cache store.
|
||||
*
|
||||
* @param {Number} limit
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Cache(limit) {
|
||||
this.store = {};
|
||||
this.keys = [];
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch `key`, promoting the object.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Number} i
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Cache.prototype.touch = function(key, i){
|
||||
this.keys.splice(i,1);
|
||||
this.keys.push(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Cache.prototype.remove = function(key){
|
||||
delete this.store[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the object stored for `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Cache.prototype.get = function(key){
|
||||
return this.store[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a cache `key`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Cache.prototype.add = function(key){
|
||||
// initialize store
|
||||
var len = this.keys.push(key);
|
||||
|
||||
// limit reached, invalid LRU
|
||||
if (len > this.limit) this.remove(this.keys.shift());
|
||||
|
||||
var arr = this.store[key] = [];
|
||||
arr.createdAt = new Date;
|
||||
return arr;
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
|
||||
/*!
|
||||
* Connect
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var HTTPServer = require('./http').Server
|
||||
, HTTPSServer = require('./https').Server
|
||||
, fs = require('fs');
|
||||
|
||||
// node patches
|
||||
|
||||
require('./patch');
|
||||
|
||||
// expose createServer() as the module
|
||||
|
||||
exports = module.exports = createServer;
|
||||
|
||||
/**
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
exports.version = '1.8.5';
|
||||
|
||||
/**
|
||||
* Initialize a new `connect.HTTPServer` with the middleware
|
||||
* passed to this function. When an object is passed _first_,
|
||||
* we assume these are the tls options, and return a `connect.HTTPSServer`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* An example HTTP server, accepting several middleware.
|
||||
*
|
||||
* var server = connect.createServer(
|
||||
* connect.logger()
|
||||
* , connect.static(__dirname + '/public')
|
||||
* );
|
||||
*
|
||||
* An HTTPS server, utilizing the same middleware as above.
|
||||
*
|
||||
* var server = connect.createServer(
|
||||
* { key: key, cert: cert }
|
||||
* , connect.logger()
|
||||
* , connect.static(__dirname + '/public')
|
||||
* );
|
||||
*
|
||||
* Alternatively with connect 1.0 we may omit `createServer()`.
|
||||
*
|
||||
* connect(
|
||||
* connect.logger()
|
||||
* , connect.static(__dirname + '/public')
|
||||
* ).listen(3000);
|
||||
*
|
||||
* @param {Object|Function} ...
|
||||
* @return {Server}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function createServer() {
|
||||
if ('object' == typeof arguments[0]) {
|
||||
return new HTTPSServer(arguments[0], Array.prototype.slice.call(arguments, 1));
|
||||
} else {
|
||||
return new HTTPServer(Array.prototype.slice.call(arguments));
|
||||
}
|
||||
};
|
||||
|
||||
// support connect.createServer()
|
||||
|
||||
exports.createServer = createServer;
|
||||
|
||||
// auto-load getters
|
||||
|
||||
exports.middleware = {};
|
||||
|
||||
/**
|
||||
* Auto-load bundled middleware with getters.
|
||||
*/
|
||||
|
||||
fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
|
||||
if (/\.js$/.test(filename)) {
|
||||
var name = filename.substr(0, filename.lastIndexOf('.'));
|
||||
exports.middleware.__defineGetter__(name, function(){
|
||||
return require('./middleware/' + name);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// expose utils
|
||||
|
||||
exports.utils = require('./utils');
|
||||
|
||||
// expose getters as first-class exports
|
||||
|
||||
exports.utils.merge(exports, exports.middleware);
|
||||
|
||||
// expose constructors
|
||||
|
||||
exports.HTTPServer = HTTPServer;
|
||||
exports.HTTPSServer = HTTPSServer;
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
|
||||
/*!
|
||||
* Connect - HTTPServer
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, parse = require('url').parse
|
||||
, assert = require('assert');
|
||||
|
||||
// environment
|
||||
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
|
||||
/**
|
||||
* Initialize a new `Server` with the given `middleware`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* var server = connect.createServer(
|
||||
* connect.favicon()
|
||||
* , connect.logger()
|
||||
* , connect.static(__dirname + '/public')
|
||||
* );
|
||||
*
|
||||
* @params {Array} middleware
|
||||
* @return {Server}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var Server = exports.Server = function HTTPServer(middleware) {
|
||||
this.stack = [];
|
||||
middleware.forEach(function(fn){
|
||||
this.use(fn);
|
||||
}, this);
|
||||
http.Server.call(this, this.handle);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from `http.Server.prototype`.
|
||||
*/
|
||||
|
||||
Server.prototype.__proto__ = http.Server.prototype;
|
||||
|
||||
/**
|
||||
* Utilize the given middleware `handle` to the given `route`,
|
||||
* defaulting to _/_. This "route" is the mount-point for the
|
||||
* middleware, when given a value other than _/_ the middleware
|
||||
* is only effective when that segment is present in the request's
|
||||
* pathname.
|
||||
*
|
||||
* For example if we were to mount a function at _/admin_, it would
|
||||
* be invoked on _/admin_, and _/admin/settings_, however it would
|
||||
* not be invoked for _/_, or _/posts_.
|
||||
*
|
||||
* This is effectively the same as passing middleware to `connect.createServer()`,
|
||||
* however provides a progressive api.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* var server = connect.createServer();
|
||||
* server.use(connect.favicon());
|
||||
* server.use(connect.logger());
|
||||
* server.use(connect.static(__dirname + '/public'));
|
||||
*
|
||||
* If we wanted to prefix static files with _/public_, we could
|
||||
* "mount" the `static()` middleware:
|
||||
*
|
||||
* server.use('/public', connect.static(__dirname + '/public'));
|
||||
*
|
||||
* This api is chainable, meaning the following is valid:
|
||||
*
|
||||
* connect.createServer()
|
||||
* .use(connect.favicon())
|
||||
* .use(connect.logger())
|
||||
* .use(connect.static(__dirname + '/public'))
|
||||
* .listen(3000);
|
||||
*
|
||||
* @param {String|Function} route or handle
|
||||
* @param {Function} handle
|
||||
* @return {Server}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.use = function(route, handle){
|
||||
this.route = '/';
|
||||
|
||||
// default route to '/'
|
||||
if ('string' != typeof route) {
|
||||
handle = route;
|
||||
route = '/';
|
||||
}
|
||||
|
||||
// wrap sub-apps
|
||||
if ('function' == typeof handle.handle) {
|
||||
var server = handle;
|
||||
server.route = route;
|
||||
handle = function(req, res, next) {
|
||||
server.handle(req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
// wrap vanilla http.Servers
|
||||
if (handle instanceof http.Server) {
|
||||
handle = handle.listeners('request')[0];
|
||||
}
|
||||
|
||||
// normalize route to not trail with slash
|
||||
if ('/' == route[route.length - 1]) {
|
||||
route = route.substr(0, route.length - 1);
|
||||
}
|
||||
|
||||
// add the middleware
|
||||
this.stack.push({ route: route, handle: handle });
|
||||
|
||||
// allow chaining
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle server requests, punting them down
|
||||
* the middleware stack.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.handle = function(req, res, out) {
|
||||
var writeHead = res.writeHead
|
||||
, stack = this.stack
|
||||
, removed = ''
|
||||
, index = 0;
|
||||
|
||||
function next(err) {
|
||||
var layer, path, c;
|
||||
req.url = removed + req.url;
|
||||
req.originalUrl = req.originalUrl || req.url;
|
||||
removed = '';
|
||||
|
||||
layer = stack[index++];
|
||||
|
||||
// all done
|
||||
if (!layer || res.headerSent) {
|
||||
// but wait! we have a parent
|
||||
if (out) return out(err);
|
||||
|
||||
// error
|
||||
if (err) {
|
||||
var msg = 'production' == env
|
||||
? 'Internal Server Error'
|
||||
: err.stack || err.toString();
|
||||
|
||||
// output to stderr in a non-test env
|
||||
if ('test' != env) console.error(err.stack || err.toString());
|
||||
|
||||
// unable to respond
|
||||
if (res.headerSent) return req.socket.destroy();
|
||||
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
if ('HEAD' == req.method) return res.end();
|
||||
res.end(msg);
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
if ('HEAD' == req.method) return res.end();
|
||||
res.end('Cannot ' + req.method + ' ' + req.url);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
path = parse(req.url).pathname;
|
||||
if (undefined == path) path = '/';
|
||||
|
||||
// skip this layer if the route doesn't match.
|
||||
if (0 != path.indexOf(layer.route)) return next(err);
|
||||
|
||||
c = path[layer.route.length];
|
||||
if (c && '/' != c && '.' != c) return next(err);
|
||||
|
||||
// Call the layer handler
|
||||
// Trim off the part of the url that matches the route
|
||||
removed = layer.route;
|
||||
req.url = req.url.substr(removed.length);
|
||||
|
||||
// Ensure leading slash
|
||||
if ('/' != req.url[0]) req.url = '/' + req.url;
|
||||
|
||||
var arity = layer.handle.length;
|
||||
if (err) {
|
||||
if (arity === 4) {
|
||||
layer.handle(err, req, res, next);
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
} else if (arity < 4) {
|
||||
layer.handle(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof assert.AssertionError) {
|
||||
console.error(e.stack + '\n');
|
||||
next(e);
|
||||
} else {
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
/*!
|
||||
* Connect - HTTPServer
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var HTTPServer = require('./http').Server
|
||||
, https = require('https');
|
||||
|
||||
/**
|
||||
* Initialize a new `Server` with the given
|
||||
*`options` and `middleware`. The HTTPS api
|
||||
* is identical to the [HTTP](http.html) server,
|
||||
* however TLS `options` must be provided before
|
||||
* passing in the optional middleware.
|
||||
*
|
||||
* @params {Object} options
|
||||
* @params {Array} middleawre
|
||||
* @return {Server}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var Server = exports.Server = function HTTPSServer(options, middleware) {
|
||||
this.stack = [];
|
||||
middleware.forEach(function(fn){
|
||||
this.use(fn);
|
||||
}, this);
|
||||
https.Server.call(this, options, this.handle);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from `http.Server.prototype`.
|
||||
*/
|
||||
|
||||
Server.prototype.__proto__ = https.Server.prototype;
|
||||
|
||||
// mixin HTTPServer methods
|
||||
|
||||
Object.keys(HTTPServer.prototype).forEach(function(method){
|
||||
Server.prototype[method] = HTTPServer.prototype[method];
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
/**
|
||||
* # Connect
|
||||
*
|
||||
* Connect is a middleware framework for node,
|
||||
* shipping with over 11 bundled middleware and a rich choice of
|
||||
* [3rd-party middleware](https://github.com/senchalabs/connect/wiki).
|
||||
*
|
||||
* Installation:
|
||||
*
|
||||
* $ npm install connect
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* - [connect](connect.html) general
|
||||
* - [http](http.html) http server
|
||||
* - [https](https.html) https server
|
||||
*
|
||||
* Middleware:
|
||||
*
|
||||
* - [logger](middleware-logger.html) request logger with custom format support
|
||||
* - [csrf](middleware-csrf.html) Cross-site request forgery protection
|
||||
* - [basicAuth](middleware-basicAuth.html) basic http authentication
|
||||
* - [bodyParser](middleware-bodyParser.html) extensible request body parser
|
||||
* - [cookieParser](middleware-cookieParser.html) cookie parser
|
||||
* - [session](middleware-session.html) session management support with bundled [MemoryStore](middleware-session-memory.html)
|
||||
* - [compiler](middleware-compiler.html) static asset compiler (sass, less, coffee-script, etc)
|
||||
* - [methodOverride](middleware-methodOverride.html) faux HTTP method support
|
||||
* - [responseTime](middleware-responseTime.html) calculates response-time and exposes via X-Response-Time
|
||||
* - [router](middleware-router.html) provides rich Sinatra / Express-like routing
|
||||
* - [staticCache](middleware-staticCache.html) memory cache layer for the static() middleware
|
||||
* - [static](middleware-static.html) streaming static file server supporting `Range` and more
|
||||
* - [directory](middleware-directory.html) directory listing middleware
|
||||
* - [vhost](middleware-vhost.html) virtual host sub-domain mapping middleware
|
||||
* - [favicon](middleware-favicon.html) efficient favicon server (with default icon)
|
||||
* - [limit](middleware-limit.html) limit the bytesize of request bodies
|
||||
* - [profiler](middleware-profiler.html) request profiler reporting response-time, memory usage, etc
|
||||
* - [query](middleware-query.html) automatic querystring parser, populating `req.query`
|
||||
* - [errorHandler](middleware-errorHandler.html) flexible error handler
|
||||
*
|
||||
* Internals:
|
||||
*
|
||||
* - connect [utilities](utils.html)
|
||||
* - node monkey [patches](patch.html)
|
||||
*
|
||||
*/
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
/*!
|
||||
* Connect - basicAuth
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils')
|
||||
, unauthorized = utils.unauthorized
|
||||
, badRequest = utils.badRequest;
|
||||
|
||||
/**
|
||||
* Enfore basic authentication by providing a `callback(user, pass)`,
|
||||
* which must return `true` in order to gain access. Alternatively an async
|
||||
* method is provided as well, invoking `callback(user, pass, callback)`. Populates
|
||||
* `req.remoteUser`. The final alternative is simply passing username / password
|
||||
* strings.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect(connect.basicAuth('username', 'password'));
|
||||
*
|
||||
* connect(
|
||||
* connect.basicAuth(function(user, pass){
|
||||
* return 'tj' == user & 'wahoo' == pass;
|
||||
* })
|
||||
* );
|
||||
*
|
||||
* connect(
|
||||
* connect.basicAuth(function(user, pass, fn){
|
||||
* User.authenticate({ user: user, pass: pass }, fn);
|
||||
* })
|
||||
* );
|
||||
*
|
||||
* @param {Function|String} callback or username
|
||||
* @param {String} realm
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function basicAuth(callback, realm) {
|
||||
var username, password;
|
||||
|
||||
// user / pass strings
|
||||
if ('string' == typeof callback) {
|
||||
username = callback;
|
||||
password = realm;
|
||||
if ('string' != typeof password) throw new Error('password argument required');
|
||||
realm = arguments[2];
|
||||
callback = function(user, pass){
|
||||
return user == username && pass == password;
|
||||
}
|
||||
}
|
||||
|
||||
realm = realm || 'Authorization Required';
|
||||
|
||||
return function(req, res, next) {
|
||||
var authorization = req.headers.authorization;
|
||||
|
||||
if (req.remoteUser) return next();
|
||||
if (!authorization) return unauthorized(res, realm);
|
||||
|
||||
var parts = authorization.split(' ')
|
||||
, scheme = parts[0]
|
||||
, credentials = new Buffer(parts[1], 'base64').toString().split(':');
|
||||
|
||||
if ('Basic' != scheme) return badRequest(res);
|
||||
|
||||
// async
|
||||
if (callback.length >= 3) {
|
||||
var pause = utils.pause(req);
|
||||
callback(credentials[0], credentials[1], function(err, user){
|
||||
if (err || !user) return unauthorized(res, realm);
|
||||
req.remoteUser = user;
|
||||
next();
|
||||
pause.resume();
|
||||
});
|
||||
// sync
|
||||
} else {
|
||||
if (callback(credentials[0], credentials[1])) {
|
||||
req.remoteUser = credentials[0];
|
||||
next();
|
||||
} else {
|
||||
unauthorized(res, realm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
|
||||
/*!
|
||||
* Connect - bodyParser
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var qs = require('qs')
|
||||
, formidable = require('formidable');
|
||||
|
||||
/**
|
||||
* Extract the mime type from the given request's
|
||||
* _Content-Type_ header.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function mime(req) {
|
||||
var str = req.headers['content-type'] || '';
|
||||
return str.split(';')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse request bodies.
|
||||
*
|
||||
* By default _application/json_, _application/x-www-form-urlencoded_,
|
||||
* and _multipart/form-data_ are supported, however you may map `connect.bodyParser.parse[contentType]`
|
||||
* to a function receiving `(req, options, callback)`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect.createServer(
|
||||
* connect.bodyParser()
|
||||
* , function(req, res) {
|
||||
* res.end('viewing user ' + req.body.user.name);
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* $ curl -d 'user[name]=tj' http://localhost/
|
||||
* $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://localhost/
|
||||
*
|
||||
* Multipart req.files:
|
||||
*
|
||||
* As a security measure files are stored in a separate object, stored
|
||||
* as `req.files`. This prevents attacks that may potentially alter
|
||||
* filenames, and depending on the application gain access to restricted files.
|
||||
*
|
||||
* Multipart configuration:
|
||||
*
|
||||
* The `options` passed are provided to each parser function.
|
||||
* The _multipart/form-data_ parser merges these with formidable's
|
||||
* IncomingForm object, allowing you to tweak the upload directory,
|
||||
* size limits, etc. For example you may wish to retain the file extension
|
||||
* and change the upload directory:
|
||||
*
|
||||
* server.use(bodyParser({ uploadDir: '/www/mysite.com/uploads' }));
|
||||
*
|
||||
* View [node-formidable](https://github.com/felixge/node-formidable) for more information.
|
||||
*
|
||||
* If you wish to use formidable directly within your app, and do not
|
||||
* desire this behaviour for multipart requests simply remove the
|
||||
* parser:
|
||||
*
|
||||
* delete connect.bodyParser.parse['multipart/form-data'];
|
||||
*
|
||||
* Or
|
||||
*
|
||||
* delete express.bodyParser.parse['multipart/form-data'];
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports = module.exports = function bodyParser(options){
|
||||
options = options || {};
|
||||
return function bodyParser(req, res, next) {
|
||||
if (req.body) return next();
|
||||
req.body = {};
|
||||
|
||||
if ('GET' == req.method || 'HEAD' == req.method) return next();
|
||||
var parser = exports.parse[mime(req)];
|
||||
if (parser) {
|
||||
parser(req, options, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parsers.
|
||||
*/
|
||||
|
||||
exports.parse = {};
|
||||
|
||||
/**
|
||||
* Parse application/x-www-form-urlencoded.
|
||||
*/
|
||||
|
||||
exports.parse['application/x-www-form-urlencoded'] = function(req, options, fn){
|
||||
var buf = '';
|
||||
req.setEncoding('utf8');
|
||||
req.on('data', function(chunk){ buf += chunk });
|
||||
req.on('end', function(){
|
||||
try {
|
||||
req.body = buf.length
|
||||
? qs.parse(buf)
|
||||
: {};
|
||||
fn();
|
||||
} catch (err){
|
||||
fn(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse application/json.
|
||||
*/
|
||||
|
||||
exports.parse['application/json'] = function(req, options, fn){
|
||||
var buf = '';
|
||||
req.setEncoding('utf8');
|
||||
req.on('data', function(chunk){ buf += chunk });
|
||||
req.on('end', function(){
|
||||
try {
|
||||
req.body = buf.length
|
||||
? JSON.parse(buf)
|
||||
: {};
|
||||
fn();
|
||||
} catch (err){
|
||||
fn(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse multipart/form-data.
|
||||
*
|
||||
* TODO: make multiple support optional
|
||||
* TODO: revisit "error" flag if it's a formidable bug
|
||||
*/
|
||||
|
||||
exports.parse['multipart/form-data'] = function(req, options, fn){
|
||||
var form = new formidable.IncomingForm
|
||||
, data = {}
|
||||
, files = {}
|
||||
, done;
|
||||
|
||||
Object.keys(options).forEach(function(key){
|
||||
form[key] = options[key];
|
||||
});
|
||||
|
||||
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('field', function(name, val){
|
||||
ondata(name, val, data);
|
||||
});
|
||||
|
||||
form.on('file', function(name, val){
|
||||
ondata(name, val, files);
|
||||
});
|
||||
|
||||
form.on('error', function(err){
|
||||
fn(err);
|
||||
done = true;
|
||||
});
|
||||
|
||||
form.on('end', function(){
|
||||
if (done) return;
|
||||
try {
|
||||
req.body = qs.parse(data);
|
||||
req.files = qs.parse(files);
|
||||
fn();
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
});
|
||||
|
||||
form.parse(req);
|
||||
};
|
|
@ -0,0 +1,163 @@
|
|||
|
||||
/*!
|
||||
* Connect - compiler
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, path = require('path')
|
||||
, parse = require('url').parse;
|
||||
|
||||
/**
|
||||
* Require cache.
|
||||
*/
|
||||
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Setup compiler.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `src` Source directory, defaults to **CWD**.
|
||||
* - `dest` Destination directory, defaults `src`.
|
||||
* - `enable` Array of enabled compilers.
|
||||
*
|
||||
* Compilers:
|
||||
*
|
||||
* - `sass` Compiles sass to css
|
||||
* - `less` Compiles less to css
|
||||
* - `coffeescript` Compiles coffee to js
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports = module.exports = function compiler(options){
|
||||
options = options || {};
|
||||
|
||||
var srcDir = options.src || process.cwd()
|
||||
, destDir = options.dest || srcDir
|
||||
, enable = options.enable;
|
||||
|
||||
if (!enable || enable.length === 0) {
|
||||
throw new Error('compiler\'s "enable" option is not set, nothing will be compiled.');
|
||||
}
|
||||
|
||||
return function compiler(req, res, next){
|
||||
if ('GET' != req.method) return next();
|
||||
var pathname = parse(req.url).pathname;
|
||||
for (var i = 0, len = enable.length; i < len; ++i) {
|
||||
var name = enable[i]
|
||||
, compiler = compilers[name];
|
||||
if (compiler.match.test(pathname)) {
|
||||
var src = (srcDir + pathname).replace(compiler.match, compiler.ext)
|
||||
, dest = destDir + pathname;
|
||||
|
||||
// Compare mtimes
|
||||
fs.stat(src, function(err, srcStats){
|
||||
if (err) {
|
||||
if ('ENOENT' == err.code) {
|
||||
next();
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
} else {
|
||||
fs.stat(dest, function(err, destStats){
|
||||
if (err) {
|
||||
// Oh snap! it does not exist, compile it
|
||||
if ('ENOENT' == err.code) {
|
||||
compile();
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
} else {
|
||||
// Source has changed, compile it
|
||||
if (srcStats.mtime > destStats.mtime) {
|
||||
compile();
|
||||
} else {
|
||||
// Defer file serving
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Compile to the destination
|
||||
function compile() {
|
||||
fs.readFile(src, 'utf8', function(err, str){
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
compiler.compile(str, function(err, str){
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
fs.writeFile(dest, str, 'utf8', function(err){
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Bundled compilers:
|
||||
*
|
||||
* - [sass](http://github.com/visionmedia/sass.js) to _css_
|
||||
* - [less](http://github.com/cloudhead/less.js) to _css_
|
||||
* - [coffee](http://github.com/jashkenas/coffee-script) to _js_
|
||||
*/
|
||||
|
||||
var compilers = exports.compilers = {
|
||||
sass: {
|
||||
match: /\.css$/,
|
||||
ext: '.sass',
|
||||
compile: function(str, fn){
|
||||
var sass = cache.sass || (cache.sass = require('sass'));
|
||||
try {
|
||||
fn(null, sass.render(str));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
less: {
|
||||
match: /\.css$/,
|
||||
ext: '.less',
|
||||
compile: function(str, fn){
|
||||
var less = cache.less || (cache.less = require('less'));
|
||||
try {
|
||||
less.render(str, fn);
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
coffeescript: {
|
||||
match: /\.js$/,
|
||||
ext: '.coffee',
|
||||
compile: function(str, fn){
|
||||
var coffee = cache.coffee || (cache.coffee = require('coffee-script'));
|
||||
try {
|
||||
fn(null, coffee.compile(str));
|
||||
} catch (err) {
|
||||
fn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
/*!
|
||||
* Connect - cookieParser
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('./../utils');
|
||||
|
||||
/**
|
||||
* Parse _Cookie_ header and populate `req.cookies`
|
||||
* with an object keyed by the cookie names.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect.createServer(
|
||||
* connect.cookieParser()
|
||||
* , function(req, res, next){
|
||||
* res.end(JSON.stringify(req.cookies));
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function cookieParser(){
|
||||
return function cookieParser(req, res, next) {
|
||||
var cookie = req.headers.cookie;
|
||||
if (req.cookies) return next();
|
||||
req.cookies = {};
|
||||
if (cookie) {
|
||||
try {
|
||||
req.cookies = utils.parseCookie(cookie);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
|
||||
/*!
|
||||
* Connect - csrf
|
||||
* Copyright(c) 2011 Sencha Inc.
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils')
|
||||
, crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* CRSF protection middleware.
|
||||
*
|
||||
* By default this middleware generates a token named "_csrf"
|
||||
* which should be added to requests which mutate
|
||||
* state, within a hidden form field, query-string etc. This
|
||||
* token is validated against the visitor's `req.session._csrf`
|
||||
* property which is re-generated per request.
|
||||
*
|
||||
* The default `value` function checks `req.body` generated
|
||||
* by the `bodyParser()` middleware, `req.query` generated
|
||||
* by `query()`, and the "X-CSRF-Token" header field.
|
||||
*
|
||||
* This middleware requires session support, thus should be added
|
||||
* somewhere _below_ `session()` and `cookieParser()`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* var form = '\n\
|
||||
* <form action="/" method="post">\n\
|
||||
* <input type="hidden" name="_csrf" value="{token}" />\n\
|
||||
* <input type="text" name="user[name]" value="{user}" />\n\
|
||||
* <input type="password" name="user[pass]" />\n\
|
||||
* <input type="submit" value="Login" />\n\
|
||||
* </form>\n\
|
||||
* ';
|
||||
*
|
||||
* connect(
|
||||
* connect.cookieParser()
|
||||
* , connect.session({ secret: 'keyboard cat' })
|
||||
* , connect.bodyParser()
|
||||
* , connect.csrf()
|
||||
*
|
||||
* , function(req, res, next){
|
||||
* if ('POST' != req.method) return next();
|
||||
* req.session.user = req.body.user;
|
||||
* next();
|
||||
* }
|
||||
*
|
||||
* , function(req, res){
|
||||
* res.setHeader('Content-Type', 'text/html');
|
||||
* var body = form
|
||||
* .replace('{token}', req.session._csrf)
|
||||
* .replace('{user}', req.session.user && req.session.user.name || '');
|
||||
* res.end(body);
|
||||
* }
|
||||
* ).listen(3000);
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `value` a function accepting the request, returning the token
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function csrf(options) {
|
||||
var options = options || {}
|
||||
, value = options.value || defaultValue;
|
||||
|
||||
return function(req, res, next){
|
||||
// generate CSRF token
|
||||
var token = req.session._csrf || (req.session._csrf = utils.uid(24));
|
||||
|
||||
// ignore GET (for now)
|
||||
if ('GET' == req.method) return next();
|
||||
|
||||
// determine value
|
||||
var val = value(req);
|
||||
|
||||
// check
|
||||
if (val != token) return utils.forbidden(res);
|
||||
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Default value function, checking the `req.body`
|
||||
* and `req.query` for the CSRF token.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function defaultValue(req) {
|
||||
return (req.body && req.body._csrf)
|
||||
|| (req.query && req.query._csrf)
|
||||
|| (req.headers['x-csrf-token']);
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
|
||||
/*!
|
||||
* Connect - directory
|
||||
* Copyright(c) 2011 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
// TODO: icon / style for directories
|
||||
// TODO: arrow key navigation
|
||||
// TODO: make icons extensible
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, parse = require('url').parse
|
||||
, utils = require('../utils')
|
||||
, path = require('path')
|
||||
, normalize = path.normalize
|
||||
, extname = path.extname
|
||||
, join = path.join;
|
||||
|
||||
/**
|
||||
* Icon cache.
|
||||
*/
|
||||
|
||||
var cache = {};
|
||||
|
||||
/**
|
||||
* Serve directory listings with the given `root` path.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `hidden` display hidden (dot) files. Defaults to false.
|
||||
* - `icons` display icons. Defaults to false.
|
||||
* - `filter` Apply this filter function to files. Defaults to false.
|
||||
*
|
||||
* @param {String} root
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports = module.exports = function directory(root, options){
|
||||
options = options || {};
|
||||
|
||||
// root required
|
||||
if (!root) throw new Error('directory() root path required');
|
||||
var hidden = options.hidden
|
||||
, icons = options.icons
|
||||
, filter = options.filter
|
||||
, root = normalize(root);
|
||||
|
||||
return function directory(req, res, next) {
|
||||
var accept = req.headers.accept || 'text/plain'
|
||||
, url = parse(req.url)
|
||||
, dir = decodeURIComponent(url.pathname)
|
||||
, path = normalize(join(root, dir))
|
||||
, originalUrl = parse(req.originalUrl)
|
||||
, originalDir = decodeURIComponent(originalUrl.pathname)
|
||||
, showUp = path != root && path != root + '/';
|
||||
|
||||
// null byte(s)
|
||||
if (~path.indexOf('\0')) return utils.badRequest(res);
|
||||
|
||||
// malicious path
|
||||
if (0 != path.indexOf(root)) return utils.forbidden(res);
|
||||
|
||||
// check if we have a directory
|
||||
fs.stat(path, function(err, stat){
|
||||
if (err) return 'ENOENT' == err.code
|
||||
? next()
|
||||
: next(err);
|
||||
|
||||
if (!stat.isDirectory()) return next();
|
||||
|
||||
// fetch files
|
||||
fs.readdir(path, function(err, files){
|
||||
if (err) return next(err);
|
||||
if (!hidden) files = removeHidden(files);
|
||||
if (filter) files = files.filter(filter);
|
||||
files.sort();
|
||||
// content-negotiation
|
||||
for (var key in exports) {
|
||||
if (~accept.indexOf(key) || ~accept.indexOf('*/*')) {
|
||||
exports[key](req, res, files, next, originalDir, showUp, icons);
|
||||
return;
|
||||
}
|
||||
}
|
||||
utils.notAcceptable(res);
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond with text/html.
|
||||
*/
|
||||
|
||||
exports.html = function(req, res, files, next, dir, showUp, icons){
|
||||
fs.readFile(__dirname + '/../public/directory.html', 'utf8', function(err, str){
|
||||
if (err) return next(err);
|
||||
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(err, style){
|
||||
if (err) return next(err);
|
||||
if (showUp) files.unshift('..');
|
||||
str = str
|
||||
.replace('{style}', style)
|
||||
.replace('{files}', html(files, dir, icons))
|
||||
.replace('{directory}', dir)
|
||||
.replace('{linked-path}', htmlPath(dir));
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader('Content-Length', str.length);
|
||||
res.end(str);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond with application/json.
|
||||
*/
|
||||
|
||||
exports.json = function(req, res, files){
|
||||
files = JSON.stringify(files);
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Content-Length', files.length);
|
||||
res.end(files);
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond with text/plain.
|
||||
*/
|
||||
|
||||
exports.plain = function(req, res, files){
|
||||
files = files.join('\n') + '\n';
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.setHeader('Content-Length', files.length);
|
||||
res.end(files);
|
||||
};
|
||||
|
||||
/**
|
||||
* Map html `dir`, returning a linked path.
|
||||
*/
|
||||
|
||||
function htmlPath(dir) {
|
||||
var curr = [];
|
||||
return dir.split('/').map(function(part){
|
||||
curr.push(part);
|
||||
return '<a href="' + curr.join('/') + '">' + part + '</a>';
|
||||
}).join(' / ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Map html `files`, returning an html unordered list.
|
||||
*/
|
||||
|
||||
function html(files, dir, useIcons) {
|
||||
return '<ul id="files">' + files.map(function(file){
|
||||
var icon = ''
|
||||
, classes = [];
|
||||
|
||||
if (useIcons && '..' != file) {
|
||||
icon = icons[extname(file)] || icons.default;
|
||||
icon = '<img src="data:image/png;base64,' + load(icon) + '" />';
|
||||
classes.push('icon');
|
||||
}
|
||||
|
||||
return '<li><a href="'
|
||||
+ join(dir, file)
|
||||
+ '" class="'
|
||||
+ classes.join(' ') + '"'
|
||||
+ ' title="' + file + '">'
|
||||
+ icon + file + '</a></li>';
|
||||
|
||||
}).join('\n') + '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and cache the given `icon`.
|
||||
*
|
||||
* @param {String} icon
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function load(icon) {
|
||||
if (cache[icon]) return cache[icon];
|
||||
return cache[icon] = fs.readFileSync(__dirname + '/../public/icons/' + icon, 'base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter "hidden" `files`, aka files
|
||||
* beginning with a `.`.
|
||||
*
|
||||
* @param {Array} files
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function removeHidden(files) {
|
||||
return files.filter(function(file){
|
||||
return '.' != file[0];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon map.
|
||||
*/
|
||||
|
||||
var icons = {
|
||||
'.js': 'page_white_code_red.png'
|
||||
, '.c': 'page_white_c.png'
|
||||
, '.h': 'page_white_h.png'
|
||||
, '.cc': 'page_white_cplusplus.png'
|
||||
, '.php': 'page_white_php.png'
|
||||
, '.rb': 'page_white_ruby.png'
|
||||
, '.cpp': 'page_white_cplusplus.png'
|
||||
, '.swf': 'page_white_flash.png'
|
||||
, '.pdf': 'page_white_acrobat.png'
|
||||
, 'default': 'page_white.png'
|
||||
};
|
|
@ -0,0 +1,100 @@
|
|||
/*!
|
||||
* Connect - errorHandler
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils')
|
||||
, url = require('url')
|
||||
, fs = require('fs');
|
||||
|
||||
/**
|
||||
* Flexible error handler, providing (_optional_) stack traces
|
||||
* and error message responses for requests accepting text, html,
|
||||
* or json.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `showStack`, `stack` respond with both the error message and stack trace. Defaults to `false`
|
||||
* - `showMessage`, `message`, respond with the exception message only. Defaults to `false`
|
||||
* - `dumpExceptions`, `dump`, dump exceptions to stderr (without terminating the process). Defaults to `false`
|
||||
*
|
||||
* Text:
|
||||
*
|
||||
* By default, and when _text/plain_ is accepted a simple stack trace
|
||||
* or error message will be returned.
|
||||
*
|
||||
* JSON:
|
||||
*
|
||||
* When _application/json_ is accepted, connect will respond with
|
||||
* an object in the form of `{ "error": error }`.
|
||||
*
|
||||
* HTML:
|
||||
*
|
||||
* When accepted connect will output a nice html stack trace.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports = module.exports = function errorHandler(options){
|
||||
options = options || {};
|
||||
|
||||
// defaults
|
||||
var showStack = options.showStack || options.stack
|
||||
, showMessage = options.showMessage || options.message
|
||||
, dumpExceptions = options.dumpExceptions || options.dump
|
||||
, formatUrl = options.formatUrl;
|
||||
|
||||
return function errorHandler(err, req, res, next){
|
||||
res.statusCode = 500;
|
||||
if (dumpExceptions) console.error(err.stack);
|
||||
if (showStack) {
|
||||
var accept = req.headers.accept || '';
|
||||
// html
|
||||
if (~accept.indexOf('html')) {
|
||||
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(e, style){
|
||||
fs.readFile(__dirname + '/../public/error.html', 'utf8', function(e, html){
|
||||
var stack = (err.stack || '')
|
||||
.split('\n').slice(1)
|
||||
.map(function(v){ return '<li>' + v + '</li>'; }).join('');
|
||||
html = html
|
||||
.replace('{style}', style)
|
||||
.replace('{stack}', stack)
|
||||
.replace('{title}', exports.title)
|
||||
.replace(/\{error\}/g, utils.escape(err.toString()));
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(html);
|
||||
});
|
||||
});
|
||||
// json
|
||||
} else if (~accept.indexOf('json')) {
|
||||
var json = JSON.stringify({ error: err });
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(json);
|
||||
// plain text
|
||||
} else {
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
res.end(err.stack);
|
||||
}
|
||||
} else {
|
||||
var body = showMessage
|
||||
? err.toString()
|
||||
: 'Internal Server Error';
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(body);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Template title.
|
||||
*/
|
||||
|
||||
exports.title = 'Connect';
|
|
@ -0,0 +1,76 @@
|
|||
|
||||
/*!
|
||||
* Connect - favicon
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, utils = require('../utils');
|
||||
|
||||
/**
|
||||
* Favicon cache.
|
||||
*/
|
||||
|
||||
var icon;
|
||||
|
||||
/**
|
||||
* By default serves the connect favicon, or the favicon
|
||||
* located by the given `path`.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `maxAge` cache-control max-age directive, defaulting to 1 day
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect.createServer(
|
||||
* connect.favicon()
|
||||
* );
|
||||
*
|
||||
* connect.createServer(
|
||||
* connect.favicon(__dirname + '/public/favicon.ico')
|
||||
* );
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function favicon(path, options){
|
||||
var options = options || {}
|
||||
, path = path || __dirname + '/../public/favicon.ico'
|
||||
, maxAge = options.maxAge || 86400000;
|
||||
|
||||
return function favicon(req, res, next){
|
||||
if ('/favicon.ico' == req.url) {
|
||||
if (icon) {
|
||||
res.writeHead(200, icon.headers);
|
||||
res.end(icon.body);
|
||||
} else {
|
||||
fs.readFile(path, function(err, buf){
|
||||
if (err) return next(err);
|
||||
icon = {
|
||||
headers: {
|
||||
'Content-Type': 'image/x-icon'
|
||||
, 'Content-Length': buf.length
|
||||
, 'ETag': '"' + utils.md5(buf) + '"'
|
||||
, 'Cache-Control': 'public, max-age=' + (maxAge / 1000)
|
||||
},
|
||||
body: buf
|
||||
};
|
||||
res.writeHead(200, icon.headers);
|
||||
res.end(icon.body);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,82 @@
|
|||
|
||||
/*!
|
||||
* Connect - limit
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Limit request bodies to the given size in `bytes`.
|
||||
*
|
||||
* A string representation of the bytesize may also be passed,
|
||||
* for example "5mb", "200kb", "1gb", etc.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* var server = connect(
|
||||
* connect.limit('5.5mb')
|
||||
* ).listen(3000);
|
||||
*
|
||||
* TODO: pause EV_READ
|
||||
*
|
||||
* @param {Number|String} bytes
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function limit(bytes){
|
||||
if ('string' == typeof bytes) bytes = parse(bytes);
|
||||
if ('number' != typeof bytes) throw new Error('limit() bytes required');
|
||||
return function limit(req, res, next){
|
||||
var received = 0
|
||||
, len = req.headers['content-length']
|
||||
? parseInt(req.headers['content-length'], 10)
|
||||
: null;
|
||||
|
||||
// deny the request
|
||||
function deny() {
|
||||
req.destroy();
|
||||
}
|
||||
|
||||
// self-awareness
|
||||
if (req._limit) return next();
|
||||
req._limit = true;
|
||||
|
||||
// limit by content-length
|
||||
if (len && len > bytes) {
|
||||
res.statusCode = 413;
|
||||
res.end('Request Entity Too Large');
|
||||
return;
|
||||
}
|
||||
|
||||
// limit
|
||||
req.on('data', function(chunk){
|
||||
received += chunk.length;
|
||||
if (received > bytes) deny();
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse byte `size` string.
|
||||
*
|
||||
* @param {String} size
|
||||
* @return {Number}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
|
||||
/*!
|
||||
* Connect - logger
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Log buffer.
|
||||
*/
|
||||
|
||||
var buf = [];
|
||||
|
||||
/**
|
||||
* Default log buffer duration.
|
||||
*/
|
||||
|
||||
var defaultBufferDuration = 1000;
|
||||
|
||||
/**
|
||||
* Log requests with the given `options` or a `format` string.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `format` Format string, see below for tokens
|
||||
* - `stream` Output stream, defaults to _stdout_
|
||||
* - `buffer` Buffer duration, defaults to 1000ms when _true_
|
||||
* - `immediate` Write log line on request instead of response (for response times)
|
||||
*
|
||||
* Tokens:
|
||||
*
|
||||
* - `:req[header]` ex: `:req[Accept]`
|
||||
* - `:res[header]` ex: `:res[Content-Length]`
|
||||
* - `:http-version`
|
||||
* - `:response-time`
|
||||
* - `:remote-addr`
|
||||
* - `:date`
|
||||
* - `:method`
|
||||
* - `:url`
|
||||
* - `:referrer`
|
||||
* - `:user-agent`
|
||||
* - `:status`
|
||||
*
|
||||
* Formats:
|
||||
*
|
||||
* Pre-defined formats that ship with connect:
|
||||
*
|
||||
* - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'
|
||||
* - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'
|
||||
* - `tiny` ':method :url :status :res[content-length] - :response-time ms'
|
||||
* - `dev` concise output colored by response status for development use
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect.logger() // default
|
||||
* connect.logger('short')
|
||||
* connect.logger('tiny')
|
||||
* connect.logger('dev')
|
||||
* connect.logger(':method :url - :referrer')
|
||||
* connect.logger(':req[content-type] -> :res[content-type]')
|
||||
* connect.logger(function(req, res){ return 'some format string' })
|
||||
*
|
||||
* Defining Tokens:
|
||||
*
|
||||
* To define a token, simply invoke `connect.logger.token()` with the
|
||||
* name and a callback function. The value returned is then available
|
||||
* as ":type" in this case.
|
||||
*
|
||||
* connect.logger.token('type', function(req, res){ return req.headers['content-type']; })
|
||||
*
|
||||
* Defining Formats:
|
||||
*
|
||||
* All default formats are defined this way, however it's public API as well:
|
||||
*
|
||||
* connect.logger.format('name', 'string or function')
|
||||
*
|
||||
* @param {String|Function|Object} format or options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports = module.exports = function logger(options) {
|
||||
if ('object' == typeof options) {
|
||||
options = options || {};
|
||||
} else if (options) {
|
||||
options = { format: options };
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// output on request instead of response
|
||||
var immediate = options.immediate;
|
||||
|
||||
// format name
|
||||
var fmt = exports[options.format] || options.format || exports.default;
|
||||
|
||||
// compile format
|
||||
if ('function' != typeof fmt) fmt = compile(fmt);
|
||||
|
||||
// options
|
||||
var stream = options.stream || process.stdout
|
||||
, buffer = options.buffer;
|
||||
|
||||
// buffering support
|
||||
if (buffer) {
|
||||
var realStream = stream
|
||||
, interval = 'number' == typeof buffer
|
||||
? buffer
|
||||
: defaultBufferDuration;
|
||||
|
||||
// flush interval
|
||||
setInterval(function(){
|
||||
if (buf.length) {
|
||||
realStream.write(buf.join(''), 'ascii');
|
||||
buf.length = 0;
|
||||
}
|
||||
}, interval);
|
||||
|
||||
// swap the stream
|
||||
stream = {
|
||||
write: function(str){
|
||||
buf.push(str);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
if (null == line) return;
|
||||
stream.write(line + '\n', 'ascii');
|
||||
} else {
|
||||
// proxy end to output loggging
|
||||
var end = res.end;
|
||||
res.end = function(chunk, encoding){
|
||||
res.end = end;
|
||||
res.end(chunk, encoding);
|
||||
var line = fmt(exports, req, res);
|
||||
if (null == line) return;
|
||||
stream.write(line + '\n', 'ascii');
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Compile `fmt` into a function.
|
||||
*
|
||||
* @param {String} fmt
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function compile(fmt) {
|
||||
fmt = fmt.replace(/"/g, '\\"');
|
||||
var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
|
||||
return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "';
|
||||
}) + '";'
|
||||
return new Function('tokens, req, res', js);
|
||||
};
|
||||
|
||||
/**
|
||||
* Define a token function with the given `name`,
|
||||
* and callback `fn(req, res)`.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} fn
|
||||
* @return {Object} exports for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.token = function(name, fn) {
|
||||
exports[name] = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Define a `fmt` with the given `name`.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String|Function} fmt
|
||||
* @return {Object} exports for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.format = function(name, str){
|
||||
exports[name] = str;
|
||||
return this;
|
||||
};
|
||||
|
||||
// default format
|
||||
|
||||
exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
|
||||
|
||||
// short format
|
||||
|
||||
exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
|
||||
|
||||
// tiny format
|
||||
|
||||
exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
|
||||
|
||||
// dev (colored)
|
||||
|
||||
exports.format('dev', function(tokens, req, res){
|
||||
var status = res.statusCode
|
||||
, color = 32;
|
||||
|
||||
if (status >= 500) color = 31
|
||||
else if (status >= 400) color = 33
|
||||
else if (status >= 300) color = 36;
|
||||
|
||||
return '\033[90m' + req.method
|
||||
+ ' ' + req.originalUrl + ' '
|
||||
+ '\033[' + color + 'm' + res.statusCode
|
||||
+ ' \033[90m'
|
||||
+ (new Date - req._startTime)
|
||||
+ 'ms\033[0m';
|
||||
});
|
||||
|
||||
// request url
|
||||
|
||||
exports.token('url', function(req){
|
||||
return req.originalUrl;
|
||||
});
|
||||
|
||||
// request method
|
||||
|
||||
exports.token('method', function(req){
|
||||
return req.method;
|
||||
});
|
||||
|
||||
// response time in milliseconds
|
||||
|
||||
exports.token('response-time', function(req){
|
||||
return new Date - req._startTime;
|
||||
});
|
||||
|
||||
// UTC date
|
||||
|
||||
exports.token('date', function(){
|
||||
return new Date().toUTCString();
|
||||
});
|
||||
|
||||
// response status code
|
||||
|
||||
exports.token('status', function(req, res){
|
||||
return res.statusCode;
|
||||
});
|
||||
|
||||
// normalized referrer
|
||||
|
||||
exports.token('referrer', function(req){
|
||||
return req.headers['referer'] || req.headers['referrer'];
|
||||
});
|
||||
|
||||
// remote address
|
||||
|
||||
exports.token('remote-addr', function(req){
|
||||
return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress));
|
||||
});
|
||||
|
||||
// HTTP version
|
||||
|
||||
exports.token('http-version', function(req){
|
||||
return req.httpVersionMajor + '.' + req.httpVersionMinor;
|
||||
});
|
||||
|
||||
// UA string
|
||||
|
||||
exports.token('user-agent', function(req){
|
||||
return req.headers['user-agent'];
|
||||
});
|
||||
|
||||
// request header
|
||||
|
||||
exports.token('req', function(req, res, field){
|
||||
return req.headers[field.toLowerCase()];
|
||||
});
|
||||
|
||||
// response header
|
||||
|
||||
exports.token('res', function(req, res, field){
|
||||
return (res._headers || {})[field.toLowerCase()];
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
/*!
|
||||
* Connect - methodOverride
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides faux HTTP method support.
|
||||
*
|
||||
* Pass an optional `key` to use when checking for
|
||||
* a method override, othewise defaults to _\_method_.
|
||||
* The original method is available via `req.originalMethod`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function methodOverride(key){
|
||||
key = key || "_method";
|
||||
return function methodOverride(req, res, next) {
|
||||
req.originalMethod = req.originalMethod || req.method;
|
||||
|
||||
// req.body
|
||||
if (req.body && key in req.body) {
|
||||
req.method = req.body[key].toUpperCase();
|
||||
delete req.body[key];
|
||||
// check X-HTTP-Method-Override
|
||||
} else if (req.headers['x-http-method-override']) {
|
||||
req.method = req.headers['x-http-method-override'].toUpperCase();
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
|
||||
/*!
|
||||
* Connect - profiler
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Profile the duration of a request.
|
||||
*
|
||||
* Typically this middleware should be utilized
|
||||
* _above_ all others, as it proxies the `res.end()`
|
||||
* method, being first allows it to encapsulate all
|
||||
* other middleware.
|
||||
*
|
||||
* Example Output:
|
||||
*
|
||||
* GET /
|
||||
* response time 2ms
|
||||
* memory rss 52.00kb
|
||||
* memory vsize 2.07mb
|
||||
* heap before 3.76mb / 8.15mb
|
||||
* heap after 3.80mb / 8.15mb
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function profiler(){
|
||||
return function(req, res, next){
|
||||
var end = res.end
|
||||
, start = snapshot();
|
||||
|
||||
// state snapshot
|
||||
function snapshot() {
|
||||
return {
|
||||
mem: process.memoryUsage()
|
||||
, time: new Date
|
||||
};
|
||||
}
|
||||
|
||||
// proxy res.end()
|
||||
res.end = function(data, encoding){
|
||||
res.end = end;
|
||||
res.end(data, encoding);
|
||||
compare(req, start, snapshot())
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare `start` / `end` snapshots.
|
||||
*
|
||||
* @param {IncomingRequest} req
|
||||
* @param {Object} start
|
||||
* @param {Object} end
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function compare(req, start, end) {
|
||||
console.log();
|
||||
row(req.method, req.url);
|
||||
row('response time:', (end.time - start.time) + 'ms');
|
||||
row('memory rss:', formatBytes(end.mem.rss - start.mem.rss));
|
||||
row('memory vsize:', formatBytes(end.mem.vsize - start.mem.vsize));
|
||||
row('heap before:', formatBytes(start.mem.heapUsed) + ' / ' + formatBytes(start.mem.heapTotal));
|
||||
row('heap after:', formatBytes(end.mem.heapUsed) + ' / ' + formatBytes(end.mem.heapTotal));
|
||||
console.log();
|
||||
}
|
||||
|
||||
/**
|
||||
* Row helper
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {String} val
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function row(key, val) {
|
||||
console.log(' \033[90m%s\033[0m \033[36m%s\033[0m', key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format byte-size.
|
||||
*
|
||||
* @param {Number} bytes
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function formatBytes(bytes) {
|
||||
var kb = 1024
|
||||
, mb = 1024 * kb
|
||||
, gb = 1024 * mb;
|
||||
if (bytes < kb) return bytes + 'b';
|
||||
if (bytes < mb) return (bytes / kb).toFixed(2) + 'kb';
|
||||
if (bytes < gb) return (bytes / mb).toFixed(2) + 'mb';
|
||||
return (bytes / gb).toFixed(2) + 'gb';
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
/*!
|
||||
* Connect - query
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* Copyright(c) 2011 Sencha Inc.
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var qs = require('qs')
|
||||
, parse = require('url').parse;
|
||||
|
||||
/**
|
||||
* Automatically parse the query-string when available,
|
||||
* populating the `req.query` object.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect(
|
||||
* connect.query()
|
||||
* , function(req, res){
|
||||
* res.end(JSON.stringify(req.query));
|
||||
* }
|
||||
* ).listen(3000);
|
||||
*
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function query(){
|
||||
return function query(req, res, next){
|
||||
req.query = ~req.url.indexOf('?')
|
||||
? qs.parse(parse(req.url).query)
|
||||
: {};
|
||||
next();
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
/*!
|
||||
* Connect - responseTime
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds the `X-Response-Time` header displaying the response
|
||||
* duration in milliseconds.
|
||||
*
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function responseTime(){
|
||||
return function(req, res, next){
|
||||
var writeHead = res.writeHead
|
||||
, start = new Date;
|
||||
|
||||
if (res._responseTime) return next();
|
||||
res._responseTime = true;
|
||||
|
||||
// proxy writeHead to calculate duration
|
||||
res.writeHead = function(status, headers){
|
||||
var duration = new Date - start;
|
||||
res.setHeader('X-Response-Time', duration + 'ms');
|
||||
res.writeHead = writeHead;
|
||||
res.writeHead(status, headers);
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
|
@ -0,0 +1,379 @@
|
|||
|
||||
/*!
|
||||
* Connect - router
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils')
|
||||
, parse = require('url').parse;
|
||||
|
||||
/**
|
||||
* Expose router.
|
||||
*/
|
||||
|
||||
exports = module.exports = router;
|
||||
|
||||
/**
|
||||
* Supported HTTP / WebDAV methods.
|
||||
*/
|
||||
|
||||
var _methods = exports.methods = [
|
||||
'get'
|
||||
, 'post'
|
||||
, 'put'
|
||||
, 'delete'
|
||||
, 'connect'
|
||||
, 'options'
|
||||
, 'trace'
|
||||
, 'copy'
|
||||
, 'lock'
|
||||
, 'mkcol'
|
||||
, 'move'
|
||||
, 'propfind'
|
||||
, 'proppatch'
|
||||
, 'unlock'
|
||||
, 'report'
|
||||
, 'mkactivity'
|
||||
, 'checkout'
|
||||
, 'merge'
|
||||
];
|
||||
|
||||
/**
|
||||
* Provides Sinatra and Express-like routing capabilities.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect.router(function(app){
|
||||
* app.get('/user/:id', function(req, res, next){
|
||||
* // populates req.params.id
|
||||
* });
|
||||
* app.put('/user/:id', function(req, res, next){
|
||||
* // populates req.params.id
|
||||
* });
|
||||
* })
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function router(fn){
|
||||
var self = this
|
||||
, methods = {}
|
||||
, routes = {}
|
||||
, params = {};
|
||||
|
||||
if (!fn) throw new Error('router provider requires a callback function');
|
||||
|
||||
// Generate method functions
|
||||
_methods.forEach(function(method){
|
||||
methods[method] = generateMethodFunction(method.toUpperCase());
|
||||
});
|
||||
|
||||
// Alias del -> delete
|
||||
methods.del = methods.delete;
|
||||
|
||||
// Apply callback to all methods
|
||||
methods.all = function(){
|
||||
var args = arguments;
|
||||
_methods.forEach(function(name){
|
||||
methods[name].apply(this, args);
|
||||
});
|
||||
return self;
|
||||
};
|
||||
|
||||
// Register param callback
|
||||
methods.param = function(name, fn){
|
||||
params[name] = fn;
|
||||
};
|
||||
|
||||
fn.call(this, methods);
|
||||
|
||||
function generateMethodFunction(name) {
|
||||
var localRoutes = routes[name] = routes[name] || [];
|
||||
return function(path, fn){
|
||||
var keys = []
|
||||
, middleware = [];
|
||||
|
||||
// slice middleware
|
||||
if (arguments.length > 2) {
|
||||
middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
|
||||
fn = middleware.pop();
|
||||
middleware = utils.flatten(middleware);
|
||||
}
|
||||
|
||||
fn.middleware = middleware;
|
||||
|
||||
if (!path) throw new Error(name + ' route requires a path');
|
||||
if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
|
||||
var regexp = path instanceof RegExp
|
||||
? path
|
||||
: normalizePath(path, keys);
|
||||
localRoutes.push({
|
||||
fn: fn
|
||||
, path: regexp
|
||||
, keys: keys
|
||||
, orig: path
|
||||
, method: name
|
||||
});
|
||||
return self;
|
||||
};
|
||||
}
|
||||
|
||||
function router(req, res, next){
|
||||
var route
|
||||
, self = this;
|
||||
|
||||
(function pass(i){
|
||||
if (route = match(req, routes, i)) {
|
||||
var i = 0
|
||||
, keys = route.keys;
|
||||
|
||||
req.params = route.params;
|
||||
|
||||
// Param preconditions
|
||||
(function param(err) {
|
||||
try {
|
||||
var key = keys[i++]
|
||||
, val = req.params[key]
|
||||
, fn = params[key];
|
||||
|
||||
if ('route' == err) {
|
||||
pass(req._route_index + 1);
|
||||
// Error
|
||||
} else if (err) {
|
||||
next(err);
|
||||
// Param has callback
|
||||
} else if (fn) {
|
||||
// Return style
|
||||
if (1 == fn.length) {
|
||||
req.params[key] = fn(val);
|
||||
param();
|
||||
// Middleware style
|
||||
} else {
|
||||
fn(req, res, param, val);
|
||||
}
|
||||
// Finished processing params
|
||||
} else if (!key) {
|
||||
// route middleware
|
||||
i = 0;
|
||||
(function nextMiddleware(err){
|
||||
var fn = route.middleware[i++];
|
||||
if ('route' == err) {
|
||||
pass(req._route_index + 1);
|
||||
} else if (err) {
|
||||
next(err);
|
||||
} else if (fn) {
|
||||
fn(req, res, nextMiddleware);
|
||||
} else {
|
||||
route.call(self, req, res, function(err){
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
pass(req._route_index + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
// More params
|
||||
} else {
|
||||
param();
|
||||
}
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
})();
|
||||
} else if ('OPTIONS' == req.method) {
|
||||
options(req, res, routes);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
router.remove = function(path, method){
|
||||
var fns = router.lookup(path, method);
|
||||
fns.forEach(function(fn){
|
||||
routes[fn.method].splice(fn.index, 1);
|
||||
});
|
||||
};
|
||||
|
||||
router.lookup = function(path, method, ret){
|
||||
ret = ret || [];
|
||||
|
||||
// method specific lookup
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
if (routes[method]) {
|
||||
routes[method].forEach(function(route, i){
|
||||
if (path == route.orig) {
|
||||
var fn = route.fn;
|
||||
fn.regexp = route.path;
|
||||
fn.keys = route.keys;
|
||||
fn.path = route.orig;
|
||||
fn.method = route.method;
|
||||
fn.index = i;
|
||||
ret.push(fn);
|
||||
}
|
||||
});
|
||||
}
|
||||
// global lookup
|
||||
} else {
|
||||
_methods.forEach(function(method){
|
||||
router.lookup(path, method, ret);
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
router.match = function(url, method, ret){
|
||||
var ret = ret || []
|
||||
, i = 0
|
||||
, fn
|
||||
, req;
|
||||
|
||||
// method specific matches
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
req = { url: url, method: method };
|
||||
while (fn = match(req, routes, i)) {
|
||||
i = req._route_index + 1;
|
||||
ret.push(fn);
|
||||
}
|
||||
// global matches
|
||||
} else {
|
||||
_methods.forEach(function(method){
|
||||
router.match(url, method, ret);
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to OPTIONS.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {ServerResponse} req
|
||||
* @param {Array} routes
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function options(req, res, routes) {
|
||||
var pathname = parse(req.url).pathname
|
||||
, body = optionsFor(pathname, routes).join(',');
|
||||
res.writeHead(200, {
|
||||
'Content-Length': body.length
|
||||
, 'Allow': body
|
||||
});
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return OPTIONS array for the given `path`, matching `routes`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Array} routes
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function optionsFor(path, routes) {
|
||||
return _methods.filter(function(method){
|
||||
var arr = routes[method.toUpperCase()];
|
||||
for (var i = 0, len = arr.length; i < len; ++i) {
|
||||
if (arr[i].path.test(path)) return true;
|
||||
}
|
||||
}).map(function(method){
|
||||
return method.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given path string,
|
||||
* returning a regular expression.
|
||||
*
|
||||
* An empty array should be passed,
|
||||
* which will contain the placeholder
|
||||
* key names. For example "/user/:id" will
|
||||
* then contain ["id"].
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Array} keys
|
||||
* @return {RegExp}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function normalizePath(path, keys) {
|
||||
path = path
|
||||
.concat('/?')
|
||||
.replace(/\/\(/g, '(?:/')
|
||||
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
|
||||
keys.push(key);
|
||||
slash = slash || '';
|
||||
return ''
|
||||
+ (optional ? '' : slash)
|
||||
+ '(?:'
|
||||
+ (optional ? slash : '')
|
||||
+ (format || '') + (capture || '([^/]+?)') + ')'
|
||||
+ (optional || '');
|
||||
})
|
||||
.replace(/([\/.])/g, '\\$1')
|
||||
.replace(/\*/g, '(.+)');
|
||||
return new RegExp('^' + path + '$', 'i');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to match the given request to
|
||||
* one of the routes. When successful
|
||||
* a route function is returned.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {Object} routes
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function match(req, routes, i) {
|
||||
var captures
|
||||
, method = req.method
|
||||
, i = i || 0;
|
||||
if ('HEAD' == method) method = 'GET';
|
||||
if (routes = routes[method]) {
|
||||
var url = parse(req.url)
|
||||
, pathname = url.pathname;
|
||||
for (var len = routes.length; i < len; ++i) {
|
||||
var route = routes[i]
|
||||
, fn = route.fn
|
||||
, path = route.path
|
||||
, keys = fn.keys = route.keys;
|
||||
if (captures = path.exec(pathname)) {
|
||||
fn.method = method;
|
||||
fn.params = [];
|
||||
for (var j = 1, len = captures.length; j < len; ++j) {
|
||||
var key = keys[j-1],
|
||||
val = typeof captures[j] === 'string'
|
||||
? decodeURIComponent(captures[j])
|
||||
: captures[j];
|
||||
if (key) {
|
||||
fn.params[key] = val;
|
||||
} else {
|
||||
fn.params.push(val);
|
||||
}
|
||||
}
|
||||
req._route_index = i;
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
|
||||
/*!
|
||||
* Connect - session
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Session = require('./session/session')
|
||||
, MemoryStore = require('./session/memory')
|
||||
, Cookie = require('./session/cookie')
|
||||
, Store = require('./session/store')
|
||||
, utils = require('./../utils')
|
||||
, parse = require('url').parse
|
||||
, crypto = require('crypto');
|
||||
|
||||
// environment
|
||||
|
||||
var env = process.env.NODE_ENV;
|
||||
|
||||
/**
|
||||
* Expose the middleware.
|
||||
*/
|
||||
|
||||
exports = module.exports = session;
|
||||
|
||||
/**
|
||||
* Expose constructors.
|
||||
*/
|
||||
|
||||
exports.Store = Store;
|
||||
exports.Cookie = Cookie;
|
||||
exports.Session = Session;
|
||||
exports.MemoryStore = MemoryStore;
|
||||
|
||||
/**
|
||||
* Warning message for `MemoryStore` usage in production.
|
||||
*/
|
||||
|
||||
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.';
|
||||
|
||||
/**
|
||||
* Default finger-printing function.
|
||||
*/
|
||||
|
||||
function defaultFingerprint(req) {
|
||||
var ua = req.headers['user-agent'] || '';
|
||||
return ua.replace(/;?\schromeframe\/[\d\.]+/, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Paths to ignore.
|
||||
*/
|
||||
|
||||
exports.ignore = [];
|
||||
|
||||
/**
|
||||
* Setup session store with the given `options`.
|
||||
*
|
||||
* Session data is _not_ saved in the cookie itself, however
|
||||
* cookies are used, so we must use the [cookieParser()](middleware-cookieParser.html)
|
||||
* middleware _before_ `session()`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect.createServer(
|
||||
* connect.cookieParser()
|
||||
* , connect.session({ secret: 'keyboard cat' })
|
||||
* );
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `key` cookie name defaulting to `connect.sid`
|
||||
* - `store` Session store instance
|
||||
* - `fingerprint` Custom fingerprint generating function
|
||||
* - `cookie` Session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: 14400000 }`
|
||||
* - `secret` Secret string used to compute hash
|
||||
*
|
||||
* Ignore Paths:
|
||||
*
|
||||
* By default `/favicon.ico` is the only ignored path, all others
|
||||
* will utilize sessions, to manipulate the paths ignored, use
|
||||
* `connect.session.ignore.push('/my/path')`. This works for _full_
|
||||
* pathnames only, not segments nor substrings.
|
||||
*
|
||||
* connect.session.ignore.push('/robots.txt');
|
||||
*
|
||||
* ## req.session
|
||||
*
|
||||
* To store or access session data, simply use the request property `req.session`,
|
||||
* which is (generally) serialized as JSON by the store, so nested objects
|
||||
* are typically fine. For example below is a user-specific view counter:
|
||||
*
|
||||
* connect(
|
||||
* connect.cookieParser()
|
||||
* , connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})
|
||||
* , connect.favicon()
|
||||
* , function(req, res, next){
|
||||
* var sess = req.session;
|
||||
* if (sess.views) {
|
||||
* res.setHeader('Content-Type', 'text/html');
|
||||
* res.write('<p>views: ' + sess.views + '</p>');
|
||||
* res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>');
|
||||
* res.end();
|
||||
* sess.views++;
|
||||
* } else {
|
||||
* sess.views = 1;
|
||||
* res.end('welcome to the session demo. refresh!');
|
||||
* }
|
||||
* }
|
||||
* ).listen(3000);
|
||||
*
|
||||
* ## Session#regenerate()
|
||||
*
|
||||
* To regenerate the session simply invoke the method, once complete
|
||||
* a new SID and `Session` instance will be initialized at `req.session`.
|
||||
*
|
||||
* req.session.regenerate(function(err){
|
||||
* // will have a new session here
|
||||
* });
|
||||
*
|
||||
* ## Session#destroy()
|
||||
*
|
||||
* Destroys the session, removing `req.session`, will be re-generated next request.
|
||||
*
|
||||
* req.session.destroy(function(err){
|
||||
* // cannot access session here
|
||||
* });
|
||||
*
|
||||
* ## Session#reload()
|
||||
*
|
||||
* Reloads the session data.
|
||||
*
|
||||
* req.session.reload(function(err){
|
||||
* // session updated
|
||||
* });
|
||||
*
|
||||
* ## Session#save()
|
||||
*
|
||||
* Save the session.
|
||||
*
|
||||
* req.session.save(function(err){
|
||||
* // session saved
|
||||
* });
|
||||
*
|
||||
* ## Session#touch()
|
||||
*
|
||||
* Updates the `.maxAge`, and `.lastAccess` properties. Typically this is
|
||||
* not necessary to call, as the session middleware does this for you.
|
||||
*
|
||||
* ## Session#cookie
|
||||
*
|
||||
* Each session has a unique cookie object accompany it. This allows
|
||||
* you to alter the session cookie per visitor. For example we can
|
||||
* set `req.session.cookie.expires` to `false` to enable the cookie
|
||||
* to remain for only the duration of the user-agent.
|
||||
*
|
||||
* ## Session#maxAge
|
||||
*
|
||||
* Alternatively `req.session.cookie.maxAge` will return the time
|
||||
* remaining in milliseconds, which we may also re-assign a new value
|
||||
* to adjust the `.expires` property appropriately. The following
|
||||
* are essentially equivalent
|
||||
*
|
||||
* var hour = 3600000;
|
||||
* req.session.cookie.expires = new Date(Date.now() + hour);
|
||||
* req.session.cookie.maxAge = hour;
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* req.session.cookie.maxAge;
|
||||
* // => 30000
|
||||
*
|
||||
* Session Store Implementation:
|
||||
*
|
||||
* Every session store _must_ implement the following methods
|
||||
*
|
||||
* - `.get(sid, callback)`
|
||||
* - `.set(sid, session, callback)`
|
||||
* - `.destroy(sid, callback)`
|
||||
*
|
||||
* Recommended methods include, but are not limited to:
|
||||
*
|
||||
* - `.length(callback)`
|
||||
* - `.clear(callback)`
|
||||
*
|
||||
* For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function session(options){
|
||||
var options = options || {}
|
||||
, key = options.key || 'connect.sid'
|
||||
, secret = options.secret
|
||||
, store = options.store || new MemoryStore
|
||||
, fingerprint = options.fingerprint || defaultFingerprint
|
||||
, cookie = options.cookie;
|
||||
|
||||
// notify user that this store is not
|
||||
// meant for a production environment
|
||||
if ('production' == env && store instanceof MemoryStore) {
|
||||
console.warn(warning);
|
||||
}
|
||||
|
||||
// ensure secret is present
|
||||
if (!secret) {
|
||||
throw new Error('connect.session({ secret: "string" }) required for security');
|
||||
}
|
||||
|
||||
// session hashing function
|
||||
store.hash = function(req, base) {
|
||||
return crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(base + fingerprint(req))
|
||||
.digest('base64')
|
||||
.replace(/=*$/, '');
|
||||
};
|
||||
|
||||
// generates the new session
|
||||
store.generate = function(req){
|
||||
var base = utils.uid(24);
|
||||
var sessionID = base + '.' + store.hash(req, base);
|
||||
req.sessionID = sessionID;
|
||||
req.session = new Session(req);
|
||||
req.session.cookie = new Cookie(cookie);
|
||||
};
|
||||
|
||||
return function session(req, res, next) {
|
||||
// self-awareness
|
||||
if (req.session) return next();
|
||||
|
||||
// parse url
|
||||
var url = parse(req.url)
|
||||
, path = url.pathname;
|
||||
|
||||
// ignorable paths
|
||||
if (~exports.ignore.indexOf(path)) return next();
|
||||
|
||||
// expose store
|
||||
req.sessionStore = store;
|
||||
|
||||
// proxy writeHead() to Set-Cookie
|
||||
var writeHead = res.writeHead;
|
||||
res.writeHead = function(status, headers){
|
||||
if (req.session) {
|
||||
var cookie = req.session.cookie;
|
||||
// only send secure session cookies when there is a secure connection.
|
||||
// proxySecure is a custom attribute to allow for a reverse proxy
|
||||
// to handle SSL connections and to communicate to connect over HTTP that
|
||||
// the incoming connection is secure.
|
||||
var secured = cookie.secure && (req.connection.encrypted || req.connection.proxySecure);
|
||||
if (secured || !cookie.secure) {
|
||||
res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID));
|
||||
}
|
||||
}
|
||||
|
||||
res.writeHead = writeHead;
|
||||
return res.writeHead(status, headers);
|
||||
};
|
||||
|
||||
// proxy end() to commit the session
|
||||
var end = res.end;
|
||||
res.end = function(data, encoding){
|
||||
res.end = end;
|
||||
if (req.session) {
|
||||
// HACK: ensure Set-Cookie for implicit writeHead()
|
||||
if (!res._header) res._implicitHeader();
|
||||
req.session.resetMaxAge();
|
||||
req.session.save(function(){
|
||||
res.end(data, encoding);
|
||||
});
|
||||
} else {
|
||||
res.end(data, encoding);
|
||||
}
|
||||
};
|
||||
|
||||
// session hashing
|
||||
function hash(base) {
|
||||
return store.hash(req, base);
|
||||
}
|
||||
|
||||
// generate the session
|
||||
function generate() {
|
||||
store.generate(req);
|
||||
}
|
||||
|
||||
// get the sessionID from the cookie
|
||||
req.sessionID = req.cookies[key];
|
||||
|
||||
// make a new session if the browser doesn't send a sessionID
|
||||
if (!req.sessionID) {
|
||||
generate();
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// check the fingerprint
|
||||
var parts = req.sessionID.split('.');
|
||||
if (parts[1] != hash(parts[0])) {
|
||||
generate();
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// generate the session object
|
||||
var pause = utils.pause(req);
|
||||
store.get(req.sessionID, function(err, sess){
|
||||
// proxy to resume() events
|
||||
var _next = next;
|
||||
next = function(err){
|
||||
_next(err);
|
||||
pause.resume();
|
||||
}
|
||||
|
||||
// error handling
|
||||
if (err) {
|
||||
if ('ENOENT' == err.code) {
|
||||
generate();
|
||||
next();
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
// no session
|
||||
} else if (!sess) {
|
||||
generate();
|
||||
next();
|
||||
// populate req.session
|
||||
} else {
|
||||
store.createSession(req, sess);
|
||||
next();
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
|
@ -0,0 +1,126 @@
|
|||
|
||||
/*!
|
||||
* Connect - session - Cookie
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../../utils');
|
||||
|
||||
/**
|
||||
* Initialize a new `Cookie` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var Cookie = module.exports = function Cookie(options) {
|
||||
this.path = '/';
|
||||
this.httpOnly = true;
|
||||
this.maxAge = 14400000;
|
||||
if (options) utils.merge(this, options);
|
||||
this.originalMaxAge = undefined == this.originalMaxAge
|
||||
? this.maxAge
|
||||
: this.originalMaxAge;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prototype.
|
||||
*/
|
||||
|
||||
Cookie.prototype = {
|
||||
|
||||
/**
|
||||
* Set expires `date`.
|
||||
*
|
||||
* @param {Date} date
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set expires(date) {
|
||||
this._expires = date;
|
||||
this.originalMaxAge = this.maxAge;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get expires `date`.
|
||||
*
|
||||
* @return {Date}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get expires() {
|
||||
return this._expires;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set expires via max-age in `ms`.
|
||||
*
|
||||
* @param {Number} ms
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set maxAge(ms) {
|
||||
this.expires = 'number' == typeof ms
|
||||
? new Date(Date.now() + ms)
|
||||
: ms;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get expires max-age in `ms`.
|
||||
*
|
||||
* @return {Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get maxAge() {
|
||||
return this.expires instanceof Date
|
||||
? this.expires.valueOf() - Date.now()
|
||||
: this.expires;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return cookie data object.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
get data() {
|
||||
return {
|
||||
originalMaxAge: this.originalMaxAge
|
||||
, expires: this._expires
|
||||
, secure: this.secure
|
||||
, httpOnly: this.httpOnly
|
||||
, domain: this.domain
|
||||
, path: this.path
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a serialized cookie string.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
serialize: function(name, val){
|
||||
return utils.serializeCookie(name, val, this.data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return JSON representation of this cookie.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
toJSON: function(){
|
||||
return this.data;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
/*!
|
||||
* Connect - session - MemoryStore
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Store = require('./store')
|
||||
, utils = require('../../utils')
|
||||
, Session = require('./session');
|
||||
|
||||
/**
|
||||
* Initialize a new `MemoryStore`.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
var MemoryStore = module.exports = function MemoryStore() {
|
||||
this.sessions = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from `Store.prototype`.
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.__proto__ = Store.prototype;
|
||||
|
||||
/**
|
||||
* Attempt to fetch session by the given `sid`.
|
||||
*
|
||||
* @param {String} sid
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.get = function(sid, fn){
|
||||
var self = this;
|
||||
process.nextTick(function(){
|
||||
var expires
|
||||
, sess = self.sessions[sid];
|
||||
if (sess) {
|
||||
sess = JSON.parse(sess);
|
||||
expires = 'string' == typeof sess.cookie.expires
|
||||
? new Date(sess.cookie.expires)
|
||||
: sess.cookie.expires;
|
||||
if (!expires || new Date < expires) {
|
||||
fn(null, sess);
|
||||
} else {
|
||||
self.destroy(sid, fn);
|
||||
}
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Commit the given `sess` object associated with the given `sid`.
|
||||
*
|
||||
* @param {String} sid
|
||||
* @param {Session} sess
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.set = function(sid, sess, fn){
|
||||
var self = this;
|
||||
process.nextTick(function(){
|
||||
self.sessions[sid] = JSON.stringify(sess);
|
||||
fn && fn();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the session associated with the given `sid`.
|
||||
*
|
||||
* @param {String} sid
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.destroy = function(sid, fn){
|
||||
var self = this;
|
||||
process.nextTick(function(){
|
||||
delete self.sessions[sid];
|
||||
fn && fn();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoke the given callback `fn` with all active sessions.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.all = function(fn){
|
||||
var arr = []
|
||||
, keys = Object.keys(this.sessions);
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
arr.push(this.sessions[keys[i]]);
|
||||
}
|
||||
fn(null, arr);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all sessions.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.clear = function(fn){
|
||||
this.sessions = {};
|
||||
fn && fn();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch number of sessions.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
MemoryStore.prototype.length = function(fn){
|
||||
fn(null, Object.keys(this.sessions).length);
|
||||
};
|
|
@ -0,0 +1,137 @@
|
|||
|
||||
/*!
|
||||
* Connect - session - Session
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../../utils')
|
||||
, Cookie = require('./cookie');
|
||||
|
||||
/**
|
||||
* Create a new `Session` with the given request and `data`.
|
||||
*
|
||||
* @param {IncomingRequest} req
|
||||
* @param {Object} data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update `.lastAccess` timestamp,
|
||||
* and reset `.cookie.maxAge` to prevent
|
||||
* the cookie from expiring when the
|
||||
* session is still active.
|
||||
*
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset `.maxAge` to `.originalMaxAge`.
|
||||
*
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.resetMaxAge = function(){
|
||||
this.cookie.maxAge = this.cookie.originalMaxAge;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the session data with optional callback `fn(err)`.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.save = function(fn){
|
||||
this.req.sessionStore.set(this.id, this, fn || function(){});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.reload = function(fn){
|
||||
var req = this.req
|
||||
, store = this.req.sessionStore;
|
||||
store.get(this.id, function(err, sess){
|
||||
if (err) return fn(err);
|
||||
if (!sess) return fn(new Error('failed to load session'));
|
||||
store.createSession(req, sess);
|
||||
fn();
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy `this` session.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.destroy = function(fn){
|
||||
delete this.req.session;
|
||||
this.req.sessionStore.destroy(this.id, fn);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Regenerate this request's session.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Session} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Session.prototype.regenerate = function(fn){
|
||||
this.req.sessionStore.regenerate(this.req, fn);
|
||||
return this;
|
||||
};
|
|
@ -0,0 +1,87 @@
|
|||
|
||||
/*!
|
||||
* Connect - session - Store
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, Session = require('./session')
|
||||
, Cookie = require('./cookie')
|
||||
, utils = require('../../utils');
|
||||
|
||||
/**
|
||||
* Initialize abstract `Store`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var Store = module.exports = function Store(options){};
|
||||
|
||||
/**
|
||||
* Inherit from `EventEmitter.prototype`.
|
||||
*/
|
||||
|
||||
Store.prototype.__proto__ = EventEmitter.prototype;
|
||||
|
||||
/**
|
||||
* Re-generate the given requests's session.
|
||||
*
|
||||
* @param {IncomingRequest} req
|
||||
* @return {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Store.prototype.regenerate = function(req, fn){
|
||||
var self = this;
|
||||
this.destroy(req.sessionID, function(err){
|
||||
self.generate(req);
|
||||
fn(err);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a `Session` instance via the given `sid`
|
||||
* and invoke the callback `fn(err, sess)`.
|
||||
*
|
||||
* @param {String} sid
|
||||
* @param {Function} fn
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Store.prototype.load = function(sid, fn){
|
||||
var self = this;
|
||||
this.get(sid, function(err, sess){
|
||||
if (err) return fn(err);
|
||||
if (!sess) return fn();
|
||||
var req = { sessionID: sid, sessionStore: self };
|
||||
sess = self.createSession(req, sess, false);
|
||||
fn(null, sess);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create session from JSON `sess` data.
|
||||
*
|
||||
* @param {IncomingRequest} req
|
||||
* @param {Object} sess
|
||||
* @return {Session}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Store.prototype.createSession = function(req, sess, update){
|
||||
var expires = sess.cookie.expires
|
||||
, orig = sess.cookie.originalMaxAge
|
||||
, update = null == update ? true : false;
|
||||
sess.cookie = new Cookie(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;
|
||||
};
|
|
@ -0,0 +1,225 @@
|
|||
|
||||
/*!
|
||||
* Connect - staticProvider
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, path = require('path')
|
||||
, join = path.join
|
||||
, basename = path.basename
|
||||
, normalize = path.normalize
|
||||
, utils = require('../utils')
|
||||
, Buffer = require('buffer').Buffer
|
||||
, parse = require('url').parse
|
||||
, mime = require('mime');
|
||||
|
||||
/**
|
||||
* Static file server with the given `root` path.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* var oneDay = 86400000;
|
||||
*
|
||||
* connect(
|
||||
* connect.static(__dirname + '/public')
|
||||
* ).listen(3000);
|
||||
*
|
||||
* connect(
|
||||
* connect.static(__dirname + '/public', { maxAge: oneDay })
|
||||
* ).listen(3000);
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `maxAge` Browser cache maxAge in milliseconds. defaults to 0
|
||||
* - `hidden` Allow transfer of hidden files. defaults to false
|
||||
* - `redirect` Redirect to trailing "/" when the pathname is a dir
|
||||
*
|
||||
* @param {String} root
|
||||
* @param {Object} options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose mime module.
|
||||
*/
|
||||
|
||||
exports.mime = mime;
|
||||
|
||||
/**
|
||||
* Respond with 416 "Requested Range Not Satisfiable"
|
||||
*
|
||||
* @param {ServerResponse} res
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function invalidRange(res) {
|
||||
var body = 'Requested Range Not Satisfiable';
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.setHeader('Content-Length', body.length);
|
||||
res.statusCode = 416;
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to tranfer the requseted file to `res`.
|
||||
*
|
||||
* @param {ServerRequest}
|
||||
* @param {ServerResponse}
|
||||
* @param {Function} next
|
||||
* @param {Object} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
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 = decodeURIComponent(url.pathname)
|
||||
, type;
|
||||
|
||||
// null byte(s)
|
||||
if (~path.indexOf('\0')) return utils.badRequest(res);
|
||||
|
||||
// when root is not given, consider .. malicious
|
||||
if (!root && ~path.indexOf('..')) return utils.forbidden(res);
|
||||
|
||||
// join / normalize from optional root dir
|
||||
path = normalize(join(root, path));
|
||||
|
||||
// malicious path
|
||||
if (root && 0 != path.indexOf(root)) return fn
|
||||
? fn(new Error('Forbidden'))
|
||||
: utils.forbidden(res);
|
||||
|
||||
// index.html support
|
||||
if (normalize('/') == path[path.length - 1]) path += 'index.html';
|
||||
|
||||
// "hidden" file
|
||||
if (!hidden && '.' == basename(path)[0]) return next();
|
||||
|
||||
fs.stat(path, function(err, stat){
|
||||
// mime type
|
||||
type = mime.lookup(path);
|
||||
|
||||
// ignore ENOENT
|
||||
if (err) {
|
||||
if (fn) return fn(err);
|
||||
return 'ENOENT' == err.code
|
||||
? next()
|
||||
: next(err);
|
||||
// redirect directory in case index.html is present
|
||||
} else if (stat.isDirectory()) {
|
||||
if (!redirect) return next();
|
||||
res.statusCode = 301;
|
||||
res.setHeader('Location', url.pathname + '/');
|
||||
res.end('Redirecting to ' + url.pathname + '/');
|
||||
return;
|
||||
}
|
||||
|
||||
// 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('ETag')) res.setHeader('ETag', utils.etag(stat));
|
||||
if (!res.getHeader('content-type')) {
|
||||
var charset = mime.charsets.lookup(type);
|
||||
res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
|
||||
}
|
||||
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 = {};
|
||||
var chunkSize = 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;
|
||||
chunkSize = opts.end - opts.start + 1;
|
||||
res.statusCode = 206;
|
||||
res.setHeader('Content-Range', 'bytes '
|
||||
+ opts.start
|
||||
+ '-'
|
||||
+ opts.end
|
||||
+ '/'
|
||||
+ stat.size);
|
||||
// invalid
|
||||
} else {
|
||||
return fn
|
||||
? fn(new Error('Requested Range Not Satisfiable'))
|
||||
: invalidRange(res);
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader('Content-Length', chunkSize);
|
||||
|
||||
// transfer
|
||||
if (head) return res.end();
|
||||
|
||||
// stream
|
||||
var stream = fs.createReadStream(path, opts);
|
||||
req.emit('static', stream);
|
||||
stream.pipe(res);
|
||||
|
||||
// callback
|
||||
if (fn) {
|
||||
function callback(err) { done || fn(err); done = true }
|
||||
req.on('close', callback);
|
||||
stream.on('end', callback);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,175 @@
|
|||
|
||||
/*!
|
||||
* Connect - staticCache
|
||||
* Copyright(c) 2011 Sencha Inc.
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, utils = require('../utils')
|
||||
, Cache = require('../cache')
|
||||
, url = require('url')
|
||||
, fs = require('fs');
|
||||
|
||||
/**
|
||||
* Enables a memory cache layer on top of
|
||||
* the `static()` middleware, serving popular
|
||||
* static files.
|
||||
*
|
||||
* By default a maximum of 128 objects are
|
||||
* held in cache, with a max of 256k each,
|
||||
* totalling ~32mb.
|
||||
*
|
||||
* A Least-Recently-Used (LRU) cache algo
|
||||
* is implemented through the `Cache` object,
|
||||
* simply rotating cache objects as they are
|
||||
* hit. This means that increasingly popular
|
||||
* objects maintain their positions while
|
||||
* others get shoved out of the stack and
|
||||
* garbage collected.
|
||||
*
|
||||
* Benchmarks:
|
||||
*
|
||||
* static(): 2700 rps
|
||||
* node-static: 5300 rps
|
||||
* static() + staticCache(): 7500 rps
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `maxObjects` max cache objects [128]
|
||||
* - `maxLength` max cache object length 256kb
|
||||
*
|
||||
* @param {Type} name
|
||||
* @return {Type}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
module.exports = function staticCache(options){
|
||||
var options = options || {}
|
||||
, cache = new Cache(options.maxObjects || 128)
|
||||
, maxlen = options.maxLength || 1024 * 256;
|
||||
|
||||
return function staticCache(req, res, next){
|
||||
var path = url.parse(req.url).pathname
|
||||
, ranges = req.headers.range
|
||||
, hit = cache.get(path)
|
||||
, hitCC
|
||||
, uaCC
|
||||
, header
|
||||
, age;
|
||||
|
||||
// cache static
|
||||
req.on('static', function(stream){
|
||||
var headers = res._headers
|
||||
, cc = utils.parseCacheControl(headers['cache-control'] || '')
|
||||
, contentLength = headers['content-length']
|
||||
, hit;
|
||||
|
||||
// ignore larger files
|
||||
if (!contentLength || contentLength > maxlen) return;
|
||||
|
||||
// dont cache items we shouldn't be
|
||||
if ( cc['no-cache']
|
||||
|| cc['no-store']
|
||||
|| cc['private']
|
||||
|| cc['must-revalidate']) return;
|
||||
|
||||
// if already in cache then validate
|
||||
if (hit = cache.get(path)){
|
||||
if (headers.etag == hit[0].etag) {
|
||||
hit[0].date = new Date;
|
||||
return;
|
||||
} else {
|
||||
cache.remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
// validation notifiactions don't contain a steam
|
||||
if (null == stream) return;
|
||||
|
||||
// add the cache object
|
||||
var arr = cache.add(path);
|
||||
arr.push(headers);
|
||||
|
||||
// store the chunks
|
||||
stream.on('data', function(chunk){
|
||||
arr.push(chunk);
|
||||
});
|
||||
|
||||
// flag it as complete
|
||||
stream.on('end', function(){
|
||||
arr.complete = true;
|
||||
});
|
||||
});
|
||||
|
||||
// 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'] || '');
|
||||
|
||||
// check if we must revalidate(bypass)
|
||||
if (hitCC['no-cache'] || uaCC['no-cache']) return next();
|
||||
|
||||
// check freshness of entity
|
||||
if (isStale(hitCC, age) || isStale(uaCC, age)) return next();
|
||||
|
||||
// conditional GET support
|
||||
if (utils.conditionalGET(req)) {
|
||||
if (!utils.modified(req, res, header)) {
|
||||
header['content-length'] = 0;
|
||||
res.writeHead(304, header);
|
||||
return res.end();
|
||||
}
|
||||
}
|
||||
|
||||
// HEAD support
|
||||
if ('HEAD' == req.method) {
|
||||
header['content-length'] = 0;
|
||||
res.writeHead(200, header);
|
||||
return res.end();
|
||||
}
|
||||
|
||||
// respond with cache
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return write(1);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if cache item is stale
|
||||
*
|
||||
* @param {Object} cc
|
||||
* @param {Number} age
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function isStale(cc, age) {
|
||||
return cc['max-age'] && cc['max-age'] <= age;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
/*!
|
||||
* Connect - vhost
|
||||
* Copyright(c) 2010 Sencha Inc.
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Setup vhost for the given `hostname` and `server`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* connect(
|
||||
* connect.vhost('foo.com',
|
||||
* connect.createServer(...middleware...)
|
||||
* ),
|
||||
* connect.vhost('bar.com',
|
||||
* connect.createServer(...middleware...)
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* @param {String} hostname
|
||||
* @param {Server} server
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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, '(.*?)') + '$');
|
||||
if (server.onvhost) server.onvhost(hostname);
|
||||
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();
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
|
||||
/*!
|
||||
* Connect
|
||||
* Copyright(c) 2011 TJ Holowaychuk
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var http = require('http')
|
||||
, res = http.OutgoingMessage.prototype;
|
||||
|
||||
// original setHeader()
|
||||
|
||||
var setHeader = res.setHeader;
|
||||
|
||||
// original _renderHeaders()
|
||||
|
||||
var _renderHeaders = res._renderHeaders;
|
||||
|
||||
if (res._hasConnectPatch) return;
|
||||
|
||||
/**
|
||||
* Provide a public "header sent" flag
|
||||
* until node does.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.__defineGetter__('headerSent', function(){
|
||||
return this._headerSent;
|
||||
});
|
||||
|
||||
/**
|
||||
* Set header `field` to `val`, special-casing
|
||||
* the `Set-Cookie` field for multiple support.
|
||||
*
|
||||
* @param {String} field
|
||||
* @param {String} val
|
||||
* @api public
|
||||
*/
|
||||
|
||||
res.setHeader = function(field, val){
|
||||
var key = field.toLowerCase()
|
||||
, prev;
|
||||
|
||||
// special-case Set-Cookie
|
||||
if (this._headers && 'set-cookie' == key) {
|
||||
if (prev = this.getHeader(field)) {
|
||||
val = Array.isArray(prev)
|
||||
? prev.concat(val)
|
||||
: [prev, val];
|
||||
}
|
||||
// charset
|
||||
} else if ('content-type' == key && this.charset) {
|
||||
val += '; charset=' + this.charset;
|
||||
}
|
||||
|
||||
return setHeader.call(this, field, val);
|
||||
};
|
||||
|
||||
/**
|
||||
* Proxy `res.end()` to expose a 'header' event,
|
||||
* allowing arbitrary augmentation before the header
|
||||
* fields are written to the socket.
|
||||
*
|
||||
* NOTE: this _only_ supports node's progressive header
|
||||
* field API aka `res.setHeader()`.
|
||||
*/
|
||||
|
||||
res._renderHeaders = function(){
|
||||
this.emit('header');
|
||||
return _renderHeaders.call(this);
|
||||
};
|
||||
|
||||
res._hasConnectPatch = true;
|
|
@ -0,0 +1,75 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>listing directory {directory}</title>
|
||||
<style>{style}</style>
|
||||
<script>
|
||||
function $(id){
|
||||
var el = 'string' == typeof id
|
||||
? document.getElementById(id)
|
||||
: id;
|
||||
|
||||
el.on = function(event, fn){
|
||||
if ('content loaded' == event) event = 'DOMContentLoaded';
|
||||
el.addEventListener(event, fn, false);
|
||||
};
|
||||
|
||||
el.all = function(selector){
|
||||
return $(el.querySelectorAll(selector));
|
||||
};
|
||||
|
||||
el.each = function(fn){
|
||||
for (var i = 0, len = el.length; i < len; ++i) {
|
||||
fn($(el[i]), i);
|
||||
}
|
||||
};
|
||||
|
||||
el.getClasses = function(){
|
||||
return this.getAttribute('class').split(/\s+/);
|
||||
};
|
||||
|
||||
el.addClass = function(name){
|
||||
var classes = this.getAttribute('class');
|
||||
el.setAttribute('class', classes
|
||||
? classes + ' ' + name
|
||||
: name);
|
||||
};
|
||||
|
||||
el.removeClass = function(name){
|
||||
var classes = this.getClasses().filter(function(curr){
|
||||
return curr != name;
|
||||
});
|
||||
this.setAttribute('class', classes);
|
||||
};
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
function search() {
|
||||
var str = $('search').value
|
||||
, links = $('files').all('a');
|
||||
|
||||
links.each(function(link){
|
||||
var text = link.textContent;
|
||||
|
||||
if ('..' == text) return;
|
||||
if (str.length && ~text.indexOf(str)) {
|
||||
link.addClass('highlight');
|
||||
} else {
|
||||
link.removeClass('highlight');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(window).on('content loaded', function(){
|
||||
$('search').on('keyup', search);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body class="directory">
|
||||
<input id="search" type="text" placeholder="Search" autocomplete="off" />
|
||||
<div id="wrapper">
|
||||
<h1>{linked-path}</h1>
|
||||
{files}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>{error}</title>
|
||||
<style>{style}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<h1>{title}</h1>
|
||||
<h2><em>500</em> {error}</h2>
|
||||
<ul id="stacktrace">{stack}</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 635 B |
After Width: | Height: | Size: 739 B |
After Width: | Height: | Size: 794 B |
After Width: | Height: | Size: 818 B |
After Width: | Height: | Size: 663 B |
After Width: | Height: | Size: 740 B |
After Width: | Height: | Size: 807 B |
After Width: | Height: | Size: 793 B |
After Width: | Height: | Size: 817 B |
After Width: | Height: | Size: 879 B |
After Width: | Height: | Size: 833 B |
After Width: | Height: | Size: 779 B |
After Width: | Height: | Size: 621 B |
After Width: | Height: | Size: 801 B |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 830 B |
After Width: | Height: | Size: 813 B |
After Width: | Height: | Size: 703 B |
After Width: | Height: | Size: 641 B |
After Width: | Height: | Size: 858 B |
After Width: | Height: | Size: 774 B |
After Width: | Height: | Size: 294 B |
After Width: | Height: | Size: 591 B |
BIN
node_modules/connect/lib/public/icons/page_white_actionscript.png
generated
vendored
Executable file
After Width: | Height: | Size: 664 B |
After Width: | Height: | Size: 512 B |
After Width: | Height: | Size: 587 B |
After Width: | Height: | Size: 656 B |
After Width: | Height: | Size: 666 B |
After Width: | Height: | Size: 603 B |
BIN
node_modules/connect/lib/public/icons/page_white_code_red.png
generated
vendored
Executable file
After Width: | Height: | Size: 587 B |
BIN
node_modules/connect/lib/public/icons/page_white_coldfusion.png
generated
vendored
Executable file
After Width: | Height: | Size: 592 B |
BIN
node_modules/connect/lib/public/icons/page_white_compressed.png
generated
vendored
Executable file
After Width: | Height: | Size: 724 B |
After Width: | Height: | Size: 309 B |
BIN
node_modules/connect/lib/public/icons/page_white_cplusplus.png
generated
vendored
Executable file
After Width: | Height: | Size: 621 B |
After Width: | Height: | Size: 700 B |
After Width: | Height: | Size: 639 B |
BIN
node_modules/connect/lib/public/icons/page_white_database.png
generated
vendored
Executable file
After Width: | Height: | Size: 579 B |
After Width: | Height: | Size: 536 B |
After Width: | Height: | Size: 638 B |
After Width: | Height: | Size: 618 B |
After Width: | Height: | Size: 623 B |
After Width: | Height: | Size: 663 B |
After Width: | Height: | Size: 676 B |
After Width: | Height: | Size: 582 B |
BIN
node_modules/connect/lib/public/icons/page_white_freehand.png
generated
vendored
Executable file
After Width: | Height: | Size: 639 B |
After Width: | Height: | Size: 402 B |
After Width: | Height: | Size: 516 B |
After Width: | Height: | Size: 612 B |
After Width: | Height: | Size: 603 B |
BIN
node_modules/connect/lib/public/icons/page_white_horizontal.png
generated
vendored
Executable file
After Width: | Height: | Size: 296 B |
After Width: | Height: | Size: 616 B |
BIN
node_modules/connect/lib/public/icons/page_white_lightning.png
generated
vendored
Executable file
After Width: | Height: | Size: 669 B |
After Width: | Height: | Size: 614 B |
After Width: | Height: | Size: 554 B |
After Width: | Height: | Size: 706 B |
After Width: | Height: | Size: 779 B |
After Width: | Height: | Size: 688 B |
BIN
node_modules/connect/lib/public/icons/page_white_paintbrush.png
generated
vendored
Executable file
After Width: | Height: | Size: 618 B |
After Width: | Height: | Size: 620 B |
After Width: | Height: | Size: 538 B |