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 || '' );
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的长度。查找结束。