大家好,感谢邀请,今天来为大家分享一下案例网站源码分享的问题,以及和有什么好的案例分享网站的一些困惑,大家要是还不太明白的话,也没有关系,因为接下来将为大家分享,希望可以帮助到大家,解决大家的问题,下面就开始吧!
前几天有空瞄了几眼express4.x的源码,今天做一下总结。首先我会使用以下代码做为一个入口,开始
constexpress=require(&39;);\n\nconstapp=express();\n\napp.get(&39;,indexHandler)\n\nfunctionindexHandler(req,res,next){\nres.set(&39;,&34;);\nres.send(`<h1style=&34;>helloworld</h1>`)\n}\n\napp.listen(9999)
从第5行代码开始看起,先翻到源码:
functioncreateApplication(){\nvarapp=function(req,res,next){\napp.handle(req,res,next);\n};\n\nmixin(app,EventEmitter.prototype,false);\nmixin(app,proto,false);\n\n//exposetheprototypethatwillgetsetonrequests\napp.request=Object.create(req,{\napp:{configurable:true,enumerable:true,writable:true,value:app}\n})\n\n//exposetheprototypethatwillgetsetonresponses\napp.response=Object.create(res,{\napp:{configurable:true,enumerable:true,writable:true,value:app}\n})\n\napp.init();\nreturnapp;\n}
首先注意一下这个叫做app的函数,他既是我们本段代码的入口,也是http请求过来时要流过的第一个函数,然后下来的两个minxin,第一个是把EventEmitter.prototype中的所有属性合并到app上来,这样一来app就拥有了事件订阅和提交的功能,关于EventEmitter可以查看node官网的文档详情。第二个minxin是将一个叫做proto的东西合并到了app上,这个proto是在application.js文件里边,定义了app上的方法,诸如listen,enabled,disabled,set等等,这些方法可以在express官方文档这里看到。下来,是定义了request,response,并分别在其上面用app属性引用了app,然后调用app.init()方法。
app.init=functioninit(){\nthis.cache={};\nthis.engines={};\nthis.settings={};\n\nthis.defaultConfiguration();\n};
chache是于保存render时的结果的,engines是模板引擎,至于settings保存的是一些设置,诸如是否开启e-tag,x-powerd,还有响应头的一些字段。下来调用了defaultConfiguration。代码比较长,直接在源码里做注释了:
app.defaultConfiguration=functiondefaultConfiguration(){\nvarenv=process.env.NODE_ENV||&39;;\n\n//defaultsettings\n/*添加头x-powered-by*/\nthis.enable(&39;);\n/*设置etag\n>tips:ETag有两种类型:强ETag(strongETag)与弱ETag(weakETag)。\n\n强ETag表示形式:&34;。\n\n弱ETag表现形式:w/&34;。\n具体的策略得看浏览器的不同实现\n*/\nthis.set(&39;,&39;);\n/*设置环境变量,开发还是生产*/\nthis.set(&39;,env);\n/*query解析函数,extendend策略下最终调用的是:qs.parse(str,{allowPrototypes:true});*/\nthis.set(&39;,&39;);\n/*访问req.subdomains时host用.分割成数组之后需要删除后边的数目是几个,举例:默认为2,tobi.ferrets.example.com的subdomains就是[&34;,&34;]\n*/\nthis.set(&39;,2);\n/*是否信任代理,*/\nthis.set(&39;,false);\n\n//trustproxyinheritback-compat\nObject.defineProperty(this.settings,trustProxyDefaultSymbol,{\nconfigurable:true,\nvalue:true\n});\n\ndebug(&39;,env);\n/*当一个子app挂载到父app的时候会触发*/\nthis.on(&39;,functiononmount(parent){\n//inherittrustproxy\nif(this.settings[trustProxyDefaultSymbol]===true\n&&typeofparent.settings[&39;]===&39;){\ndeletethis.settings[&39;];\ndeletethis.settings[&39;];\n}\n\n//inheritprotos\nsetPrototypeOf(this.request,parent.request)\nsetPrototypeOf(this.response,parent.response)\nsetPrototypeOf(this.engines,parent.engines)\nsetPrototypeOf(this.settings,parent.settings)\n});\n\n//setuplocals\nthis.locals=Object.create(null);\n\n//top-mostappismountedat/\nthis.mountpath=&39;;\n\n//defaultlocals\nthis.locals.settings=this.settings;\n\n//defaultconfiguration\n/*view为render的时候渲染模板的一个数据结构*/\nthis.set(&39;,View);\n/*模板目录,默认为views*/\nthis.set(&39;,resolve(&39;));\n/*设置jsonp回调函数的名字*/\nthis.set(&39;,&39;);\n/*生产模式开启view缓存*/\nif(env===&39;){\nthis.enable(&39;);\n}\n/*4.x不再支持app.router式的调用*/\nObject.defineProperty(this,&39;,{\nget:function(){\nthrownewError(&39;app.router\\&39;);\n}\n});\n};
至此,app.init行完毕。然后express函数返回app实例。接着,我们使用app.get定义了我们的第一条路由,至于app.get的源码,是在这里:
//methods=[&39;,&39;…]等一系列http动词\nmethods.forEach(function(method){\napp[method]=function(path){\nif(method===&39;&&arguments.length===1){\n//app.get(setting)\nreturnthis.set(path);\n}\n\nthis.lazyrouter();\n\nvarroute=this._router.route(path);\nroute[method].apply(route,slice.call(arguments,1));\nreturnthis;\n};\n});
app[method]向外引用,function,闭包了当前method的名字,这里注意到当以app.get(‘key’)形式调用的时候,程序实际return的是当前set[‘key’]的值。如果是定义路由,则走了下面的步骤,首先会给当前的app实例初始化一个router实例,源码如下:
app.lazyrouter=functionlazyrouter(){\nif(!this._router){\nthis._router=newRouter({\ncaseSensitive:this.enabled(&39;),\nstrict:this.enabled(&39;)\n});\n\nthis._router.use(query(this.get(&39;)));\nthis._router.use(middleware.init(this));\n}\n};
这种如果没有再定义的策略,设计模式上叫单例模式,初次执行肯定没有,所以这里先会初始化一个rouer绑定到app._router上,caseSensitive表示路由对大小写敏感,strict开启路由的严格模式,好,接着走到Router构造函数:
varproto=module.exports=function(options){\nvaropts=options||{};\n\nfunctionrouter(req,res,next){\nrouter.handle(req,res,next);\n}\n\n//mixinRouterclassfunctions\nsetPrototypeOf(router,proto)\n\nrouter.params={};\nrouter._params=[];\nrouter.caseSensitive=opts.caseSensitive;\nrouter.mergeParams=opts.mergeParams;\nrouter.strict=opts.strict;\nrouter.stack=[];\n\nreturnrouter;\n};
router本身是一个函数,调用自身的而handle方法,传递http,setPrototypeOf将router的__proto__属性指向proto,借此实现js式的继承,至于proto,也定义于此处,就是router的一系列方法,诸如param,handle,use等等。当然router也有自己的get,post等等方法,这些和app上定义的时候大同小异的,最后声明了几个保存变量的属性,将router返回了出来。初始化router完毕之后,router使用了两个中间件,第一个是parsequery的,第二个中间件是用于初始化req和res的,他做了一件很重要的事就是将express框架的request和response绑定到了当前的req和res上,部分代码如下:
setPrototypeOf(req,app.request)\nsetPrototypeOf(res,app.response)
这些方法里边包含了很多东西,诸如req的属性,res的send,set等等。lazyRouter执行完毕,然后执行router上的route方法:
functionroute(path){\nvarroute=newRoute(path);\n\nvarlayer=newLayer(path,{\nsensitive:this.caseSensitive,\nstrict:this.strict,\nend:true\n},route.dispatch.bind(route));\n\nlayer.route=route;\n\nthis.stack.push(layer);\nreturnroute;\n};
Route是描述路由的一个数据结构,他的构造函数如下:
functionRoute(path){\nthis.path=path;\nthis.stack=[];\n\ndebug(&39;,path)\n\n//routehandlersforvarioushttpmethods\nthis.methods={};\n}
path属性包含了当前路由的path,stack是定义路由是用于保存定义路由时生成的layer的数组,至于methods,举一个例子就是当route.get()发生时,那么this.methods.get的值就是true。接着会生成layer,layer的构造函数如下:
functionLayer(path,options,fn){\nif(!(thisinstanceofLayer)){\nreturnnewLayer(path,options,fn);\n}\n\ndebug(&39;,path)\nvaropts=options||{};\n\nthis.handle=fn;\nthis.name=fn.name||&39;;\nthis.params=undefined;\nthis.path=undefined;\nthis.regexp=pathRegexp(path,this.keys=[],opts);\n\n//setfastpathflags\nthis.regexp.fast_star=path===&39;\nthis.regexp.fast_slash=path===&39;&&opts.end===false\n}
这里边比较重要的一点是会将当前path转化成能匹配他的正则并把这个正则保存到this.regexp上,并且会把param参数在此处提取出来保存到this.keys上,还有一个handle属性保存在这个layer上执行的回调函数,以以上形式生成的layer上,其handle函数为route.dispatch.bind(route),这个函数是route实例上的方法,用于执行他的栈上保存的layer。然后layer生成完毕,此时的layer实例会将一个route属性指向当前的route,此刻,将这个layer保存到router的stacks里。
这里多提一句就是,router.use这种形式生成路由时layer上的route是undefined的,并且layer的handle就是传进去的回调函数。在后边router遍历自己stack上存储的layer时,正是基于此判断他是中间件还是一个路由业务函数。
router.route执行完毕,接下来开始执行route[method].apply(route,slice.call(arguments,1));,route[method]的定义方法,和app,router大同小异:
methods.forEach(function(method){\nRoute.prototype[method]=function(){\nvarhandles=flatten(slice.call(arguments));\n\nfor(vari=0;i<handles.length;i++){\nvarhandle=handles[i];\n\nif(typeofhandle!==&39;){\nvartype=toString.call(handle);\nvarmsg=&39;+method+&39;+type\nthrownewError(msg);\n}\n\ndebug(&39;,method,this.path)\n\nvarlayer=Layer(&39;,{},handle);\nlayer.method=method;\n\nthis.methods[method]=true;\nthis.stack.push(layer);\n}\n\nreturnthis;\n};\n});
route.get,post,…等方法也会生成layer,其handle就是定义的回调函数,然后这个layer会保存到route的stack里,在route的dispath方法里调用。这样的话,route[method].apply(route,slice.call(arguments,1));也就执行完毕了。至此,路由定义完毕。现在在这里梳理一下,app,router,route的关系:此刻,app的内部属性_router上引用的router实例,他的stack上此刻应该有如下几个layer:
用于parsequery的中间件;用于初始化req,res的init中间件;
以上两个是初始化的时候就use的中间件,接下来是:
调用app,get的时候为route创造的layer。
在app.get执行时,生成的route实例会在其stack上保存一个layer,该layer的handle就是我们定义的回调函数。ok。下来app开始listen:
app.listen=functionlisten(){\nvarserver=http.createServer(this);\nreturnserver.listen.apply(server,arguments);\n};
server还是使用http模块的createServer创造的,只不过this指向的是app也就是开头我们提到的整个程序的入口,是个函数,接着用了函数的apply方法,将app.listen调用时传过去的参数使用arguments巧妙的传过去,并将server设置为listen的上下文。listen之后,app就开始正式运行了,监听了9999端口。
——————
当在浏览器上输入http://localhost:9999/时,首先,app会被执行,而app里只有一句话就是app.handle(req,res,next);参数分别是request,response和next(在此时为undefined),所以我们继续往下看app.handle:
app.handle=functionhandle(req,res,callback){\nvarrouter=this._router;\n\n//finalhandler\nvardone=callback||finalhandler(req,res,{\nenv:this.get(&39;),\nonerror:logerror.bind(this)\n});\n\n//noroutes\nif(!router){\ndebug(&39;);\ndone();\nreturn;\n}\n\nrouter.handle(req,res,done);\n};
开始执行router.handle并将done默认值作为next参数传递过去:此处代码稍长,所以还是将解释放到源码里边。
functionhandle(req,res,out){\nvarself=this;\n\ndebug(&39;,req.method,req.url);\n\nvaridx=0;\nvarprotohost=getProtohost(req.url)||&39;\nvarremoved=&39;;\nvarslashAdded=false;\nvarparamcalled={};\n\n//storeoptionsforOPTIONSrequest\n//onlyusedifOPTIONSrequest\nvaroptions=[];\n\n//middlewareandroutes\nvarstack=self.stack;\n\n//manageinter-routervariables\nvarparentParams=req.params;\nvarparentUrl=req.baseUrl||&39;;\n/*重置传进来的next方法,restore的作用是保存初始的baseUrl,next,params的值,该方法最后返回\n一个闭包函数,该闭包函数内req上的以上三个属性会被重置为初始值,然后调用out方法\n*/\nvardone=restore(out,req,&39;,&39;,&39;);\n\n//setupnextlayer\nreq.next=next;\n\n//foroptionsrequests,respondwithadefaultifnothingelseresponds\nif(req.method===&39;){\ndone=wrap(done,function(old,err){\nif(err||options.length===0)returnold(err);\nsendOptionsResponse(res,options,old);\n});\n}\n\n//setupbasicreqvalues\nreq.baseUrl=parentUrl;\nreq.originalUrl=req.originalUrl||req.url;\n/*开始执行next方法,遍历router.stack里的layer*/\nnext();\n\nfunctionnext(err){\nvarlayerError=err===&39;\n?null\n:err;\n\n//removeaddedslash\nif(slashAdded){\nreq.url=req.url.substr(1);\nslashAdded=false;\n}\n\n//restorealteredreq.url\nif(removed.length!==0){\nreq.baseUrl=parentUrl;\nreq.url=protohost+removed+req.url.substr(protohost.length);\nremoved=&39;;\n}\n\n//signaltoexitrouter\nif(layerError===&39;){\nsetImmediate(done,null)\nreturn\n}\n\n//nomorematchinglayers\nif(idx>=stack.length){\nsetImmediate(done,layerError);\n//遍历完毕,则在check阶段执行done方法\nreturn;\n}\n\n//getpathnameofrequest\nvarpath=getPathname(req);\n\nif(path==null){\nreturndone(layerError);\n}\n\n//findnextmatchinglayer\nvarlayer;\nvarmatch;\nvarroute;\n\n/*循环的目的,找匹配的layer,如果找不到匹配的layer就一直将stack里的layer遍历完毕*/\nwhile(match!==true&&idx<stack.length){\nlayer=stack[idx++];\n/*是否匹配*/\nmatch=matchLayer(layer,path);\nroute=layer.route;\n\nif(typeofmatch!==&39;){\n//holdontolayerError\nlayerError=layerError||match;\n}\n/*不匹配就开始下一轮循环*/\nif(match!==true){\ncontinue;\n}\n/*不是以app[method],router[method]定义的路由就到此为止,跳出while,开始执行*/\nif(!route){\n//processnon-routehandlersnormally\ncontinue;\n}\n\nif(layerError){\n//routesdonotmatchwithapendingerror\nmatch=false;\ncontinue;\n}\n\n/*拿到http方法动词并判断是否为给出的动词*/\nvarmethod=req.method;\nvarhas_method=route._handles_method(method);\n/*如果不是已给出的方法动词,且为options,则在options数组里添加当前route.methods的keys*/\n//buildupautomaticoptionsresponse\nif(!has_method&&method===&39;){\nappendMethods(options,route._options());\n}\n\n//don&39;HEAD&39;/&39;.&39;trimprefix(%s)fromurl%s&39;/&39;/&39;/&39;%s%s:%s&39;Content-Type&34;text/html;charset=utf-8&34;color:red&34;color:red”>helloworld</h1>返回给了客户端,剩下的工作是一些异步工作,诸如tcp挥手,node自己内部的一些方法,俺也没多做了解,就不展开讲了。至此helloworld完成。
如果你还想了解更多这方面的信息,记得收藏关注本站。