虽然说如今能用户 express 搭建简单的静态服务器,作一些简单的数据交互,可是老是有一种知其然不知其因此然的感受。自己对nodejs的了解也不够。正好花些时间来阅读如下源码,本文基于Express 4.15.4html
项目目录架构选了最主要的一部分列了出来前端
├── lib | ├── middleware | | ├── init.js | | └── query.js ├── ├── router //router组件,负责中间件的插入和链式执行 | | ├── index.js | | ├── layer.js | | └── route.js ├── ├── application.js ├── ├── express.js ├── ├── request.js //提供一些方法丰富 request的实例功能 ├── ├── reponse.js ├── ├── util.js ├── └── view.js //提供模板渲染引擎的封装,经过 res.render() 调用引擎渲染网页 | └── index.js
在express中出现频率最高的是 express
,app
可见其重要性。举个例子,搭建一个最简单的静态服务器node
const express = require('express') const app = express() app.get('/',function(req,res){ res.send('Hello World!') res.end() }) app.listen(3000,function(){ console.log('服务器已启动...') })
接下来在源码中分析express和appgit
/* * express.js */ //调用express()返回的app实际上是一个函数 exports = module.exports = createApplication //createApplication就至关于express的 main 函数,其中完成了全部建立express实例所须要的动做 function createApplication(){ var app = function(req,res,next){ app.handle(req,res,next) } //此处是经过mixin导入 mixin(app,EventEmitter.protprype,false) mixin(app,proto,false) app.request = Object.create(req,{...})x app.response = Object.create(res,{...}) return app }
上述代码中的app.handle,handle方法是在application.js中定义的。首先看一下application.js的总体架构github
//application.js app.init = function init(){ this.cache = {} this.engines = {} this.settings = {} this.defaultConfiguration() } //app.相关方法 app.render = function render(name,options,callback){...} app.listen = function listen(){ var server = http.createServer(this); return server.listen.apply(server, arguments); } //容错处理 function logederror(err){...} function tryRender(view,options,callback){...}
app是一个请求处理函数,做为http.createServer的参数。而express实际上是一个工厂函数,用来生成请求处理函数。
为何说express是一个工厂函数?搭建静态服务器的时候的一行代码能够来证实:web
const app = express()
接下来将 app.handle() 单独提出来研究一下:
在express.js中是经过mixin导入的。其做用是将每对[req,res]进行逐级分发,做用在每一个定义好的路由及中间件上,直到最后完成express
app.handle = function(req,res,callback){ var router = this._router //final handler var done = callback||finalhandler(req,res,{ env:this.get('env'), onerror:logerror.bind(this) }) //no routes if(!router){ debug('no routes defined on app') done() return } router.handle(req,res,done) }
这里引伸除了两个关键点 中间件
和 路由
编程
在express中最关键的概念就是中间件了。一个Express应用从本质上来讲就是一系列的中间件调用。那么到底什么是中间件?数组
一个中间件本质上就是一个函数,调用中间件就是对函数的调用。在Express4.x中取消了全部内置的中间件,须要从外部引入。这样的改进是的express 是一个独立的路由和web中间件框架,更新更加方便更加符合unix哲学服务器
中间件通常函数形式以下:
function middleware(req,res,next){ //... }
异常处理是异步编程的一个难点,咱们不能用常规的 try-catch
语句块去捕获异常。try-catch只能捕获当次事件循环内的异常,对callback执行抛出的异常无能为力。因此Node在处理异常上造成一种约定,将异常做为回调函数的第一个实参传回,若是为空值,则代表没有异常抛出
处理错误中间件函数形式:
function middleware(err,req,res,next){ //error做为函数的 //... }
接下来看一下中间件函数中的参数
忽略req 和 res , next
自己也是一个函数,调用 next()
就会继续执行下一个中间件。请求过程图解以下:
↓ --------------- | middleware1 | --------------- ↓ --------------- | ... ... ... | --------------- ↓ --------------- | middlewareN | --------------- ↓
在使用express时咱们常常会看到这样的代码:
//匹配全部以/user开始的路径 app.use('/user',function(req,res,next){...}) //精确匹配到/user路径 app.get('/user',function(req,res,next){...})
根据上述代码,中间件大体能够分为两种:普通中间件
和 路由中间件
。注册普通中间件一般是经过 app.use()
, 注册路由中间件,一般是经过 app.METHOD()
来注册。
而这两种方法其实都是由 app.handle
来处理
要想了解请求处理的详细过程,首先须要了解Router。先来看一下Routerde 目录结构:
├── router | ├── index.js | ├── layer.js | └── route.js
简单来讲,Router(源码在router/index.js)就是一个中间件的容器。事实上,Router是Express中一个很是核心的东西,基本上就是一个简化版的Express框架。app的不少API,例如:app.use(),app.param(),app.handle()等,事实上都是对Router的API的一个简单包装。能够经过app._router来访问默认的Router对象。
methods.forEach(function(method){ //在JavaScript能够经过 '[]' 或 '.'来访问对象属性. //当咱们不仅属性内容,或者属性内容是个变量时能够用'[]' app[method] = function(path){ if(method==='get' && arguments.length===1){ //app.get(setting) return this.set(path) } this.lazyrouter() var route = this._router.route(path) route[method].apply(route,slice.call(arguments,1)) return this } })
经过上述代码咱们能够发下,Express对 METHOD
方法的添加都是动态的。一个forEach循环搞定了全部method函数的定义。
看下函数的内部实现,若是函数参数长度为 1 ,那么直接返回this.set(path)。查看Express API能够发现:app.get()
实现了两个功能,若是长度为1,则返回app.set()定义的变量,若是参数长度大于1 ,则进行路由处理。
接着, this.lazyrouter()
,定位到源码
app.lazyrouter = function lazyrouter(){ if(!this._router){ //若是_router不存在,就new一个Router出来 this._router = new Router({ caseSensitive: this.enabled('case sensitive routing'), strict: this.enabled('strict routing') }) this._router.use(query(this.get('query parse fn'))) this._router.use(middleware.init(this)) } }
这个new出来的Router其本质上是一个中间件容器。而且 Router是Express中一个很是核心的东西,基本上就是一个简化版的Express框架
。app的不少API,例如:app.use(),app.param(),app.handle()等,事实上都是对Router的API的一个简单包装。能够经过app._router来访问默认的Router对象。
返回以前的 methods.forEach
,发现当执行完 this.lazyrouter() 以后Route
能够简单的理解为存放路由处理函数的容器,它有一个 stack
属性为一个数组,其中的每一项是一个Layer对象,是对路由处理函数的包装。
var route = this._router.route(path)
根据上述代码能够定位到 router/index.js
proto.route = function route(path){ var route = new Route(path) var layer = new Layer(path,{ sensitive:this.caseSensitive, strict:this.strict, end:true },route.dispatch.bind(route)) layer.route = route this.stack.push(layer) return route }
这里new了一个 route
对象和一个 layer
对象,而后将route 对象赋值给 layer.route
Route模块对应的是route.js,主要是来处理路由信息的,每条路由都会生成一个Route实例。而Router模块对应的是index.js,Router是一个路由的集合,在Router模块下能够定义多个路由,也就是说,一个Router模块会包含多个Route模块。经过上边的代码咱们已经知道,每一个express建立的实例都会懒加载一个_router来进行路由处理,这个_router就是一个Router模块。
那么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 callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }; });
和application.js中处理方式是相同的。当调用 route.MEHOD()
的时候,新建一个layer 放在 route.stack
中
经过上面的分析能够发现,Router实际上是一个二维的结构。例如,一个可能的router.stack结构以下所示:
---------------- | layer1 | ---------------- ↓ ---------------- layer2.route.stack ------------ ------------ ------------ | layer2 | ------------------> | layer2-1 |-->| layer2-2 |-->| layer2-3 | ---------------- ------------ ------------ ------------ ↓ ---------------- layer3.route.stack ------------ ------------ | layer3 | ------------------> | layer3-1 |-->| layer3-2 | ---------------- ------------ ------------ ↓ ---------------- | ...... | ---------------- ↓ ---------------- | layerN | ----------------
综上所述,咱们发现一个路由中间件注册的过程大体为:app.METHOD-->router.route-->route.METHOD
而其中最重要的是经过 router.route() 来建立一条新路由,而后调用 route.METHOD()来注册相关函数