express之router
发布日期:2021-05-28 16:24:01 浏览次数:11 分类:技术文章

本文共 13686 字,大约阅读时间需要 45 分钟。

lib/router/index.js//对象正则表达式,用来匹配对象toString方法的结果var objectRegExp = /^\[object (\S+)\]$/;var slice = Array.prototype.slice;var toString = Object.prototype.toString;//router对象的构造函数var proto = module.exports = function(options) {  var opts = options || {};  //router对象本身也是一个标准的express请求处理函数,内部调用了它的handle方法  function router(req, res, next) {    router.handle(req, res, next);  }  // 上面的函数对象继承了构造函数  setPrototypeOf(router, proto)  //路径参数名与它的处理函数数组的映射  router.params = {};  router._params = [];  //大小写敏感  router.caseSensitive = opts.caseSensitive;  //合并路径参数  router.mergeParams = opts.mergeParams;  //是否严格匹配  router.strict = opts.strict;  //Layer的堆,layer的handle为挂载在router上不同路径的中间件与Route对象的dispatcher函数  router.stack = [];  return router;};//为指定的路径参数名设置处理函数proto.param = function param(name, fn) {  //如果第一个参数为函数  if (typeof name === 'function') {    deprecate('router.param(fn): Refactor to use path params');    this._params.push(name);    return;  }  // apply param functions  var params = this._params;  var len = params.length;  var ret;  //路径参数名不用带:,在path中表示时会带:  if (name[0] === ':') {    deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead');    //去掉:    name = name.substr(1);  }  //遍历_params的函数  for (var i = 0; i < len; ++i) {    //执行函数,获取新的fn    if (ret = params[i](name, fn)) {      fn = ret;    }  }  if ('function' !== typeof fn) {    throw new Error('invalid param() call for ' + name + ', got ' + fn);  }  //添加到params数组  (this.params[name] = this.params[name] || []).push(fn);  return this;};//router的请求处理函数,根router被express实例对象调用//其他router对象,被所属Layer的请求处理方法调用proto.handle = function handle(req, res, out) {  var self = this;  //堆中Layer下标  var idx = 0;  //协议+域名+端口  var protohost = getProtohost(req.url) || ''  //被移除的路径  var removed = '';  //添加斜杠  var slashAdded = false;  //已经被处理的路径参数  var paramcalled = {};  //可以处理的方法数组  var options = [];  //获取Layer数组,layer的handle为中间件处理函数或者Route的dispatch函数或者router本身  var stack = self.stack;  //req的params,可能由这个router之前的处理函数设置  var parentParams = req.params;  //baseUrl为之前匹配到的位置,接下来的路径将要被匹配  var parentUrl = req.baseUrl || '';  //获取一个结束函数done,done会恢复req上的指定键值为当前时刻状态,并且将req传递给out方法并调用它  //因为在下面可能会重置req上的这几个属性  var done = restore(out, req, 'baseUrl', 'next', 'params');  //设置next递归方法  req.next = next;  //如果是options请求  if (req.method === 'OPTIONS') {    //包装done    done = wrap(done, function(old, err) {      if (err || options.length === 0) return old(err);      //发送options响应,告诉客户端可接受的http请求      sendOptionsResponse(res, options, old);    });  }  //基本url  req.baseUrl = parentUrl;  //原始url  req.originalUrl = req.originalUrl || req.url;  //执行next函数  next();  //匹配函数,递归匹配  function next(err) {    //如果err为route则layer错误为null    var layerError = err === 'route'      ? null      : err;    //如果添加了斜杠    if (slashAdded) {      //移除斜杠      req.url = req.url.substr(1);      slashAdded = false;    }    // 如果移除了url的一部分,恢复被改变的url    //因为在匹配过程中可能会移除匹配到的部分路径    if (removed.length !== 0) {      //重置基本Url      req.baseUrl = parentUrl;      //协议+域名+端口+移除部分+之前的path部分      req.url = protohost + removed + req.url.substr(protohost.length);      //移除部分为空      removed = '';    }    //router错误代表要离开router,调用done    if (layerError === 'router') {      setImmediate(done, null)      return    }    //当下标超出时,说明layer已经匹配完毕,调用done方法    if (idx >= stack.length) {      setImmediate(done, layerError);      return;    }    //获取请求的path,即端口号后面部分,因为前面恢复了移除部分,所以这里获取的是全部的path    var path = getPathname(req);    //没有path,调用done    if (path == null) {      return done(layerError);    }    // 查找匹配Layer    var layer;    //是否匹配    var match;    //layer的route,只有路由才能处理请求,结束请求,中间件不行    var route;    //未匹配且下标在正确范围内时    while (match !== true && idx < stack.length) {      //从堆中获取Layer      layer = stack[idx++];      //使用Layer匹配路径      match = matchLayer(layer, path);      //获取Layer的route      //只有使用route方法添加的Layer才有route属性      //使用use方法添加的中间件Layer没有route      route = layer.route;      //如果匹配结果类型不正确      if (typeof match !== 'boolean') {        // 错误        layerError = layerError || match;      }      //如果没有匹配上,继续      if (match !== true) {        continue;      }      //如果route不存在,说明是use方法添加的中间件函数      //layer.route只有在使用route方法时才会存在,此时layer的handle为route的dispatch方法      //!!!注意这里,match已经为true了,而continue就会出while循环,下面会处理中间件,处理完毕后会递归next,然后继续匹配      if (!route) {        continue;      }      //出现错误继续匹配      if (layerError) {        match = false;        continue;      }      //到这说明route存在,且匹配上了      //请求方法      var method = req.method;      //route是否能处理这个方法      var has_method = route._handles_method(method);      //不能处理且为OPTIONS方法      if (!has_method && method === 'OPTIONS') {        //将route可以处理的方法全部加入options数组        appendMethods(options, route._options());      }      // 不能处理且方法不是head,继续匹配,说明这个Layer对应的虽然path匹配上了,但是其route不能处理req的http方法      if (!has_method && method !== 'HEAD') {        match = false;        continue;      }    }    //到这说明递归匹配结束了,此时要么下标超出,要么match为true    // 遍历所有layer依然没有匹配项,调用done    if (match !== true) {      return done(layerError);    }    //匹配上之后,可能为中间件或者包含route的Layer    //如果route存在,说明为路由层,存储route对象到req    if (route) {      req.route = route;    }    // 如果选项中指明了合并路径参数,则req上的params重新设置为合并后的路径参数    //即将之前得到的路径参数与这一层Layer得到的路径参数合并    req.params = self.mergeParams      ? mergeParams(layer.params, parentParams)      : layer.params;    //层路径,在match方法中被重新设置    var layerPath = layer.path;    //处理路径参数值,第二个参数为处理后的路径参数对象    self.process_params(layer, paramcalled, req, res, function (err) {      //处理完毕之后的回调      if (err) {        //有错误next        return next(layerError || err);      }      //如果route存在,说明是路由Layer,调用layer的请求处理方法,实际上调用了Route的dispatch方法,分发请求      if (route) {        return layer.handle_request(req, res, next);      }      //如果route不存在,说明是中间件方法      //修剪前缀      trim_prefix(layer, layerError, layerPath, path);    });  }  //修剪前缀,注意这个方法是handle的内部方法,在next中调用  //参数一为匹配到的中间件层,参数三为层路径,参数四为完整的请求path  function trim_prefix(layer, layerError, layerPath, path) {    //layer路径长度不为0    if (layerPath.length !== 0) {      //获取path中layerPath的下一个字符      var c = path[layerPath.length]      //如果不是/也不是.,返回错误,因为每一层匹配的路径应该以/分隔      //比如path为/aaa/bbb,layerPath为/aa,则不处理      if (c && c !== '/' && c !== '.') return next(layerError)      //removed,即代表被匹配的这一层路径layerPath会被从req.url中移除掉      //这个移除会在next的开始恢复      removed = layerPath;      //重置移除后的url      req.url = protohost + req.url.substr(protohost.length + removed.length);      //如果主机名不存在且url开始不包含/,加上一个/      if (!protohost && req.url[0] !== '/') {        req.url = '/' + req.url;        slashAdded = true;      }      //基本url,为上一次匹配的基本url加上被移除的部分,最后的/要去掉      //基本url的意思是,完整的url减去以后要匹配的url,就是基本的      req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'        ? removed.substring(0, removed.length - 1)        : removed);    }    //这一步执行中间件,执行完毕会返回next,则上面的移除layerPath行为,就是为了中间件而使用的    //如果有错误,调用错误处理方法,在handle_error中只有layer的handle为标准的错误处理中间件才会被调用,不然直接next    if (layerError) {      layer.handle_error(layerError, req, res, next);    }     //没有错误,直接调用请求处理方法,在handle_request中只有layer的handle为标准的请求处理中间件才会被调用    else {      layer.handle_request(req, res, next);    }  }};//处理这一层的路径参数值,即对这一层Layer来说,获取它的路径参数值,传递给相应的处理函数proto.process_params = function process_params(layer, called, req, res, done) {  var params = this.params;  //从Layer捕获的路径参数key数组  var keys = layer.keys;  //不存在直接done  if (!keys || keys.length === 0) {    return done();  }  //参数下标  var i = 0;  var name;  //参数回调函数下标  var paramIndex = 0;  //键对象,name属性为参数名  var key;  //参数值  var paramVal;  //回调函数数组  var paramCallbacks;  //参数已经被调用的结果  var paramCalled;  // 按顺序执行param的回调  // 回调函数可以是异步的  function param(err) {    //如果有错误,直接done结束    if (err) {      return done(err);    }    //如果下标超出,说明遍历完了所有路径参数,直接done    if (i >= keys.length ) {      return done();    }    //回调函数下标为0    paramIndex = 0;    //获取第一个键对象    key = keys[i++];    //获取第一个路径参数名    name = key.name;    //从req中获取路径参数值    paramVal = req.params[name];    //从router.params中获取指定路径参数名的回调    paramCallbacks = params[name];    //指定路径参数是否已经被调用    paramCalled = called[name];    //参数值不存在或者回调函数不存在,直接递归调用    if (paramVal === undefined || !paramCallbacks) {      return param();    }    //参数之前已经被调用,或者调用出错    if (paramCalled && (paramCalled.match === paramVal      || (paramCalled.error && paramCalled.error !== 'route'))) {      //重新存储参数值为之前调用结果      req.params[name] = paramCalled.value;      //递归调用      return param(paramCalled.error);    }    //设置已经被调用的对象    called[name] = paramCalled = {      //这是正常情况,没错      error: null,      match: paramVal,      value: paramVal    };    //为参数执行回调    paramCallback();  }  //为参数执行所有回调函数,递归调用,在回调函数执行完后会返回这个函数  function paramCallback(err) {    //获取指定下标回调函数    var fn = paramCallbacks[paramIndex++];    // 存储调用结果为req中值    paramCalled.value = req.params[key.name];    //出错    if (err) {      //存储错误,返回param函数      paramCalled.error = err;      param(err);      return;    }    //回调函数不存在,返回param    if (!fn) return param();    try {      //执行回调函数,执行完返回paramCallback      fn(req, res, paramCallback, paramVal, key.name);    } catch (e) {      paramCallback(e);    }  }  //开始执行  param();};//添加中间件函数,可以添加普通中间件、router对象等//中间件函数会对指定路径的所有http请求运行proto.use = function use(fn) {  //函数参数的默认偏移  var offset = 0;  //默认路径  var path = '/';  //第一个参数不是函数  if (typeof fn !== 'function') {    var arg = fn;    //一直获取到不是数组的第一个元素    while (Array.isArray(arg) && arg.length !== 0) {      arg = arg[0];    }    //第一个参数是路径,函数参数偏移为1    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 a middleware function')  }  //遍历中间件函数  for (var i = 0; i < callbacks.length; i++) {    var fn = callbacks[i];    if (typeof fn !== 'function') {      throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))    }    debug('use %o %s', path, fn.name || '
') //使用指定路径、中间件函数创建Layer,继承了router对象的大小写敏感、严格等选项 var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); //这个layer没有route,说明他不是路由,只是中间件 layer.route = undefined; //添加到router的堆 this.stack.push(layer); } return this;};//创建一个指定路径的Route对象proto.route = function route(path) { //new Rotue对象 var route = new Route(path); //创建Layer对象,路径与Route相同,handle为Route的dispatch方法 //说明这个Layer会将请求交给Route的dispatch来处理,然后再dispatch中再进行相应http方法的处理 var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); //这个Layer有route属性,代表了它是一个路由层,而不是中间件层 layer.route = route; //与中间件层一样添加到堆中 this.stack.push(layer); return route;};// http方法属性,可以添加指定http方法、指定路径的路由处理函数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; };});//将addition数组不重复的添加到list数组function appendMethods(list, addition) { for (var i = 0; i < addition.length; i++) { var method = addition[i]; if (list.indexOf(method) === -1) { list.push(method); } }}// 获取请求的pathname,即端口号后面、查询?前面部分function getPathname(req) { try { return parseUrl(req).pathname; } catch (err) { return undefined; }}// 获取协议+域名function getProtohost(url) { if (typeof url !== 'string' || url.length === 0 || url[0] === '/') { return undefined } //查询问号 var searchIndex = url.indexOf('?') //不包含查询字符创的url长度 var pathLength = searchIndex !== -1 ? searchIndex : url.length //协议结束位置 var fqdnIndex = url.substr(0, pathLength).indexOf('://') //获取第一个/之前的部分,即协议+域名+端口 return fqdnIndex !== -1 ? url.substr(0, url.indexOf('/', 3 + fqdnIndex)) : undefined}// 获取obj的类型function gettype(obj) { var type = typeof obj; //不是对象直接返回 if (type !== 'object') { return type; } //是对象,如[object Array],进行替换 return toString.call(obj) .replace(objectRegExp, '$1');}//将路径与层匹配,调用了Layer的match//匹配过程,重新设置了Layer的path、params等属性function matchLayer(layer, path) { try { return layer.match(path); } catch (err) { return err; }}//合并路径参数,将匹配得到的路径参数,与之前匹配得到的路径参数合并function mergeParams(params, parent) { if (typeof parent !== 'object' || !parent) { return params; } //拷贝parent var obj = mixin({}, parent); //键不是数字,直接混合即可 if (!(0 in params) || !(0 in parent)) { return mixin(obj, params); } var i = 0; var o = 0; // 键是数字,获取极限值,此时为数组 while (i in params) { i++; } while (o in parent) { o++; } // 遍历设置 for (i--; i >= 0; i--) { //将params中原来的值后移 params[i + o] = params[i]; if (i < o) { delete params[i]; } } //将obj与params混合,此时obj中元素,会填补params后移漏出的空位 return mixin(obj, params);}// ?function restore(fn, obj) { //键数组、值数组 var props = new Array(arguments.length - 2); var vals = new Array(arguments.length - 2); for (var i = 0; i < props.length; i++) { //第三个参数起为键,存储键值 props[i] = arguments[i + 2]; vals[i] = obj[props[i]]; } //返回一个函数 return function () { // 值存回对象? for (var i = 0; i < props.length; i++) { obj[props[i]] = vals[i]; } //将参数传递给fn,调用它 return fn.apply(this, arguments); };}//发送OPTIONS为响应function sendOptionsResponse(res, options, next) { try { //options为数组,转化为字符串 var body = options.join(','); //Allow响应头代表了服务器支持的HTTP方法,以逗号分隔 res.set('Allow', body); res.send(body); } catch (err) { next(err); }}//包装函数,重复第一个参数,调用fn?function wrap(old, fn) { return function proxy() { // var args = new Array(arguments.length + 1); args[0] = old; for (var i = 0, len = arguments.length; i < len; i++) { args[i + 1] = arguments[i]; } fn.apply(this, args); };}

 

转载地址:https://blog.csdn.net/qq_27868061/article/details/79275735 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:express之route.js
下一篇:express之view.js

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年02月02日 05时59分20秒