介绍
expressjs是我们大多数人都知道的网络框架。我真的很喜欢使用Express设置新服务器的容易和快速。
在引擎盖下,Express是节点HTTP模块周围的轻质包装器。它抽象了HTTP模块的一些复杂性,并为我们提供了一种构建后端服务器的简单方法。
在本文中,我想了解如何实施Express,看看我是否可以从中学到一些东西。
开始阅读代码
ExpressJS的源代码可用:https://github.com/expressjs/express
首先,让我们检查目录结构:
├── index.js
├── lib
│ ├── application.js
│ ├── express.js
│ ├── middleware
│ │ ├── init.js
│ │ └── query.js
│ ├── request.js
│ ├── response.js
│ ├── router
│ │ ├── index.js
│ │ ├── layer.js
│ │ └── route.js
│ ├── utils.js
│ └── view.js
主要代码现用在lib
文件夹中,其中有一些模块,例如:应用程序,中间件,请求,响应,路由器,确实很简单,与其他网络框架相比,只有几个文件。
现在,我们可以直接跳入express.js
并读取代码。但是我想以不同的方式处理阅读过程。
让我们说我们有一个使用Express的微小应用程序。
const express = require('express')
const myapp = express()
const port = 3000
myapp.use(whateverLogger())
myapp.use('/hello', (req, res) => {
res.send('Hello World!')
})
myapp.post('/world', (req, res) => {
res.send('ok')
})
myapp.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
我们可以将此应用程序分为两个阶段:
-
阶段1:注册处理程序
- 在此阶段,处理程序(中间件)已设置为如下:
whateverLogger
和/hello
和/world
的处理程序。
- 在此阶段,处理程序(中间件)已设置为如下:
-
第2阶段:处理请求
- 此阶段是服务器接收请求并处理它们的地方
让我们开始在每个阶段开始读取源代码。
阶段1:注册处理程序
我们需要注意的主要方法是两个:
myapp = express()
myapp.use()
对于express()
,我的第一个猜测是阅读express.js
express.js
...
var proto = require('./application');
var Route = require('./router/route');
var Router = require('./router');
...
exports = module.exports = createApplication;
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
...
return app
}
(为了清晰而省略其他细节)
函数调用返回app
,这也是一个函数。 mixin(app, proto, false)
表明该app
的大多数逻辑将位于./application
我们可以跳过混合素逻辑,并假定其目的是将方法和属性添加到app
我们的下一个跳动是application.js
application.js
在第194行,我们可以看到app.use
的定义:
...
var Router = require('./router');
...
app.use = function use(fn) {
...
var router = this._router;
var fns = flatten(slice.call(arguments, offset));
...
fns.forEach(function (fn) {
if (!fn || !fn.handle || !fn.set) {
return router.use(path, fn);
}
...
}, this);
return this;
};
这种方法中有很多活动,但是首先要吸引我们的眼睛的是fns.forEach
,称为router.use(path, fn)
app.use
收到功能列表(这是我们的路径的处理程序)。然后使用router
注册每个功能。
现在让我们跳到router.js
router.js
这个文件中发生了很多有趣的事情。
首先,构造函数代码显示了stack
。
每当我看到堆栈(或队列)时,我立即想到一个数组,当我们插入或访问数组时具有某种顺序。
var proto = module.exports = function(options) {
...
router.stack = [];
return router;
};
继续,我们找到router.use
最佳点是为循环创建新的Layer
并将其推到堆栈
的循环
proto.use = function use(fn) {
...
var callbacks = flatten(slice.call(arguments, offset));
...
for (var i = 0; i < callbacks.length; i++) {
var fn = callbacks[I];
...
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined;
this.stack.push(layer);
}
return this;
};
- 堆栈上的每个项目都是
Layer
-
Layer
只能接收path
和fn
回调
现在,在这一点上,我可以想象该设置如何存储在堆栈中:
myapp.use('/hello', handler1, handler2)
myapp.use('/world', handler3, handler4)
这将成为:
router.stack = [
Layer('/world', handler4),
Layer('/world', handler3),
Layer('/hello', handler2),
Layer('/hello', handler1) // <-- index: 0
]
我相信我们现在对注册过程的工作方式有基本的了解,而Scence
背后的数据结构是什么在转到第2阶段之前,这里是总结所有内容的图。
https://www.codediagram.io/app/shares?token=5dce1a36
on 第2阶段:处理请求,我们通过研究next()
函数的实现
请保持调整ð