原来 connect 中间件 handle 与 next 串联的这么简妙

一、简介

connect 一个可扩展的 HTTP 服务器框架,用于使用称为中间件的"插件"的 Node

二、简单用法

graph LR 安装connect --> 创建app --> 添加各种中间件 --> 监听端口

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 开始进入程序开始服务

graph LR createServer -- this --> 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 应用)。

graph LR handle--next --> call--next --> 中间件 --next -->下一个中间件--next-->其他[...]

十一、handle 函数与 next 函数直接进行串联

从顶层的handle 函数开始,到最后一个中间件 handle 结束,中间通过 next 函数进行串联。next 函数将一个 handle 分成了两个半:

  • 一半在所有next 调用之前执行
  • 一半在所有next 调用之后执行

这么一个巧妙的函数串联模型。

graph LR A[handle] --> B(Next之前可能为空) --> D[下一个 handle] A[handle] --> AB(Next) --> D[下一个 handle] A[handle] <-.- C(Next之后可能为空) <-.- D[下一个 handle] D[handle] --> E(Next之前可能为空) --> G[下一个 handle] D[handle] --> Next --> G[下一个 handle] D[handle] <-.- F(Next之后可能为空) <-.- G[下一个 handle] G[下一个 handle] --> ...

十二、联想:与链表结构对比

虽然 JavaScript 中链表数据结构直接使用的非常少,但是其编程的思想无处不在。

graph LR A((头部)) --> B((节点1)) B --> C((节点2)) C --> D((节点3)) D --> E((尾部))

next 方法与链表非常相似,但是又不同, 链表往往守卫相连,但是 next 与handle之间是链接关系,next 可以将 handle 的运行时一分为二,将 handle 函数链接。

十三、小结

connect 是最容易理解的中间件模型。在监听之前,一个 use 方法调用,在 stack 中添加一个 layer, 监听之后请求开始进来,在 handle 中取出 stack 中 layer,在 next 机制下,将 layer 中的 handle 一分为二的串联起来,构成 connect。connect 简单精妙非常,适合学习。

相关推荐
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
北岛寒沫3 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy3 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
(⊙o⊙)~哦4 小时前
JavaScript substring() 方法
前端
无心使然云中漫步4 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者4 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢6 小时前
【Vue】VueRouter路由
前端·javascript·vue.js