当前位置: 首页 > 后端技术 > Node.js

Express4.0源码分析

时间:2023-04-03 23:48:55 Node.js

express4.X源码解读第一天express4.X和3.X有很大区别,4.X去掉了connect的依赖,3.X基本上所有基于connect的中间件都不能用,如果还有的话,也被4.X改写了。所以如果你想继续使用这些熟悉的中间件,你需要手动安装依赖包,或者使用一些其他的中间件。下面开始源码解读1.什么是expresstypeofexpress==='function'//true可以知道express是一个函数,这个函数会在程序启动时运行functioncreateApplication(){varapp=function(req,res,next){app.handle(req,res,next);};混合(应用程序,原型);混合(应用程序,EventEmitter.prototype);app.request={__proto__:req,app:app};app.response={__proto__:res,app:app};应用程序初始化();返回应用程序;}上面这个函数是express,有没有看到很眼熟的东西,有没有看到app,有没有看到这个眼熟的东西。..顺便说一句,没错。是每个nodejs教程中启动nodejs教学的例子,nodejs启动服务器:http.createSever的回调函数。app是express贯穿整个流程的功能。其实整个express的执行过程就是不断的给req和res这两个对象修改和添加属性。直到请求完成。中间件使用app作为回调修改req和res。从而达到可插拔的效果。varapp=express();这就是为什么当引入express时,必须执行此功能。2.程序如何启动?express作为一个web框架,需要先启动一个server。让我们看看服务器是从哪里启动的。varserver=app.listen(app.get('port'),function(){debug('Expressserverlisteningonport'+server.address().port);});express使用了一种我不喜欢使用的写法。它将所有方法直接放入应用程序功能中。大家我们都知道,函数在js中就是一个对象,除了可以执行之外,和对象没什么区别。但是,这无形中增加了代码的阅读难度,而且容易混淆,因为app不仅仅是一个中间件,更是公共方法的载体。好吧,当谈到启动服务器时,应用程序没有启动服务器的能力。这种能力混合在应用程序文件中。其实就是混合http.createServer的方法,不过这里还是要看代码。app.listen=function(){varserver=http.createServer(this);返回server.listen.apply(服务器,参数);};看看有没有这个,这个这个这个很重要,这个==app.该应用程序已作为回调传入,这就是魔法中间件开始其旅程的地方。3、从入口分析源码functioncreateApplication(){varapp=function(req,res,next){app.handle(req,res,next);};混合(应用程序,原型);mixin(app,EventEmitter.prototype);app.request={__proto__:req,app:app};app.response={__proto__:res,app:app};应用程序初始化();returnapp;}首先,将application模块的所有属性混合进去app中,将event的所有属性混合到app中,这个就是给app添加事件函数。然后将req和res模块分别赋给app,这样就可以直接调用request和response了。具体执行过程还是在app.init中。最后返回程序实例app。接下来,转到应用程序模块的init方法。app.init=function(){this.cache={};this.settings={};this.engines={};这个.defaultConfiguration();};添加了缓存设置引擎三个对象,现在看不到效果。具体执行过程去defaultConfiguration看this.enable('x-powered-by')看enable,再进去看enable,其实是set了,只不过第二个参数是boolean。设置了什么?请记住,我们不了解函数的三个对象之一的设置。这个设置只是给设置对象添加一些属性。我们先看看defaultConfigurationthis.enable('x-powered-by')并设置x-powered-by为true。x-powered-by是什么意思?有些查询工具可以在我们输入某个站点的URL后判断该站点的WebServer和程序类型。即在http请求的时候,可以看到x-powered-by:Express,不设置是看不到服务区类型的。这应该是http请求的一部分。this.set('etag','弱');此处处理etag的Express依赖于一个名为etag的包varenv=process.env.NODE_ENV||'development';this.set('env',env);this.set('queryparser','extended');this.set('subdomainoffset',2);this.set('trustproxy',错误的);继续在这里设置属性。//继承原型this.on('mount',function(parent){this.request.__proto__=parent.request;this.response.__proto__=parent.response;this.engines.__proto__=parent.engines;this.settings.__proto__=parent.settings;});//设置localsthis.locals=Object.create(null);//最顶层的应用程序安装在/this.mountpath='/';//默认localsthis.locals.settings=this.settings;//默认配置this.set('view',View);this.set('views',resolve('views'));this.set('jsonp回调名','callback');if(env==='production'){this.enable('viewcache');}Object.defineProperty(this,'router',{get:function(){thrownewError('\'app.router\'已弃用!\n请参阅3.x到4.x迁移指南,了解有关如何更新您的应用程序的详细信息。');}});之前不知道这里mount是什么意思,看了其他应用才知道。这用于挂载其他应用程序。比如我有几个应用,可以启动几个业务服务,用一个中心服务监听端口,然后挂载其他几个应用模块。我们研究一下app.use方法,发现这个时候express的初始化过程就已经完成了。之前看过3.X的源码,好像不是这样的,但是仔细观察,确实到这里就结束了。其余的方法呢?让我们仔细看看将中间件添加到应用程序路由器。这是源码中的解释。将中间件添加到路由。前面说过,中间件和路由没有本质区别,是一回事。app.use=functionuse(fn){varoffset=0;变种路径='/';变种自我=这个;//'/'的默认路径//消除歧义app.use([fn])if(typeoffn!=='function'){vararg=fn;while(Array.isArray(arg)&&arg.length!==0){arg=arg[0];}//第一个参数是路径if(typeofarg!=='function'){offset=1;path=fn;}}varfns=flatten(slice.call(arguments,offset));if(fns.length===0){thrownewTypeError('app.use()需要中间件函数');}//设置路由器this.lazyrouter();varrouter=this._router;fns.forEach(function(fn){//非expressappif(!fn||!fn.handle||!fn.set){returnrouter.use(path,fn);}debug('.useappunder%s',path);fn.mountpath=path;fn.parent=self;//在req和res上恢复.app属性router.use(path,functionmounted_app(req,res,next){varorig=req.app;fn.handle(req,res,function(err){req.__proto__=orig.request;res.__proto__=orig.response;下一个(错误);});});//安装了一个应用程序fn.emit('mount',self);});归还这个;};于是我们看到lazyrouter的东西,这个函数里new了一个Router对象,所以这个暂时略过。我们得去路线上看看。昨天看源码的时候遇到了麻烦。发现很多代码不是那么好理解的,有的看的一头雾水,然后就搞错了。一些错误,打了很多断点,终于弄明白了。要理解express的处理流程,首先要搞清楚app.use和app.handle这两个方法。这两个方法非常重要。我们已经知道app本身是如果作为回调参数传入http.createServer,那么应用的所有路由都会落入这个函数中,通过中间件一一处理。想想也不是很复杂,但是看代码还是很蛋疼的样子。首先,req和res封装了很多方法,但是这个方法混在什么地方呢?这里我犯了一个错误,误以为我用的时候会有这个方法,于是在use函数里找啊找,打了好多断点,还是没找到这个操作在哪里执行。但实际上use从来没有做过这个操作。use的作用是将这个回调压入路由中路由实例的栈中。参见代码if(!fn||!fn.handle||!fn.set){returnrouter.use(path,fn);}app的使用执行了Route实例的使用。继续看Route的usevarlayer=newLayer(path,{sensitive:self.caseSensitive,strict:false,end:false},fn);layer.route=undefined;self.stack.push(层);并且你会发现路线的使用和app的使用都会有一些重复的代码。不同的是使用路由会创建一个图层。这一层是一个实例,每个回调函数的一个实例。这个例子包含了全局配置的一些属性,比如严格匹配,大小写。另外存储了当前使用的路由url和回调,全部入栈。查看路由的实例化过程,会发现express默认在里面放了两个中间件。代码如下app.lazyrouter=function(){if(!this._router){this._router=newRouter({caseSensitive:this.enabled('casesensitiverouting'),strict:this.enabled('strict路由')});this._router.use(query(this.get('queryparserfn')));this._router.use(middleware.init(this));}};所以app默认会有两个中间件Components,query和middleware。程序的执行到此结束。那么还有一个问题,request和response这两个对象的很多扩展方法是从哪里来的。下面看一下openmiddleware/initexports.init=function(app){returnfunctionexpressInit(req,res,next){if(app.enabled('x-powered-by'))res.setHeader('X-Powered-By','Express');req.res=res;res.req=req;req.next=下一步;req.__proto__=app.request;res.__proto__=app.response;res.locals=res.locals||对象.create(null);下一个();};};这里可以看到request和response都放在了回调的req和res中。由于这两个内置的中间件是先加入的,放在栈的前两个,所以每次请求都会先进入这两个中间件,然后再把很多东西带进其他的中间件。还有个问题,不能用addroutes?不能控制哪个中间件走哪条路由吗?如何控制它。看这里。..proto.match_layer=functionmatch_layer(layer,req,res,done){varerror=null;变量路径;尝试{path=parseUrl(req).pathname;如果(!layer.match(path)){path=undefined;}}catch(err){错误=错误;}完成(错误,路径);};这里,图层中存储的路径规律将用于匹配当前路径。成功则进入回调执行,失败则继续执行。