一、简介
二、简单用法
2.1)安装
sh
pnpm add connect
2.2)简单示例
ts
import connect from 'connect'
const app = connect()
app.use((req, res) => {
rese.end({a: 2})
})
app.listen(3000, () => {
console.log("server on:http://localhost:3000")
})
三、Node.js 模块和 api
- http 模块
- http.createServer 方法
四、内置核心概念
你可以将这些信息转换成表格形式如下:
术语 | 描述 |
---|---|
app | 应用 (底层对象函数) |
use | 使用中间件 (调用后产生 stack 和 layer 栈) |
route | 路由 (路径) |
handle | 中间件处理函数 |
next | 调用下一个中间件,立即为 handle 的传递器 |
layer | 一个 use 调用会产生一层 (app 栈层,用于保存 route 和其他信息) |
4.1)app 函数对象
app 是一个 函数对象,为 http.createServer 参数服务。
ts
http.createServer(app)
createServer 需要传递一个函数,函数参数是 req 请求对象和 res 响应对象。
4.2)app 的实现
ts
function app(req, res, next){ app.handle(req, res, next); }
connect 实现的 app 函数对象,参数中包含 next 方法,虽然开始的时候为空。
4.3)app 的增强
ts
merge(app, proto); // 合并自己的原型
merge(app, EventEmitter.prototype); // 合并时间触发
app.route = '/'; // 添加根路由
app.stack = []; // 添加栈
4.4)原型三方法:use/handle/listen
ts
proto.use = function use(route, fn) {
this.stack.push({ route: path, handle: handle }); // 添加中间件和路由处理(本质是添加 layer 到栈)
return this; // 放回 this,支持链式调用
}
proto.handle = function handle(req, res, out) {
function next() {
//
call() // 调用 call 方法,开始执行第一个中间件的处理函数,并传递 next
}
next() // 开始 next
}
proto.listen = function listen() {}
五、app.use
app.use 本质就是在 app.stack 收集 layer 栈(需要将每次调用 use 就会在 stack 中添加一层
):
ts
this.stack.push({ route: path, handle: handle }); // push 的一个 layer
这个 layer 栈非常重要,后面 handle 与 next 串联一起时访问就是 stack
中的 layer 中保存的数据(route 和 handle)。
六、listen 开始监听
代码写完了,需要监听服务才能接收外部的请求
ts
app.listen(3000, () => {
console.log("server on:http://localhost:3000")
})
- 可以配置自己的监听端口和 host 地址,配置监听回调函数。此从开始就可以接收外部服务了。
ts
proto.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
注意: this 时运行时绑定,this 运行时指向 app 对象函数。
七、从 app.handle 开始进入程序开始服务
ts
function app(req, res, next){ app.handle(req, res, next); }
http.createServer(this)
中的 req/res 传递给app.handle
。app.handle
是顶层 app 属性,每一个请求都从这里开始。
八、app.handle(即: proto.handle) 顶层处理函数
每一个请求的开始的地方,他有四个个核心的内容:
你可以将这些信息转换成表格形式如下:
术语 | 描述 |
---|---|
index | 当前访问栈的索引 |
stack | 保存在栈中的层 |
layer | use 的调用之后产生的层 |
next | 顶层 next 方法,会被传递到每一个 handle 中 |
call next | 内部真实的 invoker (召唤者) |
ts
var index = 0;
var stack = this.stack;
var layer = stack[index++];
// 顶层方法
function next(){
// ...
call(layer.handle, route, err, req, res, next);
}
// 并且需要自己执行
next()
九、真正的发起者:call 函数
call 函数 梦开始的地方,从上面的理解中我们知道,call 是顶层 next 的调用的发现者,它会真正的调用 layer 中保存的 handle 函数,实现如下
ts
function call(handle, route, err, req, res, next) {
var arity = handle.length;
var error = err;
var hasError = Boolean(err);
try {
if (hasError && arity === 4) {
// error-handling middleware
handle(err, req, res, next);
return;
} else if (!hasError && arity < 4) {
// request-handling middleware
handle(req, res, next);
return;
}
} catch (e) {
// replace the error
error = e;
}
// continue
next(error);
}
根据是否有错误和参数长度,来确定 handle 函数应该如何调用,此过程如果发生了错误,直接调用 next 方法进入下一个中间件的 handle 函数。
十、next 方法的传递
顶层 next 方法在顶层的 handle 中进行调用,准备好数据之后,有 call 函数传递 next 到中间 handle 函数中(其实整个 connect 中虽然写了很多的 next 方法,其实就是调用的这一个,这个 next 方法贯穿了整个 connect 应用)。
十一、handle 函数与 next 函数直接进行串联
从顶层的handle 函数开始,到最后一个中间件 handle 结束,中间通过 next 函数进行串联。next 函数将一个 handle 分成了两个半:
- 一半在所有next 调用之前执行
- 一半在所有next 调用之后执行
这么一个巧妙的函数串联模型。
十二、联想:与链表结构对比
虽然 JavaScript 中链表数据结构直接使用的非常少,但是其编程的思想无处不在。
next 方法与链表非常相似,但是又不同, 链表往往守卫相连,但是 next 与handle之间是链接关系,next 可以将 handle 的运行时一分为二,将 handle 函数链接。
十三、小结
connect 是最容易理解的中间件模型。在监听之前,一个 use 方法调用,在 stack 中添加一个 layer, 监听之后请求开始进来,在 handle 中取出 stack 中 layer,在 next 机制下,将 layer 中的 handle 一分为二的串联起来,构成 connect。connect 简单精妙非常,适合学习。