寇阿相思树框架一直都保持着简洁性,它只对结节的超文本传送协议模块进行了封装,而在真正实际使用,我们还需要更多地像路由这样的模块来构建我们的应用,而寇阿相思树路由器是常用的寇阿相思树的路由库。这里通过解析寇阿相思树路由器的源码来达到深入学习的目的。
源码架构图
调用链路-路线()
超文本传送协议请求调用流程
使用
const Koa=require(' Koa ');const Router=require(' KOA-Router ');const app=new Koa();const Router=new Router();router.get('/'),async (ctx,next)={ console。日志('索引');CTX。body=" index});app.use(router.routes()).使用(路由器。allowedmethods());app。听(3000);路由器
功能路由器(opts) { if(!(路由器的这个实例){返回新路由器(opts);} this.opts=opts | | { }this。方法=这个。选择。方法| |[' HEAD ',' OPTIONS ',' GET ',' PUT ',' PATCH ',' POST ',' DELETE '];//存放路由器。参数方法指定的参数的中间件这个。params={ };//存放层实例这个。stack=[];};层
功能层(路径、方法、中间件){这个。opts=opts | | { };这个。名称=这个。选择。名称| | null这个。methods=[];//存放小路路径参数的一些属性,例如:/test/: str={ name : str,prefix: '/'.}这个。param name=[];//存放该路由的中间件this.stack=Array.isArray(中间件)?中间件:【中间件】;方法。foreach(函数(方法){ var l=this。方法。push(方法。touppercase());//如果支持得到请求,一并支持头请求如果(这个。方法[l-1]==' GET '){ this。方法。松开(“头”);} },这个);//确保中间件是函数这个。堆栈。foreach(function(fn){ var type=(fn的类型);如果(键入!==' function '){ 0抛出新的错误(方法。tostring()' ` '(这。选择。名称| |路径)' ` : '中间件` ' '必须是函数,而不是`‘type‘`’);} },这个);this.path=path/将路由转为正则表达式this.regexp=pathToRegExp(路径,this.paramNames,this。opts);调试('定义的路由%s %s,这个. methods,这个。选择。给这个加前缀。路径);};给路由器实例挂载超文本传送协议方法
/** *创建` router.verb()'方法,其中*动词*是超文本传送协议动词之一,如` router.get()'或` router.post()' .**使用" router.verb()"将统一资源定位器模式与回调函数或控制器操作进行匹配,* *其中**动词* *是超文本传送协议动词之一,如" router.get()"或router.post().* *此外,` router.all()'可用于匹配所有方法* * ```javascript *路由器*。get('/',(ctx,next)={ * ctx.body='Hello World!* }) * .post('/users ',(ctx,next)={ * //.* }) * .put('/users/:id ',(ctx,next)={ * //.* }) * .del('/users/:id ',(ctx,next)={ * //.* }) * .所有('/users/:id ',(ctx,next)={ * //.* });* ``` * *当路由匹配时,其路径在` ctx处可用_匹配路径`,如果命名为,*,该名称可在` ctx上获得_ MatcheTeRoutename `* *路由路径将使用*[路径到regexp](https://github。com/pillar js/路径到regexp)转换为正则表达式。* *匹配请求时将不考虑查询字符串。* * ####命名路由* *路由可以选择有名称。这允许在开发过程中生成网址和方便地重命名网址* * ` ` ` ` JavaScript *路由器。get(' user ','/users/:id ',(ctx,next)={ * //.* });* * router.url('user ',3);*//='/users/3 ' * ` `* * # # #多个中间件* *可以给多个中间件n : * * ` ` ` JavaScript *路由器。get(*/users/: id ',* (ctx,next)={ * return user。芬多内(CTX)。参数。id ).然后(函数(用户){ * CTX。用户=用户;* next();* });* },* CTX={ *控制台。原木(CTX。用户);* //={ id: 17,名称: ' Alex ' } * } *);* ` `* * # #嵌套路由器* *支持嵌套路由器: * * ` ` ` JavaScript * var论坛=new Router();* var post=new Router();* *帖子。get('/',(ctx,next)=}.});*张贴。get('/: PID ',(ctx,next)={ 0.});*转发。使用('/forward/: FID/post '、posts.routes()、post。allowedmethods());* * //回应"/论坛/123/帖子"和"/论坛/123/帖子/123"* app。使用(论坛。routes());* ``` * * ###路由器前缀* *路由路径可以在路由器级别加前缀: * * ` ` ` ` JavaScript * var Router=new Router({ *前缀: '/user ' * });* * router.get('/',);//响应/user ' *路由器。get('/: id ',);//响应/users/: id‘* ` ` `* * # # URL参数* *命名路由参数被捕获并添加到` ctx。帕拉姆斯。* * ` ` JavaScript *路由器。获取('/:类别/:标题',(CTX,下一个)={ *控制台。原木(CTX。参数);* //={ category: 'programming ',title : ' how-to-node ' } * });* ``` * *使用[路径到regexp](https://github。com/支柱js/路径到regexp)模块*将路径转换为正则表达式* * @ name get | put | post | patch | delete | del * @模块: KOA-Router的成员。原型* @ param { String }路径* @ param { Function=}中间件路由中间件* @param {Function}回调路由回调* @返回{ Router } */var methods=require(' methods ');methods.forEach(函数(方法){路由器。原型[方法]=函数(名称、路径、中间件){ var中间件;//如果指定了路由名字属性if(路径类型==' string ' | | RegExp的路径实例){ 0中间件=Array.prototype.slice.call(参数,2);} else {中间件=Array.prototype.slice.call(参数,1);路径=名称;name=null} //路由注册this.register(路径、[方法]、中间件、{ name : name });归还这个;};});路由器。原型。注册
/** *创建并注册路由* * @param {String}路径路径字符串* @param {Array .字符串}方法超文本传送协议动词数组* @param {Function}中间件也接受多个中间件。* @返回{ Layer } * @ private */路由器。原型。寄存器=功能(路径、方法、中间件、opts){ opts=opts | | { };var路由器=this//图层实例数组,初始为空数组var堆栈=this.stack//支持路径数组if (Array.isArray(路径)){ //如果是多路径,递归注册路由路径。foreach(功能(p){路由器。注册。调用(路由器,p,方法,中间件,opts);});归还这个;} //创建路由变化路由=新层(路径、方法、中间件,{ end: opts.end===false?opts.end : true,name: opts.name,敏感: opts。敏感| |这个。选择。敏感| |假,严格: opts。严格| |这个。选择。严格| |假,前缀3360 opts。前缀| |这个。选择。前缀| | ',ignorcaptures 3360 opts。ignorcaptures });//设置前置路由如果(这个。选择。前缀){ route。setprefix(这个。选择。前缀);} //添加参数中间件Object.keys(this.params).forEach(函数(参数){ //将路由器中这个参数维护的参数中间件挂载到层实例中route.param(param,这个。params[param]);},这个);//所有层实例存放在路由器的堆属性中stack.push(路由);返回路线;};路由器。原型。匹配
/** *匹配给定的"路径"并返回相应的路由* * @param {String}路径* @param {String}方法* @返回{对象.路径,路径和方法}返回匹配路径和*路径和方法的图层* @ private */路由器。原型。match=函数(路径,方法){ //层实例组成的数组var layers=this.stackvar层;var matched={ path: [],pathAndMethod: [],route : false };对于(变量len=layers.length,I=0;我透镜;I){ layer=layers[I];调试('测试%s %s,层。路径,层。regexp);//1.匹配路由if(层。匹配(路径)){匹配。路径。推(层);//2.匹配超文本传送协议(超文本传输协议的缩写)请求方法if(层。方法。长度====0 | | ~层。方法。indexof(method)){匹配。路径和方法。推(层);//3.指定了超文本传送协议(超文本传输协议的缩写)请求方法,判定为路由匹配成功如果(图层.方法.长度)匹配,=true} } }返回匹配的;};路由器。原型。路由
/** *返回路由器中间件,该中间件调度与请求匹配的路由。* * @返回{功能} */路由器。原型。routes=路由器。原型。中间件=Function(){ var router=this;var调度=函数调度(ctx,next) { debug('%s %s ',ctx.method,ctx。路径);//请求路由var path=路由器。选择。路由器路径| | CTX。路由器路径| | CTX。路径;//将注册路由和请求的路由进行匹配var matched=router.match(路径,CTX。方法);var layerChain,layer,I;如果(CTX。匹配){ CTX。匹配。用力。申请(CTX。匹配,匹配。路径);} else { CTX。匹配=匹配。路径;} ctx.router=router//路线属性是三次匹配的结果,表示最终是否匹配成功if(!匹配的. route)返回next();//同时满足路由匹配和超文本传送协议(超文本传输协议的缩写)请求方法的层数组var匹配层=匹配。小路和方法/匹配多个路由时认为最后一个是匹配有效的路由var MoST specificlayer=匹配层[匹配层。CTX ._匹配路由=MoST specificlayer。路径;if (mostSpecificLayer.name) { ctx ._ MatcheTeRotename=MoST specific layer。姓名;} //将匹配的路由减少为一个数组层链=匹配的层。减少(函数(备忘录,层){ //执行注册路由中间件之前,对语境中的一些参数进行设置memo.push(函数(ctx,下一个){ //:path/XXX捕获的路径ctx.captures=layer.captures(路径,CTX。捕获);//捕获的路径上的参数,{ key : value } CTX。params=图层。ctx,ctx.captures。参数);//路由名称CTX。RouterName=图层。姓名;返回next();});//返回路由中间件的数组返回备忘录。concat(图层。堆栈);}, []);//处理为承诺对象返回撰写(layerChain)(ctx,下一步);};dispatch.router=这个退货派单;};Router.prototype.allowedMethod
/** *返回单独的中间件,用于响应带有包含允许方法的"允许"头的"选项"请求,以及根据情况响应带有"405不允许的方法"和"501未实现"的*请求* * @ example * * ` ` ` JavaScript * var Koa=require(' Koa ');* var Router=require(' KOA-Router ');* * var app=new Koa();* var Router=新路由器();* * app。使用(路由器。routes());* app。使用(路由器。allowedmethods());* ` `* * * *带[嘣]的示例(https://github。com/hapi js/Boom)* * * * ` ` ` ` JavaScript * var Koa=require(' Koa ');* var Router=require(' KOA-Router ');* var Boom=必需(‘Boom’);* * var app=new Koa();* var Router=新路由器();* * app。使用(路由器。routes());* app。使用(路由器。允许的方法({ * throw : true,* not implemented d :()=new boom。notimplemented(),* methonotallowed :()=new boom。methonotallowed()* });* ` `* * @ param { Object=}选项* @ param { Boolean=}选项。投掷失误而不是设置状态和标头* @ param { Function=}选项。NotImplemented throw返回值代替默认NotImplemented错误* @ param { Function=}选项。方法不允许抛出返回值代替默认方法不允许的错误* @返回{功能} */路由器。原型。allow methods=Function(options){ options=options | | { };var实现=this.methods返回函数allowedMethods(ctx,next) { //所有中间件执行完之后执行允许方法方法返回下一个()。然后(function(){允许的var={ };//没有响应状态码或者响应了404 if(!CTX。地位| | CTX。status===404){//在比赛方法中,匹配的路由的层实例对象组成的数组ctx.matched.forEach(函数(路由){ route.methods.forEach(函数(方法){ //把匹配的路由的超文本传送协议(超文本传输协议的缩写)方法保存起来,认为是允许的超文本传送协议(超文本传输协议的缩写)请求方法允许的[方法]=方法;});});var allowedArr=Object.keys(允许);//如果该方法在路由器实例的方法中不存在if(!~已实现。指数(CTX。方法)){//如果在初始化路由器时配置了扔属性为如果(选项。throw){ var notImplementedThrowable,则为trueif(选项。notimplemented==' function '){//指定了报错函数notImplementedThrowable=选项。notimplemented();//设置用户从其函数返回的任何内容} else { //没有指定则抛出超文本传送协议(超文本传输协议的缩写)异常notImplementedThrowable=new Httperror .NotImplemented();}引发notImplementedThrowable } else {//没有配置扔则响应501 ctx.status=501/设置响应头中的允许字段,返回允许的超文本传送协议(超文本传输协议的缩写)方法ctx.set('Allow ',allowedArr.join(','));} } else if(allowedarr。长度){ if(CTX。method===' OPTIONS '){//如果是选择请求,则认为是请求成功,响应200,并根据选择请求约定返回允许的超文本传送协议(超文本传输协议的缩写)方法CTX . status=200 CTX . body=CTX . set(' Allow ',allowedArr.join(','));} else if(!允许的[ctx.method]) { //如果请求方法在路由器实例的方法中存在,但是在匹配的路由中该超文本传送协议(超文本传输协议的缩写)方法不存在if(选项。throw){ var notAllowedThrowable;if(选项。方法notallowed==' function '){ notAllowedThrowable=options。方法不允许();//设置用户从其函数返回的任何内容} else { notAllowedThrowable=new Httperror .方法不允许();}抛出notAllowedThrowable} else { //响应405 http请求方法错误ctx.status=405ctx.set('Allow ',allowedArr.join(','));} } } } });};};Router.prototype.use
/** *使用给定的中间件。* *中间件按照` .“使用()”定义的顺序运行。它们被顺序调用,请求从第一个中间件开始,沿着中间件堆栈向下。* * @示例* * ```javascript * //会话中间件将在授权*路由器*之前运行使用(会话())* .使用(授权());* * //只对给定的路径* router.use('/users ',userAuth())使用中间件;* * //或带有路径数组* router.use(['/users ','/admin'],user auth());* * app。使用(路由器。routes());* ``` * * @ param { String=}路径* @param {Function}中间件* @param {Function=}.* @返回{路由器} */路由器。原型。use=function(){ var Router=this;定义变量中间件=Array.prototype.slice.call(参数);定义变量路径;//支持路径数组/如果第一个参数是一个数组,且数组中元素为字符串if (Array.isArray(中间件[0])类型的中间件[0][0]==='string') { //递归调用使用方法中间件[0]。forEach(函数(p) { router.use.apply(路由器,[p]).concat(中间件。切片(1)));});归还这个;} var hasPath=中间件类型[0]===' string ';if (hasPath) { path=中间件。shift();}中间件。forEach(函数(m) { //如果这个中间件是由router.routes()方法返回的派遣中间件,即这是一个嵌套的路由if (m.router) { //遍历路由器。堆栈属性中所有的层m.router.stack.forEach(函数(nestedLayer) { //被嵌套的路由需要以父路由小路为前缀如果(路径)nestedLayer.setPrefix(路径);//如果父路由有指定前缀,被嵌套的路由需要把这个前缀再加上if(路由器。选择。前缀)nestedlayer。setprefix(路由器。选择。前缀);路由器。堆栈。推送(嵌套层);});if(路由器。params){ object。密钥(路由器。参数).forEach(函数(键){ m . router。参数(密钥,路由器。params[key]);});} } else { router.register(路径|| '(。*)',[],m,{ end: false,ignoreCaptures:hasPath });} });归还这个;};以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。