likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 12

一、简介

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

二、简单用法

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

2.1)安装

pnpm add connect

2.2)简单示例

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 参数服务。

http.createServer(app) 

createServer 需要传递一个函数,函数参数是 req 请求对象和 res 响应对象。

4.2)app 的实现

function app(req, res, next){ app.handle(req, res, next); }

connect 实现的 app 函数对象,参数中包含 next 方法,虽然开始的时候为空。

4.3)app 的增强

merge(app, proto); // 合并自己的原型
merge(app, EventEmitter.prototype); // 合并时间触发
app.route = '/'; // 添加根路由
app.stack = []; // 添加栈

4.4)原型三方法:use/handle/listen

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 中添加一):

this.stack.push({ route: path, handle: handle }); // push 的一个 layer

这个 layer 栈非常重要,后面 handle 与 next 串联一起时访问就是 stack 中的 layer 中保存的数据(route 和 handle)。

六、listen 开始监听

代码写完了,需要监听服务才能接收外部的请求

app.listen(3000, () => {
    console.log("server on:http://localhost:3000")
})
  • 可以配置自己的监听端口和 host 地址,配置监听回调函数。此从开始就可以接收外部服务了。
proto.listen = function listen() {
  var server = http.createServer(this); 
  return server.listen.apply(server, arguments);
};

注意: this 时运行时绑定,this 运行时指向 app 对象函数。

七、从 app.handle 开始进入程序开始服务

this
createServer
app.handle
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保存在栈中的层
layeruse 的调用之后产生的层
next顶层 next 方法,会被传递到每一个 handle 中
call next内部真实的 invoker (召唤者)
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 函数,实现如下

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 应用)。

next
next
next
next
handle
call
中间件
下一个中间件
...

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

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

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

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

handle
Next之前可能为空
handle
Next
Next之后可能为空
Next之前可能为空
下一个 handle
Next
Next之后可能为空
...

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

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

头部
节点1
节点2
节点3
尾部

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

十三、小结

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