router.js的代碼其實是router/index.js,里面的代碼是exPRess的路由的核心和入口。下面我們看一下重要的代碼。
proto.handle = function handle(req, res, out) { var self = this; debug('dispatching %s %s', req.method, req.url); var search = 1 + req.url.indexOf('?');//搜索參數的位置 var pathlength = search ? search - 1 : req.url.length;//url路徑的長度 var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://'); //如果url不以/開頭,則找出://的位置 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 parentParams = req.params; var parentUrl = req.baseUrl || ''; var done = restore(out, req, 'baseUrl', 'next', 'params'); // setup next layer req.next = next; // for options requests, respond with a default if nothing else responds if (req.method === 'OPTIONS') { done = wrap(done, function(old, err) { if (err || options.length === 0) return old(err); sendOptionsResponse(res, options, old); }); } // setup basic req values req.baseUrl = parentUrl; req.originalUrl = req.originalUrl || req.url; next(); function next(err) { var layerError = err === 'route' ? null : err; // remove added slash if (slashAdded) { req.url = req.url.substr(1); slashAdded = false; } // restore altered req.url if (removed.length !== 0) { req.baseUrl = parentUrl; req.url = protohost + removed + req.url.substr(protohost.length); removed = ''; } // no more matching layers if (idx >= stack.length) { setImmediate(done, layerError); return; } // get pathname of request var path = getPathname(req); if (path == null) { return done(layerError); } // find next matching layer var layer; var match; var route; while (match !== true && idx < stack.length) {//idx在是遞增的變量,不需要置0,while的邏輯為在路由棧中找到每個匹配path的layer并且一個個執行 layer = stack[idx++]; match = matchLayer(layer, path); route = layer.route; if (typeof match !== 'boolean') { // hold on to layerError layerError = layerError || match; } if (match !== true) {//沒有匹配 continue; } if (!route) {// // process non-route handlers normally continue; } if (layerError) { // routes do not match with a pending error match = false; continue; } var method = req.method; var has_method = route._handles_method(method);//因為是使用use方法增加的,所以不需要去判斷是否匹配了請求方法 // build up automatic options response if (!has_method && method === 'OPTIONS') { appendMethods(options, route._options()); } // don't even bother matching route if (!has_method && method !== 'HEAD') { match = false; continue; } } // no match if (match !== true) {//找不到匹配的路由,執行執行done return done(layerError); } // store route for dispatch on change if (route) { req.route = route; } // Capture one-time layer values req.params = self.mergeParams ? mergeParams(layer.params, parentParams) : layer.params; var layerPath = layer.path; // this should be done for the layer self.process_params(layer, paramcalled, req, res, function (err) { if (err) { return next(layerError || err); } if (route) { return layer.handle_request(req, res, next); } trim_prefix(layer, layerError, layerPath, path); }); } function trim_prefix(layer, layerError, layerPath, path) { var c = path[layerPath.length]; if (c && '/' !== c && '.' !== c) return next(layerError); // Trim off the part of the url that matches the route // middleware (.use stuff) needs to have the path stripped if (layerPath.length !== 0) { debug('trim prefix (%s) from url %s', layerPath, req.url); removed = layerPath; 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) req.baseUrl = parentUrl + (removed[removed.length - 1] === '/' ? removed.substring(0, removed.length - 1) : removed); } debug('%s %s : %s', layer.name, layerPath, req.originalUrl); if (layerError) { layer.handle_error(layerError, req, res, next); } else { layer.handle_request(req, res, next); } }};proto.use = function use(fn) { var offset = 0; var path = '/'; // default path to '/' // disambiguate router.use([fn]) if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } // first arg is the path if (typeof arg !== 'function') { offset = 1; path = fn; } } var callbacks = flatten(slice.call(arguments, offset)); if (callbacks.length === 0) { throw new TypeError('Router.use() requires middleware functions'); } for (var i = 0; i < callbacks.length; i++) { var fn = callbacks[i]; if (typeof fn !== 'function') { throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn)); } // add the middleware debug('use %s %s', path, fn.name || '<anonymous>'); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; this.stack.push(layer); } return this;};proto.route = function route(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;//返回該路由對象,讓用戶配置相關的路徑和回調};1.從use和route函數的代碼中我們知道,這兩個函數存儲路由數據的方式是不一樣的。雖然都是通過往router的stack里累加layer,但use是里的layer對應的回調是傳進來的fn,而route里的layer對應的回調是route的dispatch,并且通過返回route對象,讓用戶配置相關的路徑和回調。
2.handle函數是處理路由的入口,也是核心的代碼,其中的邏輯比較多,我們主要關注一下next函數和里面的while邏輯,while的邏輯主要是在路由的二維數組中(見route分析那章)逐行查找匹配的路由,直到找到一個匹配的路由,如果找到了一個匹配的路由,則暫時停止查找,并且利于idx來記住當前的位置。然后把邏輯轉到layer層中。
3.通過1的分析,我們知道,轉到layer層的時候,可能只是執行一個fn,也可能是執行route對象的dispatch,不過對于router對象來說,這些都是透明的,執行完layer層后,layer層的函數會通過router傳過去的next回到router的next函數邏輯中,然后基于idx位置繼續查找匹配的路由,繼續以上的過程,知道idx等于stack的長度。查找結束。
新聞熱點
疑難解答