express之router
发布日期:2021-05-28 16:24:01
浏览次数:31
分类:技术文章
本文共 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
路过,博主的博客真漂亮。。
[***.116.15.85]2024年12月29日 21时14分54秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
android 补间动画的实现
2019-06-23
2017年广东省ACM省赛(GDCPC-2017)总结
2019-06-23
第十届蓝桥杯B组C++题目详解和题型总结
2019-06-23
简单理解函数回调——同步回调与异步回调
2019-06-23
Android 多个Activity 跳转及传参
2019-06-23
anroid 广播
2019-06-23
AJAX POST&跨域 解决方案 - CORS
2019-06-23
关于最小生成树中的kruskal算法中判断两个点是否在同一个连通分量的方法总结...
2019-06-23
开篇,博客的申请理由
2019-06-23
JMeter IP欺骗压测
2019-06-23
最简单的RPC框架实现
2019-06-23
Servlet 技术全总结 (已完成,不定期增加内容)
2019-06-23
[JSOI2008]星球大战starwar BZOJ1015
2019-06-23
CountDownLatch与thread-join()的区别
2019-06-23
linux下MySQL安装登录及操作
2019-06-23
centos 7 部署LDAP服务
2019-06-23
揭秘马云帝国内幕:马云的野心有多大
2019-06-23
topcoder srm 680 div1
2019-06-23
算法专题(1)-信息学基本解题流程!
2019-06-23
iOS项目分层
2019-06-23