nodejs 实践:express 最佳实践(三) express 解析
nodejs 实践:express 最佳实践(三) express 解析
nodejs 发展很快,从 npm 上面的包托管数量就可以看出来。不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需要开发者创造大量的轮子来解决现实的问题。
知其然,并知其所以然这是程序员的天性。所以把常用的模块拿出来看看,看看高手怎么写的,学习其想法,让自己的技术能更近一步。
引言
前面一篇文章都已经研究过 express 3.x 中的核心框架 connect 的代码,这一阵来看看 express 3.x 和 express 4.x 的核心代码是怎样,特别关注两个部分:
- express 3.x 中 connect 的使用。
- express 4.x 中 的流程
解析
要使用 express 首先要明白几个概念:
- application (3.x)
- subApplication (3.x)
- router (3.x)
- middleware (3.x)
- route (3.x)
- layer (4.x)
首先 application 就是指的一个 express 实例,这个实例可以有自己环境变量等,而 subApplication 是指的 application 下面又会嵌套 一个 express 实例对像。
每一个 application 中都只有一个 router 路由对像,这个对像管理这个 application 下面有所有的 subApplication, middleware 和 route.
subApplication 不说了, middleware 的表现形式在 express 中有三种: fn(req, res, next)
,fn(err, req, res, next)
, fn(req, res)
。
第一种是正常的中间件:你对数据进行处理,然后调用 next 调用下一个中间件。
而第二种是错识中间件: 你出错口才调用它,而且必须是 四个参数,不能多也不能少。
第三种也是对数据进行处理,但是没有 next 说明数据到里结束。
route 就指的某个具体的路由,就是比如:get, post, delete, put, all
等方法,这类方法能新建一个路由的对像。
layer 是 4.x 新出来的概念,这个也简单,就是方法的执行体。所谓的执行体,在 3.x中, subApplication 调用 handle, route调用 handle, middleware 调用自己,而在 4.x 中,这些都变成了 layer ,统一起来了。 请求来了, 就遍历 layer 看看哪个 layer匹配了路由,然后执行 layer.handle_request 方法。
上面说了这么多就是说 一个 application 就是由 subApplication, middleware, route 组成的, 这些才是真正的有执行体的地方, 下面说下, 这几个部分怎样加入到一个 application 中去的。
subApplication 加到入到 application 一般是调用 use 方法:
var userCenterApp = express()
var app = express()
app.use('/user', userCenterApp);
// app.use(subApplication);
在上面代码中, /user
是指的 子应用的 挂载点,可以理解成,这个子应用的所有的方法,都是在 /user
这个 url 之下执行的。
如果没有第一个参数, 那是个子应用的挂载点默认是 /
可以理解成根应用。
这其中,当子应用挂载成功到父应用上时,子应用会发出一个 mount 事件,express 会在默认情况下,把父应用的 settings, engines 等设置都拷过来。
this.on('mount', function onmount(parent) {
// inherit 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
// 复制属性到 子 express 的 request 和 response 中去
setPrototypeOf(this.request, parent.request)
setPrototypeOf(this.response, parent.response)
setPrototypeOf(this.engines, parent.engines)
setPrototypeOf(this.settings, parent.settings)
});
如果你需要子应用与父应用有不同的设置,可以监听这个事件,可以这样做:
var subApp = express();
subApp.on('mount', function(parent) {
// dosomething
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
})
通常我们都不对 subApplication 进行设置,直接把父级 locals 变量直接合并过来。
middleWare 加入到 application 中也是使用 use 方法:
var app = express();
app.use(function(req, res, next) {
//正常使用
})
app.use(function(next, req, res, next) {
//错误处理
})
app.use(function(req, res) {
// 到此为止
})
app.use('/user', function(req, res, next) {
//针对 /user 下的所有请求都调用此处方法
})
一般来说 错误处理中间件都会放在所有的中间件的最后面,这会有两个处理中间件,一个是针对客户端不可知路由,一个是针对服务器错误。
app.all('*', function clientError(req, res, next) {
res.rend('404');
})
app.use(function serverError(err, req, res, next) {
res.rend('404);
})
router 加入到 application 中也是使用 use 方法:
var router = express.Router()
var app = express();
router.get('/app', fn);
app.use(router); // 挂载到 根目录 / 下
app.use('/user', router); // 挂载到 /user 下
route 加入到 application 中有两种方法:app, router
// 通过 app 使用
// 这样使用
app.get('/user', fn1, fn2)
app.post('/user', fn3, fn4)
app.delet('/user', fn5, fn6)
// 也可以这样使用
var route = app.route('/user');
route.get(fn1, fn2)
.post(fn3, fn4)
.delete(fn5, fn6)
在 router 中,也可以这样使用:
var router = express.Router()
router.get('/user', fn1, fn2)
router.post('/user', fn3, fn4)
// 另一种写法
var route = router.route('/user')
route.get(fn1, fn2)
.post(fn3, fn4)
前面 route 中的 fn*
,其实也是中间件,遵守中间件的使用方法。
而 express 中核心的路由循环,就在 _router.handle 中,如下:
proto.handle = function handle(req, res, out) {
var self = this;
// some thing
next();
function next(err) {
var layer;
var match;
var route;
// 在这里每次请求会遍历所有下一个路由,直到 match 成功后,会跳出循环
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path); // 这是核心方法
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
// 没有route 的 layer, 会匹配 match === true, 然后再下面再执行
if (!route) {
// process non-route handlers normally
continue;
}
if (layerError) {
// routes do not match with a pending error
match = false;
continue;
}
var method = req.method
// 如果有路由,则看看有没有该方法如 get post 方法。
var has_method = route._handles_method(method);
// build up automatic options response
if (!has_method && method === 'OPTIONS') {
appendMethods(options, route._options());
}
// don't even bother matching route
if (!has_method && method !== 'HEAD') {
match = false;
continue;
}
}
// no match
if (match !== true) {
return done(layerError);
}
// store route for dispatch on change
if (route) {
req.route = route;
}
// this should be done for the layer
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err);
}
if (route) {
return layer.handle_request(req, res, next);
}
// 移除多余的匹配
trim_prefix(layer, layerError, layerPath, path);
});
}
function trim_prefix(layer, layerError, layerPath, path) {
// somenthing
if (layerError) {
layer.handle_error(layerError, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
流程就在,是否匹配路由,是否route中有方法,然后执行相应的方法上面。
调用流程: app() ===> app.handle ===> this._router.handle ===> if layer.match(path) ==> layer.handle_request
而 app.use 方法就是调用 _router.use 方法:
// app.use
fns.forEach(function (fn) {
// non-express app
if (!fn || !fn.handle || !fn.set) { // 如果只是一个函数,直接加入到 route 中
return router.use(path, fn);
}
// 下面针对的是一个子 express 的应用,就是指有 handle 的应用
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = this;
// 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) {
setPrototypeOf(req, orig.request) // 这里每次都把属性复制过来,就是怕第三方的把属性给改了,之后让框架崩溃
setPrototypeOf(res, orig.response)
next(err);
});
});
router.use 方法
proto.use = function use(fn) {
var offset = 0;
var path = '/';
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; // 这个layer是没有路由的
this.stack.push(layer);
}
return this;
};
总结
把 express 中的 middleware, application, router, route 概念弄清楚了,express 的用法也就清楚了。
另外,我在解析 express 代码时,也运行了 express 的代码, 同时打断点进行调试,并且在关键的地方也有注释,如果有需要,也可以从这个地址进行下查看:
看代码主要是弄清楚具体的流程,和实现思想,给自己的架构添加想法。
下一篇要讲讲,express 中的最佳实践,包括项目规划, 目录结构,中间件使用,错误中间件,promise,co,await/await,限流,重写,子域名等。
nodejs 实践:express 最佳实践(三) express 解析的更多相关文章
- nodejs 实践:express 最佳实践(四) express-session 解析
nodejs 实践:express 最佳实践(四) express-session 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs ...
- nodejs 实践:express 最佳实践(五) connect解析
nodejs 实践:express 最佳实践(五) connect解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固,需 ...
- nodejs 实践:express 最佳实践(七) 改造模块 connect2 解析
nodejs 实践:express 最佳实践(七) 改造模块 connect2 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的 ...
- nodejs 实践:express 最佳实践系列
nodejs 实践:express 最佳实践系列 nodejs 实践:express 最佳实践(一) 项目结构 nodejs 实践:express 最佳实践(二) 中间件 nodejs 实践:expr ...
- nodejs 实践:express 最佳实践 (一)
express 最佳实践 (一) 最近,一直在使用 nodejs 做项目,对 nodejs 开发可以说深有体会. 先说说 nodejs 在业务中的脚色,, 在 web同构 方面, nodejs 的优势 ...
- nodejs 实践:express 最佳实践 (一) 项目结构
express 最佳实践 (一) 第二篇: express 最佳实践(二):中间件 最近,一直在使用 nodejs 做项目,对 nodejs 开发可以说深有体会. 先说说 nodejs 在业务中的脚色 ...
- nodejs 实践:express 最佳实践(二) 中间件
express 最佳实践(二):中间件 第一篇 express 最佳实践(一):项目结构 express 中最重要的就是中间件了,可以说中间件组成了express,中间件就是 express 的核心. ...
- nodejs 实践:express 最佳实践(六) express 自省获得所有的路由
nodejs 实践:express 最佳实践(六) express 自省获得所有的路由 某些情况下,你需要知道你的应用有多少路由,这在 express 中没有方法可以.因此我这边曲线了一下,做成了一个 ...
- nodejs 实践:express 最佳实践(八) egg.js 框架的优缺点
nodejs 实践:express 最佳实践(八) egg.js 框架的优缺点 优点 所有的 web开发的点都考虑到了 agent 很有特色 文件夹规划到位 扩展能力优秀 缺点 最大的问题在于: 使用 ...
随机推荐
- 生成0-42之间的7个不重复的int值
public static void main(String[] args) { //set集合存储不重复无序的值 Set<Integer> set = new HashSet<In ...
- P1880 [NOI1995]石子合并[区间dp+四边形不等式优化]
P1880 [NOI1995]石子合并 丢个地址就跑(关于四边形不等式复杂度是n方的证明) 嗯所以这题利用决策的单调性来减少k断点的枚举次数.具体看lyd书.这部分很生疏,但是我还是选择先不管了. # ...
- 四维偏序 CDQ套CDQ
对CDQ深一步的理解 昨天做了一道CDQ,看了一堆CDQ可做的题,今天又做了一道四维偏序 感觉对CDQ的理解又深了一点,故来写一写现在自己对于CDQ的理解 CDQ其实就是实现了这样的一个问题的转化: ...
- 高性能的序列化与反序列化:kryo的简单使用
前言:kryo是个高效的java序列化/反序列化库,目前Twitter.yahoo.Apache.strom等等在使用该技术,比如Apache的spark.hive等大数据领域用的较多. 为什么使用k ...
- jupyter-notebook重设项目工作路径
一. . Anaconda Prompt 命令(方法没生效) 1 选择一个用于存放config文件的文件夹(先创建) 2 在cmd中进入该文件夹的路径 3在cmd中 输入命令 jupyter not ...
- Codeplus2017 11月赛T3——基因
题目:https://www.luogu.org/problemnew/show/P4059 DP,状态应分为空格或字母,可用0和1表示,据此转移,详见代码. 另:注意初始化,因为有负值所以要先把f数 ...
- Django admin有用的自定义功能
引用园友 无名小妖 的博客 https://www.cnblogs.com/wumingxiaoyao/p/6928297.html 写的很好,但是博客园不能转载,不过我已经点赞了~
- Ajax的属性
1.属性列表 url: (默认: 当前页地址) 发送请求的地址. type: (默认: "GET") 请求方式 ("POST" 或 "GET ...
- 《Java多线程编程核心技术》读后感(八)
不使用等待/通知机制实现线程间通信 使用sleep()结合while(true)死循环来实现多个线程间通信 package Third; import java.util.ArrayList; imp ...
- SQL Server等待事件新解
资源等待类型 并行:CXPACKET Buffer:PAGEIOLATCH_X 非Buffer:LATCH_X I/O:ASYNC_IO_COMPITION:IO_COMPITION CPU:SOS_ ...