You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
app.use=functionuse(fn){varoffset=0;varpath='/';// default path to '/'// disambiguate app.use([fn])if(typeoffn!=='function'){vararg=fn;while(Array.isArray(arg)&&arg.length!==0){arg=arg[0];}// first arg is the pathif(typeofarg!=='function'){offset=1;path=fn;}}varfns=flatten(slice.call(arguments,offset));if(fns.length===0){thrownewTypeError('app.use() requires a middleware function')}// setup routerthis.lazyrouter();varrouter=this._router;fns.forEach(function(fn){// non-express appif(!fn||!fn.handle||!fn.set){returnrouter.use(path,fn);}debug('.use app under %s',path);fn.mountpath=path;fn.parent=this;// restore .app property on req and resrouter.use(path,functionmounted_app(req,res,next){varorig=req.app;fn.handle(req,res,function(err){setPrototypeOf(req,orig.request)setPrototypeOf(res,orig.response)next(err);});});// mounted an appfn.emit('mount',this);},this);returnthis;};
router.use=functionuse(fn){varoffset=0;varpath='/';// default path to '/'// disambiguate router.use([fn])if(typeoffn!=='function'){vararg=fn;while(Array.isArray(arg)&&arg.length!==0){arg=arg[0];}// first arg is the pathif(typeofarg!=='function'){offset=1;path=fn;}}varcallbacks=flatten(slice.call(arguments,offset));if(callbacks.length===0){thrownewTypeError('Router.use() requires a middleware function')}for(vari=0;i<callbacks.length;i++){varfn=callbacks[i];if(typeoffn!=='function'){thrownewTypeError('Router.use() requires a middleware function but got a '+gettype(fn))}// add the middlewaredebug('use %o %s',path,fn.name||'<anonymous>')varlayer=newLayer(path,{sensitive: this.caseSensitive,strict: false,end: false},fn);layer.route=undefined;this.stack.push(layer);}returnthis;};
proto.handle=functionhandle(req,res,out){varself=this;debug('dispatching %s %s',req.method,req.url);varidx=0;varprotohost=getProtohost(req.url)||''varremoved='';varslashAdded=false;varparamcalled={};// store options for OPTIONS request// only used if OPTIONS requestvaroptions=[];// middleware and routesvarstack=self.stack;// manage inter-router variablesvarparentParams=req.params;varparentUrl=req.baseUrl||'';vardone=restore(out,req,'baseUrl','next','params');// setup next layerreq.next=next;// for options requests, respond with a default if nothing else respondsif(req.method==='OPTIONS'){done=wrap(done,function(old,err){if(err||options.length===0)returnold(err);sendOptionsResponse(res,options,old);});}// setup basic req valuesreq.baseUrl=parentUrl;req.originalUrl=req.originalUrl||req.url;next();functionnext(err){varlayerError=err==='route'
? null
: err;// remove added slashif(slashAdded){req.url=req.url.substr(1);slashAdded=false;}// restore altered req.urlif(removed.length!==0){req.baseUrl=parentUrl;req.url=protohost+removed+req.url.substr(protohost.length);removed='';}// signal to exit routerif(layerError==='router'){setImmediate(done,null)return}// no more matching layersif(idx>=stack.length){setImmediate(done,layerError);return;}// get pathname of requestvarpath=getPathname(req);if(path==null){returndone(layerError);}// find next matching layervarlayer;varmatch;varroute;while(match!==true&&idx<stack.length){layer=stack[idx++];match=matchLayer(layer,path);route=layer.route;if(typeofmatch!=='boolean'){// hold on to layerErrorlayerError=layerError||match;}if(match!==true){continue;}if(!route){// process non-route handlers normallycontinue;}if(layerError){// routes do not match with a pending errormatch=false;continue;}varmethod=req.method;varhas_method=route._handles_method(method);// build up automatic options responseif(!has_method&&method==='OPTIONS'){appendMethods(options,route._options());}// don't even bother matching routeif(!has_method&&method!=='HEAD'){match=false;continue;}}// no matchif(match!==true){returndone(layerError);}// store route for dispatch on changeif(route){req.route=route;}// Capture one-time layer valuesreq.params=self.mergeParams
? mergeParams(layer.params,parentParams)
: layer.params;varlayerPath=layer.path;// this should be done for the layerself.process_params(layer,paramcalled,req,res,function(err){if(err){returnnext(layerError||err);}if(route){returnlayer.handle_request(req,res,next);}trim_prefix(layer,layerError,layerPath,path);});}functiontrim_prefix(layer,layerError,layerPath,path){if(layerPath.length!==0){// Validate path breaks on a path separatorvarc=path[layerPath.length]if(c&&c!=='/'&&c!=='.')returnnext(layerError)// Trim off the part of the url that matches the route// middleware (.use stuff) needs to have the path strippeddebug('trim prefix (%s) from url %s',layerPath,req.url);removed=layerPath;req.url=protohost+req.url.substr(protohost.length+removed.length);// Ensure leading slashif(!protohost&&req.url[0]!=='/'){req.url='/'+req.url;slashAdded=true;}// Setup base URL (no trailing slash)req.baseUrl=parentUrl+(removed[removed.length-1]==='/'
? removed.substring(0,removed.length-1)
: removed);}debug('%s %s : %s',layer.name,layerPath,req.originalUrl);if(layerError){layer.handle_error(layerError,req,res,next);}else{layer.handle_request(req,res,next);}}};
取出当前的stack,也就是存放着Layer的数组,执行next()函数,在next函数里会通过while循环stack,如果match会false会停止。
首先会把Layer取出来并执行matchLayer(layer, path),matchLayer会调用Layer.match来进行判断,如果不匹配,会置match为false,并用continue跳过当前的循环跳入下一个。我们能看到还有一种会跳过的方式为next(err),传进来一个layerError值,他会设置match为false并跳过所有的循环。所以我们写中间件的时候给next传err msg就不会运行下面的中间件以及app.get / get post等方法。当然这个err如果你传的是'route'字符串,他不视为错误只是跳过当前的中间件,这是官方钦定的方法。虽然会有点反直觉,如果用户没看到相关API传了个route进去却没报错可能会有点摸不着头脑。
Middleware
用过express都知道,express有中间件的概念,中间件可以:
当有请求向服务器发起的时候,可以让中间件处理进来的请求。比如,在一开始URL进来的时候并不能很好的拿到传进来queryString,express会自带处理queryString的中间件,解析并放在req.query里,让用户很方便的操作queryString。或者让每个响应头添加上csrf-token等等。中间件不是必须的,我们可以选择所需要的中间件功能来提供帮助。
今天就来解读中间件的运行原理,使用中间件的代码很简单
app.use()
,下面是express5.x源码use函数接收的一个参数不是函数的话,就为匹配的路径,他也可以接收多个函数并扁平化。注意到中间的代码,fns会循环被
router.use
用到,并且会传入第二个函数,执行fn.handle
。能看到有一段lazyrouter()
的函数被执行,他的代码具体:可以看到,他会创建_router,并像前面app.use传进来的fn一样,最后调用_router.use(fn),看看router.use又是什么
可以看到
router.use
做的事前跟前面的app.use
一样,处理path,扁平化fn。除了在最后的时候调用new Layer()
,并放入router.statck
,在实例化router的时候(new Router()
),会初始化一个stack数组来放Layer到这里,先整理一下所有的结构,当用到
app.use
的时候会初始化一个_router在app里,这个_router有个属性叫stack,用来存放Layer。具体代码以及生成的结构
stack前两个是啥呢?记不记得在lazyloader有两个函数
看到具体的代码能得知,他们算是官方自带的中间件了,一个能帮助解析querystring放到
req.query
里。另外一个告诉浏览器服务器的支持来自Express。ok,我们还有layer没探讨是做什么的
Layer很简单,构造函数取fn,path的值赋值到this。在去到path之后会被
pathRegexp
转成正则表达式,所以这个patch其实也支持正则的传入。Layer有三个原型方法,match
作为匹配当前的正则是否匹配路由,handle_error,handle_request
执行fn并将req,res,next
传到函数参数内,这两个不同在于如果handle_request
执行fn的时候在try catch
抛出异常后,会回到next(err),去执行handle_error
。接下来就得找到什么时候执行handle_request
函数。在请求响应的时候会触发routed的操作执行下面的代码https://github.com/expressjs/express/blob/master/lib/router/index.js#L136-L320
取出当前的stack,也就是存放着Layer的数组,执行
next()
函数,在next函数里会通过while循环stack,如果match会false会停止。首先会把Layer取出来并执行
matchLayer(layer, path)
,matchLayer
会调用Layer.match
来进行判断,如果不匹配,会置match为false,并用continue跳过当前的循环跳入下一个。我们能看到还有一种会跳过的方式为next(err),传进来一个layerError值,他会设置match为false并跳过所有的循环。所以我们写中间件的时候给next传err msg就不会运行下面的中间件以及app.get / get post等方法
。当然这个err如果你传的是'route'字符串,他不视为错误只是跳过当前的中间件,这是官方钦定的方法。虽然会有点反直觉,如果用户没看到相关API传了个route进去却没报错可能会有点摸不着头脑。最后就是执行相应的
layer.handle_error
或者layer.handle_request
了express有个洋葱模型的概念,就是执行顺序,看下列的代码
执行顺序为1,3,5,6,4,2
大致意思为执行顺序从外到内再从内到外
其实很容易理解,执行函数推进栈的时候从上往下执行,到执行最后一个函数的时候需要回收栈的时候就从最后一个回来啦
The text was updated successfully, but these errors were encountered: