定义路由的时候发生了什么
还是这个简单例子:
var express = require('express') var app = express() app.get('/', function (req, res) { res.send('hello world') }) app.listen(3000)
这其中app.get('/',function(req, res){res.send('hello world')});会处理访问/路径的get请求,到底发生了什么,这就需要去代码里找找看。
app.init()
引入express模块后,执行了var app = express()这一句,其实就是执行了express的主函数:
function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; //新建一个app函数,参数里的req和res是原生的请求和相应对象原型的实例 //这个app函数作为参数被传进http.createServer里,也就是说app.handle()就是处理所有请求的中间件 mixin(app, EventEmitter.prototype, false); //将EventEmitter类的原型上的属性都合并入app中 mixin(app, proto, false); //proto是application.js导出的对象,带有很多属性,将这些属性合并到app上 // expose the prototype that will get set on requests app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } }) //request属性继承自原生请求对象,http.IncomingMessage.prototype // expose the prototype that will get set on responses app.response = Object.create(res, { app: { configurable: true, enumerable: true, writable: true, value: app } }) //response属性继承自原生响应对象,http.ServerResponse.prototype //上面的request属性和response属性都多加了一个app的数据属性指向app自己 app.init(); //app初始化 return app; //返回app函数 }
这其中最后执行了app.init()进行了初始化,app.init()的定义在application.js中,看看做了什么。
app.init = function init() { this.cache = {}; this.engines = {}; this.settings = {}; this.defaultConfiguration(); };
初始化了几个值,分别是缓存,视图引擎和配置,然后执行了app.defaultConfiguration()。
app.defaultConfiguration = function defaultConfiguration() { var env = process.env.NODE_ENV || 'development'; //获取环境变量中的NODE_ENV,如果没有默认就是development开发环境 // default settings this.enable('x-powered-by'); //开启x-powered-by响应头,这个响应头会标识后台使用的技术是express框架 this.set('etag', 'weak');//设置ETag响应头使用弱类型,ETag响应头是资源的特定版本标识符 this.set('env', env);//设置环境变量 this.set('query parser', 'extended'); //设置query中间件为扩展模式,也就是用parseurl解析查询字符串的时候添加一个allowPrototypes: true的选项,使得用户可以修改req.query上的属性 this.set('subdomain offset', 2);//子域名偏移量 this.set('trust proxy', false);//是否使用代理来确认客户端的ip地址可靠 // trust proxy inherit back-compat //trustProxyDefaultSymbol,express实例挂载在其他实例上时设置继承trust proxy的标识 Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: true }); debug('booting in %s mode', env); //mount事件处理器,当此express实例挂载到另外一个父express实例时,发射mount事件,处理器参数为父实例 this.on('mount', function onmount(parent) { // inherit trust proxy //继承父实例的trust proxy设置 if (this.settings[trustProxyDefaultSymbol] === true && typeof parent.settings['trust proxy fn'] === 'function') { delete this.settings['trust proxy']; delete this.settings['trust proxy fn']; } // inherit protos //继承父实例的各种设置 setPrototypeOf(this.request, parent.request) setPrototypeOf(this.response, parent.response) setPrototypeOf(this.engines, parent.engines) setPrototypeOf(this.settings, parent.settings) }); // setup locals this.locals = Object.create(null); //app.locals初始化是一个没有属性的对象 // top-most app is mounted at / //顶层app挂载在路径/ this.mountpath = '/'; // default locals this.locals.settings = this.settings; //app.locals.settings就是app.settings // default configuration this.set('view', View);//设置view为定义好的View构造函数 this.set('views', resolve('views'));//设置默认视图目录 this.set('jsonp callback name', 'callback');//设置默认jsonp回调名 if (env === 'production') { this.enable('view cache');//生产环境开启视图缓存 } Object.defineProperty(this, 'router', { get: function() { throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.'); } }); //为app.router设置访问器属性的get方法,如果调用app.router就警告,因为router已经从app中分离出去了,要单独调用 };
经过这一段执行,app就初始化完成了。
先回到createApplication方法,如果我们在内部打印出app,看看它通过mixin那两步,获得了什么属性,就可以找出是什么时候给他添加上了请求的方法:
function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; console.log(app, 1) mixin(app, EventEmitter.prototype, false); console.log(app, 2) mixin(app, proto, false); console.log(app, 3) app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } }) app.response = Object.create(res, { app: { configurable: true, enumerable: true, writable: true, value: app } }) app.init(); return app; }
分别看一看三次打印,app这个函数获得了什么属性
下面是第一次:
[Function: app] 1
第二次:
{ [Function: app]
domain: undefined,
_events: undefined,
_maxListeners: undefined,
setMaxListeners: [Function: setMaxListeners],
getMaxListeners: [Function: getMaxListeners],
emit: [Function: emit],
addListener: [Function: addListener],
on: [Function: addListener],
prependListener: [Function: prependListener],
once: [Function: once],
prependOnceListener: [Function: prependOnceListener],
removeListener: [Function: removeListener],
removeAllListeners: [Function: removeAllListeners],
listeners: [Function: listeners],
listenerCount: [Function: listenerCount],
eventNames: [Function: eventNames] } 2
第三次:
{ [Function: app] domain: undefined, _events: undefined, _maxListeners: undefined, setMaxListeners: [Function: setMaxListeners], getMaxListeners: [Function: getMaxListeners], emit: [Function: emit], addListener: [Function: addListener], on: [Function: addListener], prependListener: [Function: prependListener], once: [Function: once], prependOnceListener: [Function: prependOnceListener], removeListener: [Function: removeListener], removeAllListeners: [Function: removeAllListeners], listeners: [Function: listeners], listenerCount: [Function: listenerCount], eventNames: [Function: eventNames], init: [Function: init], defaultConfiguration: [Function: defaultConfiguration], lazyrouter: [Function: lazyrouter], handle: [Function: handle], use: [Function: use], route: [Function: route], engine: [Function: engine], param: [Function: param], set: [Function: set], path: [Function: path], enabled: [Function: enabled], disabled: [Function: disabled], enable: [Function: enable], disable: [Function: disable], acl: [Function], bind: [Function], checkout: [Function], connect: [Function], copy: [Function], delete: [Function], get: [Function], head: [Function], link: [Function], lock: [Function], 'm-search': [Function], merge: [Function], mkactivity: [Function], mkcalendar: [Function], mkcol: [Function], move: [Function], notify: [Function], options: [Function], patch: [Function], post: [Function], propfind: [Function], proppatch: [Function], purge: [Function], put: [Function], rebind: [Function], report: [Function], search: [Function], subscribe: [Function], trace: [Function], unbind: [Function], unlink: [Function], unlock: [Function], unsubscribe: [Function], all: [Function: all], del: [Function], render: [Function: render], listen: [Function: listen] } 3
对比后可以发现,在经过mixin(app, proto, false);的操作后,app多了这些属性,这些恰恰就是原生http.METHODS:
[ 'acl', 'bind', 'checkout', 'connect', 'copy', 'delete', 'get', 'head', 'link', 'lock', 'm-search', 'merge', 'mkactivity', 'mkcalendar', 'mkcol', 'move', 'notify', 'options', 'patch', 'post', 'propfind', 'proppatch', 'purge', 'put', 'rebind', 'report', 'search', 'subscribe', 'trace', 'unbind', 'unlink', 'unlock', 'unsubscribe' ]
所以,这些属性是通过proto这个对象传递过来的,
var proto = require('./application');
var methods = require('methods');
这个第三方模块用于返回友好格式的http.METHODS
然后搜索methods,就能找到如下代码:
/** * Delegate `.VERB(...)` calls to `router.VERB(...)`. */ //给app添加原生http.METHODS上的方法,在调用的时候将委派给router.VERB methods.forEach(function(method){ app[method] = function(path){ //path参数就是调用时的第一个参数,一般是资源的url if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } //如果类似于这样调用get,app.get(url);,只传递一个参数的话,就把这个参数对应的值找到,然后直接返回这个值 //app.set()方法用于设置一个值或者获取一个值 //如果是调用app.get()并且传递两个参数,或者是其他方法例如app.post(),那就走处理请求的流程 this.lazyrouter(); //lazyrouter(),如果app上还没有router模块,就给app添加上_router属性,_router是router的一个实例 //lazyrouter()还添加了生成req.query的中间件和初始化中间件,都生成了对应的layer实例,存在了_router.stack里 //调用router模块上的route方法,传入get请求的url var route = this._router.route(path); //先创建了一个指定路径的route对象 //然后使用Route.prototype.dispatch作为fn创建了一个新的layer实例,最后请求会在dispatch那个函数里面处理,新layer实例被加入_router.stack //最后返回这个route对象 route[method].apply(route, slice.call(arguments, 1)); //为Route对象的指定方法应用第二个参数,即处理函数 //route.protorype.METHOD又会创建一个layer实例,将当前处理函数作为handle,这个实例最后加入了route.stack里,而不是_router.stack里 //然后给route实例的methods对象里添入当前对应的http动作,也就是route.methods[method] = true return this; }; });
这一段为app添加类似get,post请求类型名称的属性时候发生了很多事情,一行一行地分析。
首先如果调用的是app.get()方法的话,而且只传递了一个参数,那就不是为了处理请求,而是为了获取app.set()方法设置的值。app.set()用来设置值,app.get()用来获取app.set()设置的值。app.set()设置的值会存储在app.settings里面。
下面是app.set的源码:
/** * Assign `setting` to `val`, or return `setting`'s value. * * app.set('foo', 'bar'); * app.set('foo'); * // => "bar" * * Mounted servers inherit their parent server's settings. * * @param {String} setting * @param {*} [val] * @return {Server} for chaining * @public */ //app.set()用于设置一个值或者获取一个值 app.set = function set(setting, val) { if (arguments.length === 1) { // app.get(setting) return this.settings[setting]; } //如果只有一个参数,就去获取这个值 debug('set "%s" to %o', setting, val); //使用第三方debug模块打印提示,即将设置一个值 // set value this.settings[setting] = val; //将这个值设置在app.settings里,settings这个属性是一个对象,是在app.init()里初始化的 // trigger matched settings //如果是设置特殊值,就触发相应的方法来做相关设置 switch (setting) { case 'etag': this.set('etag fn', compileETag(val)); break; case 'query parser': this.set('query parser fn', compileQueryParser(val)); break; case 'trust proxy': this.set('trust proxy fn', compileTrust(val)); // trust proxy inherit back-compat Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: false }); break; } return this; };
接着下面执行了this.lazyrouter();,这一句为app添加了router模块,还添加了req.query的中间件。
下面是app.lazyrouter:
/** * lazily adds the base router if it has not yet been added. * * We cannot add the base router in the defaultConfiguration because * it reads app settings which might be set after that has run. * * @private */ //如果app还没有router模块,就添加上router模块,并为router.stack添加上了解析查询字符串的中间件函数对应的layer实例,还添加了一个初始化的中间件函数 //作者注释:我们不能给defaultConfiguration上直接添加router模块,因为有可能router运行之后,才会去settings对象里设置 app.lazyrouter = function lazyrouter() { //判断app上有没有_router属性,如果有就不用添加了 if (!this._router) { //_router属性是一个Router的实例 this._router = new Router({ caseSensitive: this.enabled('case sensitive routing'), strict: this.enabled('strict routing') }); //enabled用于查看app.settings是否存在某个值,返回布尔值 //根据app.settings上有没有对应的值来判断是否开启router的caseSensitive和strict //caseSensitive,路由的路径path是否大小写敏感 //strict,path最后面多加了一个反斜杠是否影响路由,例如'/foo'和'/foo/',默认此项关闭,两种被视为同一个路由 this._router.use(query(this.get('query parser fn'))); //这里去获取settings里的'query parser fn'这个值,这个值是在app.defaultConfiguration()初始化app的配置的时候加上的 //app.defaultConfiguration里有一句this.set('query parser', 'extended'),这一句走到app.set()里面其实走了switch语句 //最终设置了this.set('query parser fn', compileQueryParser(val));,这里的val就是'extended' //而compileQueryParser来自文件utils.js,此方法将设置的'query parser'字符串值转换成对应的方法 //而'extended'对应的方法是parseExtendedQueryString,所以this.set('query parser', 'extended')最终添加的是util.js里的parseExtendedQueryString方法 //再把这个方法传递给middleware/query.js里的方法,最终返回的query方法用来解析查询字符串,我们常用的req.query就是用这个query方法解析生成的 //解析查询字符串并生成req.query的中间件函数传给了router.use(),router.use里创建了layer实例来最终负责执行中间件函数,layer被存入_router.stack中 this._router.use(middleware.init(this)); //middleware.init()返回了一个初始化的中间件,它的作用是使得req和res可以互相访问对方,并且它们继承原生的对象,还设置了默认的响应头部X-Powered-By //接着router.use会生成它的layer实例,然后存入_router.stack中 } };
lazyrouter里实例化Router的时候调用的Router构造函数在router/index.js里:
/** * Initialize a new `Router` with the given `options`. * * @param {Object} options * @return {Router} which is an callable function * @public */ //导出router模块,router模块是一个匿名函数,它return了一个构造函数 var proto = module.exports = function(options) { var opts = options || {}; function router(req, res, next) { router.handle(req, res, next); } // mixin Router class functions //设置router函数的原型为proto对象,proto对象在这个文件里的被加入了大量的属性和方法 //于是router.__proto__ === proto //这样router的实例都可以调用添加在proto上的方法 setPrototypeOf(router, proto) //在router函数上初始化一些属性 router.params = {}; router._params = []; router.caseSensitive = opts.caseSensitive;//caseSensitive路由是否大小写敏感,此项默认关闭,'/Foo'和'/foo'被视为同一个路由 router.mergeParams = opts.mergeParams; router.strict = opts.strict; //strict,path最后面多加了一个反斜杠是否影响路由,例如'/foo'和'/foo/',默认此项关闭,两种被视为同一个路由 router.stack = []; //stack里存着所有的layer实例,是layer实例最终来负责执行中间件函数 return router; //返回router函数 };
lazyrouter里面了里面也为app添加了两个中间件函数,一个是query中间件,用来解析请求的query参数,另外是个是一个初始化中间件,这个init中间件初始化了req和res还有res.locals。
下面是query中间件:
/** * @param {Object} options * @return {Function} * @api public */ //express已经不包含大部分的请求解析中间件了,如json、urlencoded、cookie等中间件都变成可配置的了,只有查询字符串解析中间件还是内建的 //此处的query函数就是返回一个函数供router模块使用的方法来解析查询字符串,也就是生成req.query module.exports = function query(options) { var opts = merge({}, options) var queryparse = qs.parse; //使用第三方插件qs作为查询字符串解析器 if (typeof options === 'function') { queryparse = options; opts = undefined; } //如果options参数是一个函数,那么它就作为查询字符串解析器 if (opts !== undefined && opts.allowPrototypes === undefined) { // back-compat for qs module opts.allowPrototypes = true; } //如果options没有allowPrototypes属性,那就给opts上加一个allowPrototypes的属性,这个allowPrototypes是给qs插件传递的参数 //这个参数为true会允许用户可以随意修改req.query上的属性 return function query(req, res, next){ //当req.query不存在的时候,执行解析 if (!req.query) { var val = parseUrl(req).query; //使用parseurl插件解析req对象,获取到查询字符串 req.query = queryparse(val, opts); //解析查询字符串为对象,存为req.query } next();//调用下一个中间件 }; };
下面是init初始化中间件:
/** * Initialization middleware, exposing the * request and response to each other, as well * as defaulting the X-Powered-By header field. * * @param {Function} app * @return {Function} * @api private */ //初始化中间件,使得req和res可以互相访问对方,并且它们继承原生的对象,还设置了默认的响应头部X-Powered-By exports.init = function(app){ return function expressInit(req, res, next){ if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express'); req.res = res; res.req = req; req.next = next; setPrototypeOf(req, app.request) setPrototypeOf(res, app.response) //app.request代表原生请求对象http.IncomingMessage.prototype,app.response代表响应对象http.ServerResponse.prototype //app.request和app.response是在express.js里的主函数createApplication里定义的 res.locals = res.locals || Object.create(null); //res.locals初始化 next(); }; };
为app添加中间件的时候是使用router.use()来添加的, router.use()方法定义在router/index.js里面,去看它内部可以看出添加的每一个中间件处理函数,都生成了一个对应的layer实例,这所有的中间件函数对应的layer实例最后都存入了router.stack这个数组里面。
下面是router.use:
/** * Use the given middleware function, with optional path, defaulting to "/". * * Use (like `.all`) will run for any http METHOD, but it will not add * handlers for those methods so OPTIONS requests will not consider `.use` * functions even if they could respond. * * The other difference is that _route_ path is stripped and not visible * to the handler function. The main effect of this feature is that mounted * handlers can operate without any code changes regardless of the "prefix" * pathname. * * @public */ //使用提供好的中间件函数,可选参数是路径,默认路径是/ //use就像all一样会处理任意类型的http动作(例如get,post),但是它不会为这些http动作添加handlers,所以OPTIONS类型的请求不会考虑use方法 //此router.use方法类似于app.use //中间件就类似一个管道一样,请求从第一个中间件函数处理开始,一直到最后一个,一个一个按顺序通过中间件的栈,对匹配到的路径做处理 //router.use调用的时候最常用方式: //1.只有一个参数,是一个function //2.有两个参数,第一个是匹配到的url路径,第二个是一个function //3.传入一个数组参数,数组里是一个一个中间件function proto.use = function use(fn) { var offset = 0; //offset判断参数里第几个参数是function的偏移 var path = '/'; //默认匹配到的路径是/ // default path to '/' // disambiguate router.use([fn]) if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } //此处判断说明router.use也可以传进来一个数组参数,数组里的每一个元素都是一个中间件函数 // first arg is the path if (typeof arg !== 'function') { offset = 1; path = fn; } //如果传进来的第一个参数不是function类型,说明第一个参数是path,偏移量为1 } var callbacks = flatten(slice.call(arguments, offset)); //var slice = Array.prototype.slice; //使用slice将函数的参数变成一个数组,然后就可以当做数组来操作了 //所以Array.prototype.slice.call(arguments, offset)正好返回了offset偏移对应的参数,也就是中间件function,但此时它还是一个数组的形式,接着借用flatten第三方模块将数组扁平化,以防数组出现嵌套,最终的callbacks就是个中间件数组 //值得一提的是js草案里已经有flatten方法了,但是仍是实验性方法 if (callbacks.length === 0) { throw new TypeError('Router.use() requires a middleware function') } //如果callbacks数组长度为0,就报错误 for (var i = 0; i < callbacks.length; i++) {//开始循环callbacks数组 var fn = callbacks[i]; if (typeof fn !== 'function') { throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn)) } //如果不是一个function就报错 // add the middleware debug('use %o %s', path, fn.name || '<anonymous>') //输出调试信息 //使用指定路径、中间件函数创建Layer的实例,继承了router对象的大小写敏感、严格等选项 var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); //这个layer实例没有route,说明他不是路由,只是中间件 layer.route = undefined; //将layer实例添加到router的堆 this.stack.push(layer); //也就是说这里有几个中间件函数,就创建几个layer实例,让后把所有layer实例都存入router.stack里面 } return this; };
也就是说中间件处理函数最后放在了layer.handle上。
到这里this.lazyrouter()就分析完了。
接着看var route = this._router.route(path);调用了router的route方法,它根据给定的path路径创建新的route实例。并且创建了对应的layer实例,这个layer是带有route属性的,说明此中间件是带路由的。
下面是router.route:
/** * Create a new Route for the given path. * * Each route contains a separate middleware stack and VERB handlers. * * See the Route api documentation for details on adding handlers * and middleware to routes. * * @param {String} path * @return {Route} * @public */ //对给定路径创建新route实例 //每个route实例会包含一个分离的中间件stack和http动作handlers proto.route = function route(path) { var route = new Route(path);//创建一个新route实例 var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); //route.dispatch.bind(route),把Route.prototype.dispatch的this设置为当前route实例,创建一个新dispatch函数作为参数来创建layer实例 //Route.prototype.dispatch用于分配req和res对象 //这个layer的handle就是dispatch方法 //请求会最终在这个dispatch里面处理 layer.route = route; //这个Layer有route属性,代表了它是一个路由层,而不是中间件层 this.stack.push(layer);//_router.stack加入新layer return route;//返回新route实例 };
接着最后执行了route[method].apply(route, slice.call(arguments, 1));
它将this指向设置成当前route实例,调用已经在route里面定义好的http 动作函数,传入的参数就是处理函数,route里会根据这个处理函数生成对应的layer,这个layer会存入route.stack里面。
下面是在route.js里为route.prototype定义好的http 动作函数:
//循环http动作给Route.prototype添加上统一的处理函数,调用时直接用apply在设置了path的route实例上调用,传进来的参数是处理函数 methods.forEach(function(method){ Route.prototype[method] = function(){ var handles = flatten(slice.call(arguments));//将处理函数参数转换成一个数组 for (var i = 0; i < handles.length; i++) {//循环处理函数数组 var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires a callback function but got a ' + type throw new Error(msg); }//如果处理函数不是function类型,就报错 debug('%s %o', method, this.path) var layer = Layer('/', {}, handle);//以当前的处理函数新建一个layer实例 layer.method = method;//layer.method为当前的http method this.methods[method] = true;//给当前apply调用的route实例的methods属性里加入当前http method this.stack.push(layer);//当前route实例的stack中加入layer实例 } //可以看到这里生成的layer实例没有设置path,而是设置成'/',这是因为path在route实例里面设置过了 return this; }; });
通过上面的分析发现,不管是什么样的处理函数最终都是一个layer实例,处理所有请求的中间件例如query和 init所对应的layer都存在了router实例的stack了里面。
而用户自定义的带路由路径的处理函数所对应的layer实例都存在了对应route实例的stack里面。而这个route实例是一个路由层layer的属性,这个layer也在router.stack里面。
也就是说app._router.stack里面有两种layer,一种是query和init这种,另外一种是指定路径路由layer,路径layer有一个route属性,route上面还有个route.stack里面存储着最终处理函数的那个layer。
app.listen()
app.listen = function listen() { var server = http.createServer(this); //将app传递给http.createServer创建服务器实例 return server.listen.apply(server, arguments); //监听端口 };

浙公网安备 33010602011771号