123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- 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;
- var proto = module.exports = function(options) {
- options = options || {};
- function router(req, res, next) {
- router.handle(req, res, next);
- }
-
- router.__proto__ = proto;
- router.params = {};
- router._params = [];
- router.caseSensitive = options.caseSensitive;
- router.strict = options.strict;
- router.stack = [];
- return router;
- };
- proto.param = function(name, fn){
-
- if ('function' == typeof name) {
- this._params.push(name);
- return;
- }
-
- 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;
- }
- }
-
-
- if ('function' != typeof fn) {
- throw new Error('invalid param() call for ' + name + ', got ' + fn);
- }
- (this.params[name] = this.params[name] || []).push(fn);
- return this;
- };
- 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 = {};
-
-
- var options = [];
-
- var stack = self.stack;
-
- 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;
-
- 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);
-
- var route = layer.route;
-
- if (route) {
-
- if (err) {
- return next(err);
- }
- req.route = route;
-
- if (method === 'options' && !route.methods['options']) {
- options.push.apply(options, route._options());
- }
- }
-
- req.params = layer.params;
- layerPath = layer.path;
-
- 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);
-
-
- 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);
- }
-
- if (!fqdn && req.url[0] !== '/') {
- req.url = '/' + req.url;
- slashAdded = true;
- }
-
- 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);
- };
- }
- };
- proto.process_params = function(layer, called, req, res, done) {
- var params = this.params;
-
- var keys = layer.keys;
-
- if (!keys || keys.length === 0) {
- return done();
- }
- var i = 0;
- var name;
- var paramIndex = 0;
- var key;
- var paramVal;
- var paramCallbacks;
- var paramCalled;
-
-
- 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();
- }
-
- if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) {
-
- req.params[name] = paramCalled.value;
-
- return param(paramCalled.error);
- }
- called[name] = paramCalled = {
- error: null,
- match: paramVal,
- value: paramVal
- };
- try {
- return paramCallback();
- } catch (err) {
- return done(err);
- }
- }
-
- function paramCallback(err) {
- var fn = paramCallbacks[paramIndex++];
-
- paramCalled.value = req.params[key.name];
- if (err) {
-
- paramCalled.error = err;
- param(err);
- return;
- }
- if (!fn) return param();
- fn(req, res, paramCallback, paramVal, key.name);
- }
- param();
- };
- proto.use = function(route, fn){
-
- 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);
- }
-
- if ('/' == route[route.length - 1]) {
- route = route.slice(0, -1);
- }
- var layer = new Layer(route, {
- sensitive: this.caseSensitive,
- strict: this.strict,
- end: false
- }, fn);
-
- debug('use %s %s', route || '/', fn.name || 'anonymous');
- this.stack.push(layer);
- return this;
- };
- 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;
- };
- 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;
- };
- });
|