Mercurial > hg > nodescore
diff node_modules/express/lib/router/index.js @ 73:0c3a2942ddee
now using express to server static content
author | Rob Canning <rc@kiben.net> |
---|---|
date | Sun, 29 Jun 2014 12:11:51 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/node_modules/express/lib/router/index.js Sun Jun 29 12:11:51 2014 +0000 @@ -0,0 +1,452 @@ +/** + * Module dependencies. + */ + +var Route = require('./route'); +var Layer = require('./layer'); +var methods = require('methods'); +var debug = require('debug')('express:router'); +var parseUrl = require('parseurl'); +var slice = Array.prototype.slice; + +/** + * Initialize a new `Router` with the given `options`. + * + * @param {Object} options + * @return {Router} which is an callable function + * @api public + */ + +var proto = module.exports = function(options) { + options = options || {}; + + function router(req, res, next) { + router.handle(req, res, next); + } + + // mixin Router class functions + router.__proto__ = proto; + + router.params = {}; + router._params = []; + router.caseSensitive = options.caseSensitive; + router.strict = options.strict; + router.stack = []; + + return router; +}; + +/** + * Map the given param placeholder `name`(s) to the given callback. + * + * Parameter mapping is used to provide pre-conditions to routes + * which use normalized placeholders. For example a _:user_id_ parameter + * could automatically load a user's information from the database without + * any additional code, + * + * The callback uses the same signature as middleware, the only difference + * being that the value of the placeholder is passed, in this case the _id_ + * of the user. Once the `next()` function is invoked, just like middleware + * it will continue on to execute the route, or subsequent parameter functions. + * + * Just like in middleware, you must either respond to the request or call next + * to avoid stalling the request. + * + * app.param('user_id', function(req, res, next, id){ + * User.find(id, function(err, user){ + * if (err) { + * return next(err); + * } else if (!user) { + * return next(new Error('failed to load user')); + * } + * req.user = user; + * next(); + * }); + * }); + * + * @param {String} name + * @param {Function} fn + * @return {app} for chaining + * @api public + */ + +proto.param = function(name, fn){ + // param logic + if ('function' == typeof name) { + this._params.push(name); + return; + } + + // apply param functions + var params = this._params; + var len = params.length; + var ret; + + if (name[0] === ':') { + name = name.substr(1); + } + + for (var i = 0; i < len; ++i) { + if (ret = params[i](name, fn)) { + fn = ret; + } + } + + // ensure we end up with a + // middleware function + if ('function' != typeof fn) { + throw new Error('invalid param() call for ' + name + ', got ' + fn); + } + + (this.params[name] = this.params[name] || []).push(fn); + return this; +}; + +/** + * Dispatch a req, res into the router. + * + * @api private + */ + +proto.handle = function(req, res, done) { + var self = this; + + debug('dispatching %s %s', req.method, req.url); + + var method = req.method.toLowerCase(); + + var search = 1 + req.url.indexOf('?'); + var pathlength = search ? search - 1 : req.url.length; + var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://'); + var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : ''; + var idx = 0; + var removed = ''; + var slashAdded = false; + var paramcalled = {}; + + // store options for OPTIONS request + // only used if OPTIONS request + var options = []; + + // middleware and routes + var stack = self.stack; + + // manage inter-router variables + var parent = req.next; + var parentUrl = req.baseUrl || ''; + done = wrap(done, function(old, err) { + req.baseUrl = parentUrl; + req.next = parent; + old(err); + }); + req.next = next; + + // for options requests, respond with a default if nothing else responds + if (method === 'options') { + done = wrap(done, function(old, err) { + if (err || options.length === 0) return old(err); + + var body = options.join(','); + return res.set('Allow', body).send(body); + }); + } + + next(); + + function next(err) { + if (err === 'route') { + err = undefined; + } + + var layer = stack[idx++]; + var layerPath; + + if (!layer) { + return done(err); + } + + if (slashAdded) { + req.url = req.url.substr(1); + slashAdded = false; + } + + req.baseUrl = parentUrl; + req.url = protohost + removed + req.url.substr(protohost.length); + req.originalUrl = req.originalUrl || req.url; + removed = ''; + + try { + var path = parseUrl(req).pathname; + if (undefined == path) path = '/'; + + if (!layer.match(path)) return next(err); + + // route object and not middleware + var route = layer.route; + + // if final route, then we support options + if (route) { + // we don't run any routes with error first + if (err) { + return next(err); + } + + req.route = route; + + // we can now dispatch to the route + if (method === 'options' && !route.methods['options']) { + options.push.apply(options, route._options()); + } + } + + // Capture one-time layer values + req.params = layer.params; + layerPath = layer.path; + + // this should be done for the layer + return self.process_params(layer, paramcalled, req, res, function(err) { + if (err) { + return next(err); + } + + if (route) { + return layer.handle(req, res, next); + } + + trim_prefix(); + }); + + } catch (err) { + next(err); + } + + function trim_prefix() { + var c = path[layerPath.length]; + if (c && '/' != c && '.' != c) return next(err); + + // Trim off the part of the url that matches the route + // middleware (.use stuff) needs to have the path stripped + removed = layerPath; + if (removed.length) { + debug('trim prefix (%s) from url %s', layerPath, req.url); + req.url = protohost + req.url.substr(protohost.length + removed.length); + } + + // Ensure leading slash + if (!fqdn && req.url[0] !== '/') { + req.url = '/' + req.url; + slashAdded = true; + } + + // Setup base URL (no trailing slash) + if (removed.length && removed.substr(-1) === '/') { + req.baseUrl = parentUrl + removed.substring(0, removed.length - 1); + } else { + req.baseUrl = parentUrl + removed; + } + + debug('%s %s : %s', layer.handle.name || 'anonymous', layerPath, req.originalUrl); + var arity = layer.handle.length; + try { + if (err && arity === 4) { + layer.handle(err, req, res, next); + } else if (!err && arity < 4) { + layer.handle(req, res, next); + } else { + next(err); + } + } catch (err) { + next(err); + } + } + } + + function wrap(old, fn) { + return function () { + var args = [old].concat(slice.call(arguments)); + fn.apply(this, args); + }; + } +}; + +/** + * Process any parameters for the layer. + * + * @api private + */ + +proto.process_params = function(layer, called, req, res, done) { + var params = this.params; + + // captured parameters from the layer, keys and values + var keys = layer.keys; + + // fast track + if (!keys || keys.length === 0) { + return done(); + } + + var i = 0; + var name; + var paramIndex = 0; + var key; + var paramVal; + var paramCallbacks; + var paramCalled; + + // process params in order + // param callbacks can be async + function param(err) { + if (err) { + return done(err); + } + + if (i >= keys.length ) { + return done(); + } + + paramIndex = 0; + key = keys[i++]; + + if (!key) { + return done(); + } + + name = key.name; + paramVal = req.params[name]; + paramCallbacks = params[name]; + paramCalled = called[name]; + + if (paramVal === undefined || !paramCallbacks) { + return param(); + } + + // param previously called with same value or error occurred + if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) { + // restore value + req.params[name] = paramCalled.value; + + // next param + return param(paramCalled.error); + } + + called[name] = paramCalled = { + error: null, + match: paramVal, + value: paramVal + }; + + try { + return paramCallback(); + } catch (err) { + return done(err); + } + } + + // single param callbacks + function paramCallback(err) { + var fn = paramCallbacks[paramIndex++]; + + // store updated value + paramCalled.value = req.params[key.name]; + + if (err) { + // store error + paramCalled.error = err; + param(err); + return; + } + + if (!fn) return param(); + + fn(req, res, paramCallback, paramVal, key.name); + } + + param(); +}; + +/** + * Use the given middleware function, with optional path, defaulting to "/". + * + * Use (like `.all`) will run for any http METHOD, but it will not add + * handlers for those methods so OPTIONS requests will not consider `.use` + * functions even if they could respond. + * + * The other difference is that _route_ path is stripped and not visible + * to the handler function. The main effect of this feature is that mounted + * handlers can operate without any code changes regardless of the "prefix" + * pathname. + * + * @param {String|Function} route + * @param {Function} fn + * @return {app} for chaining + * @api public + */ + +proto.use = function(route, fn){ + // default route to '/' + if ('string' != typeof route) { + fn = route; + route = '/'; + } + + if (typeof fn !== 'function') { + var type = {}.toString.call(fn); + var msg = 'Router.use() requires callback functions but got a ' + type; + throw new Error(msg); + } + + // strip trailing slash + if ('/' == route[route.length - 1]) { + route = route.slice(0, -1); + } + + var layer = new Layer(route, { + sensitive: this.caseSensitive, + strict: this.strict, + end: false + }, fn); + + // add the middleware + debug('use %s %s', route || '/', fn.name || 'anonymous'); + + this.stack.push(layer); + return this; +}; + +/** + * Create a new Route for the given path. + * + * Each route contains a separate middleware stack and VERB handlers. + * + * See the Route api documentation for details on adding handlers + * and middleware to routes. + * + * @param {String} path + * @return {Route} + * @api public + */ + +proto.route = function(path){ + var route = new Route(path); + + var layer = new Layer(path, { + sensitive: this.caseSensitive, + strict: this.strict, + end: true + }, route.dispatch.bind(route)); + + layer.route = route; + + this.stack.push(layer); + return route; +}; + +// create Router#VERB functions +methods.concat('all').forEach(function(method){ + proto[method] = function(path){ + var route = this.route(path) + route[method].apply(route, [].slice.call(arguments, 1)); + return this; + }; +});