express4.X 跟3.X 有很大区别,4.X 去除了connect的依赖,3.X基于connect的中间件基本所有不能用,若是还有可使用的,也是4.X重写的。因此要想继续使用这些熟悉的中间件,就要手动安装依赖包,或者用一些其余的中间件。node
typeof express === 'function' //true
能够知道express是个函数,这个函数是程序启动就会运行起来web
function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, proto); mixin(app, EventEmitter.prototype); app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app }; app.init(); return app; }
上面这个函数就是express,有没有看到很熟悉的东西,看到app没,还在哪里看到过这个熟悉的东西...express
对了,没错。就是每个nodejs教程里面开始nodejs教学的事例,nodejs启动服务器:http.createSever 的回调函数。app是express贯穿整个流程的函数。其实整个express 执行过程就是往req,res这两个对象不停的修改属性,添加属性。直到完成请求。中间件也就是经过app作为回调,进而修改req,res。从而实现可插拔的效果。json
var app = express();
这就是为何引入express,都要开始执行一下这个函数。服务器
express作为一个web框架,首先要有启动一个服务器的,咱们看下服务器是在哪里启动的app
var server = app.listen(app.get('port'), function() { debug('Express server listening on port ' + server.address().port); });
express用了一个我不太喜欢用的写法,他把全部的方法直接放到app这个函数上去了,你们都知道函数在js中就是对象,除了自己是能够执行之外,和对象是没有什么区别的。不过这就无形之中增长了阅读代码的难度,并且很容易混淆,由于app既作为一个中间件,还要作为一个公共方法的载体。框架
好了,讲到启动服务器,app是没有启动服务器的能力的,这个能力是在application 这个文件中被mix进去的,其实就是mix一个http.createServer方法,可是这里仍是要看一下代码。dom
app.listen = function(){ var server = http.createServer(this); return server.listen.apply(server, arguments); };
看到this没有啊,这个this很重要哈,this == app 。app作为回调已经传进来了,神奇的中间件在这里开始了旅程。ide
function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, proto); mixin(app, EventEmitter.prototype); app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app }; app.init(); return app; }
好,下面到了application模块的init方法里面去了函数
app.init = function(){ this.cache = {}; this.settings = {}; this.engines = {}; this.defaultConfiguration(); };
增长了cache setting engines 三个对象,如今看不出来做用,具体执行过程到defaultConfiguration里面看看
this.enable('x-powered-by')
看到了enable,而后进去看enable其实就set,只不过第二个参数是boolean。set是什么呢?还记得咱们没有了解功能的三个对象之一的setting,这个set就是往setting对象添加一些属性而已。
好 先看defaultConfiguration
this.enable('x-powered-by')
设置x-powered-by 为true,x-powerd-by是什么意思呢?
有些查询工具在咱们输入某个站点的URL后就能判断这个站点的WebServer与程序类型。
就是在http请求的时候,可以看到x-powered-by:Express,不设置 就看不到服务区类型,这应该是http请求的一部分
this.set('etag', 'weak');
这里处理etag的 Express依赖了一个叫etag的包
var env = process.env.NODE_ENV || 'development'; this.set('env', env); this.set('query parser', 'extended'); this.set('subdomain offset', 2); this.set('trust proxy', false);
这里继续设置属性。
// inherit protos 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; }); // setup locals this.locals = Object.create(null); // top-most app is mounted at / this.mountpath = '/'; // default locals this.locals.settings = this.settings; // default configuration this.set('view', View); this.set('views', resolve('views')); this.set('jsonp callback name', 'callback'); 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.'); } });
这里的mount,我以前不知道什么意思,后来看其余应用才知道,这是用来挂载其余应用的,好比我有几个应用,能够起几个业务服务,用一个中央服务监听端口,而后挂载其余几个应用模块
研究发现这个时候express的初始化流程已经走完了,之前看过3.X的源码,貌似不是这样子的,可是仔细观察,确确实实到这里是结束了。剩余的方法都是怎么处理的呢?在细细往下看吧
add middleware to the app router
这是源码里面的解释,向路由添加中间件,前面说过中间件和路由没有本质区别,是同样的东西。
app.use = function use(fn) { var offset = 0; var path = '/'; var self = this; // default path to '/' // disambiguate app.use([fn]) if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } // first arg is the path if (typeof arg !== 'function') { offset = 1; path = fn; } } var fns = flatten(slice.call(arguments, offset)); if (fns.length === 0) { throw new TypeError('app.use() requires middleware functions'); } // setup router this.lazyrouter(); var router = this._router; fns.forEach(function (fn) { // non-express app if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); } debug('.use app under %s', path); fn.mountpath = path; fn.parent = self; // restore .app property on req and res router.use(path, function mounted_app(req, res, next) { var orig = req.app; fn.handle(req, res, function (err) { req.__proto__ = orig.request; res.__proto__ = orig.response; next(err); }); }); // mounted an app fn.emit('mount', self); }); return this; };
因而咱们看到lazyrouter这么个东西,这个函数里面new 了一个Router对象,因此这一张暂时略过了 咱们要去route里面看看了
昨天看源码遇到了麻烦,发现不少代码还不是那么容易看懂,有些迷糊,而后犯了一些错误,打了不少断点终于弄清楚了
想要明白express的处理流程,必须先要弄清楚app.use和 app.handle这两个方法,这两个方法很重要。
前面咱们已经知道app自己是作为回调参数传进http.createServer里面的,应用全部的路由都会掉进这个函数里面去,通过一个一个中间件进行处理。自己想一想不是很复杂,但看起代码来仍是很蛋疼的
首先req,res被封装了不少方法进去,可是这个方法是在什么地方mix进去的呢。在这里我就犯了个错误,错误的认为会在use的时候就会有这个方法,因此我在use函数里面找啊找,打了不少个断点,始终没有找到哪里执行了这个操做。
但实际上,use始终没有作这个操做,use的做用就是route里面把这个回调push进route实例的stack里面,看代码
if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); }
app的use执行了 Route实例的use。继续看Route的use
var layer = new Layer(path, { sensitive: self.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; self.stack.push(layer);
去看会发现route的use和app的use会有些重复的代码,不一样的地方就在于Route的use会建立一个layer。这个layer就是个实例,就是每一个回调函数的实例。这个实例包括全局配置的一些属性,好比严格匹配,大小写。还有就是把当前use的路由url和回调存储起来了,所有push进stack里面去。
看下route的实例化过程,会发现express默认放置了两个中间件进去。代码以下
app.lazyrouter = function() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled('case sensitive routing'), strict: this.enabled('strict routing') }); this._router.use(query(this.get('query parser fn'))); this._router.use(middleware.init(this)); } };
因此app默认就会有两个中间件,query和 middleware。程序执行到这里已经执行结束了。
那又有问题了,request,response这两个对象的不少扩展方法,从何而来。
下面就来看看吧
打开middleware/init
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; req.__proto__ = app.request; res.__proto__ = app.response; res.locals = res.locals || Object.create(null); next(); }; };
这里就看到了 request,response是在这里被放置到回调的req,res上去的。因为内置的这两个中间件是首先添加的,被放置在stack的前两个,因此每一个请求进来首先会进入这两个中间件里面去,而后带了不少东西进入其余的中间件去。
还有问题啊,use不是能够增长路由吗 不是能够控制哪一些中间件走哪一些路由嘛,那是怎么控制的呢。看这里。。。
proto.match_layer = function match_layer(layer, req, res, done) { var error = null; var path; try { path = parseUrl(req).pathname; if (!layer.match(path)) { path = undefined; } } catch (err) { error = err; } done(error, path); };
这里会把layer里面存储的route正则拿来和当前路由匹配,成功则进入回调执行,失败则继续执行。