中间件

中间件

我们本章来探讨一下 Koa.js 当中的中间件,在使用 Koa.js 的过程当中,经常会接触到中间件这个概念,之前在学习 Redux 的过程当中,里面也有一个中间件(middleware)的概念,所以就打算抽点时间,整理一下中间件的相关概念,关于 Redux 当中的 middleware 会另起篇幅来进行介绍

什么是中间件

中间件是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的

上面是 维基百科 当中对于中间件的解释,看上去可能比较抽象,其实简单的总结来说,所谓中间件,就是处在服务业务与用户应用中间的软件(架构),主要用来将具体业务和底层逻辑解耦的组件

在深入展开之前,我们先来看看中间件的洋葱模型

中间件的洋葱模型

关于洋葱模型,也不说复杂了,直接通过几个例子来了解一下到底什么是中间件的洋葱模型,先以 express 为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var express = require('express')
var app = express()

app.use(function middleware1(req, res, next) {
console.log('A middleware1 开始')
next()
console.log('B middleware1 结束')
})

app.use(function middleware2(req, res, next) {
console.log('C middleware2 开始')
next()
console.log('D middleware2 结束')
})

app.use(function middleware3(req, res, next) {
console.log('E middleware3 开始')
next()
console.log('F middleware3 结束')
})

app.get('/', function handler(req, res) {
res.send('ok')
console.log('======= G =======')
})

if (module.parent) {
module.exports = app
} else {
app.listen(8080)
}

// =============================================

// 控制台输出结果为

A middleware1 开始
C middleware2 开始
E middleware3 开始
======= G =======
F middleware3 结束
D middleware2 结束
B middleware1 结束

运行结果的示意图如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
            --------------------------------------
| middleware1 |
| ---------------------------- |
| | middleware2 | |
| | ------------------- | |
| | | middleware3 | | |
| | | | | |
next next next ——————————— | | |
请求 ——————————————————> | handler | — 收尾工作-> |
响应 <————————————————— | G | | | |
| A | C | E ——————————— F | D | B |
| | | | | |
| | ------------------- | |
| ---------------------------- |
--------------------------------------


顺序 A -> C -> E -> G -> F -> D -> B
\---------------/ \----------/
↓ ↓
请求响应完毕 收尾工作

Redux 的里面也有一个中间件(middleware)的概念,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
function middleware1(store) {
return function (next) {
return function (action) {
console.log('A middleware1 开始')
next(action)
console.log('B middleware1 结束')
};
};
}

function middleware2(store) {
return function (next) {
return function (action) {
console.log('C middleware2 开始')
next(action)
console.log('D middleware2 结束')
};
};
}

function middleware3(store) {
return function (next) {
return function (action) {
console.log('E middleware3 开始')
next(action)
console.log('F middleware3 结束')
}
}
}

function reducer(state, action) {
if (action.type === 'MIDDLEWARE_TEST') {
console.log('======= G =======')
}
return {}
}

var store = Redux.createStore(
reducer,
Redux.applyMiddleware(
middleware1,
middleware2,
middleware3
)
)

store.dispatch({ type: 'MIDDLEWARE_TEST' })

运行结果的示意图如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
            --------------------------------------
| middleware1 |
| ---------------------------- |
| | middleware2 | |
| | ------------------- | |
| | | middleware3 | | |
| | | | | |
next next next ——————————— | | |
dispatch —————————————> | reducer | — 收尾工作->|
nextState <————————————— | G | | | |
| A | C | E ——————————— F | D | B |
| | | | | |
| | ------------------- | |
| ---------------------------- |
--------------------------------------


顺序 A -> C -> E -> G -> F -> D -> B
\---------------/ \----------/
↓ ↓
更新 state 完毕 收尾工作

当然,在 Koa.js 当中也是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const Koa = require('koa')
let app = new Koa()

const middleware1 = async (ctx, next) => {
console.log('A middleware1 开始')
await next()
console.log('B middleware1 结束')
}

const middleware2 = async (ctx, next) => {
console.log('C middleware2 开始')
await next()
console.log('D middleware2 结束')
}

const middleware3 = async (ctx, next) => {
console.log('E middleware3 开始')
await next()
console.log('F middleware3 结束')
}

app.use(middleware1)
app.use(middleware2)
app.use(middleware3)

app.use(async(ctx, next) => {
ctx.body = 'hello world'
console.log('======= G =======')
})

app.listen(3000)

// A middleware1 开始
// C middleware2 开始
// E middleware3 开始
// ======= G =======
// F middleware3 结束
// D middleware2 结束
// B middleware1 结束

可以发现,结果是一致的,至于为什么会出现以上的结果,这也就是本篇文章需要说到的地方,在 Koa.js 当中,是通过一个中间件引擎 koa-compose 模块来实现的,也就是 Koa.js 实现洋葱模型的核心引擎

Koa.js 的切面

Koa.js 的切面是由中间件机制实现的,一个中间件一般有两个切面,遵循先进后出的切面执行顺序,类似入栈出栈的顺序,可以参考下图

中间件原理

通过之前的洋葱模型可以看出,中间件的在 await next() 前后的操作,很像数据结构的一种场景,就是先进后出的『栈』,同时又有统一上下文管理操作数据,在 Koa.js 当中最为人所知的便是基于『洋葱模型』的 HTTP 中间件处理流程

Koa.js 当中的洋葱模式可以拆解成以下几个元素

  • 生命周期
  • 中间件
  • 中间件在生命周期中
    • 前置操作
    • 等待其他中间件操作
    • 后置操作

所以综上所述,我们也可以总结出一下中间件的相关特性

  • 有统一的 Context
  • 操作先进后出(栈数据结构)
  • 有控制先进后出的机制的 next() 方法
  • 有提前结束机制

这样子我们可以单纯用 Promise 做个简单的实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
let context = {
data: []
}

async function middleware1(ctx, next) {
console.log('A middleware1 开始')
ctx.data.push(1)
await next()
console.log('B middleware1 结束')
ctx.data.push(6)
}

async function middleware2(ctx, next) {
console.log('C middleware2 开始')
ctx.data.push(2)
await next()
console.log('D middleware2 结束')
ctx.data.push(5)
}

async function middleware3(ctx, next) {
console.log('E middleware3 开始')
ctx.data.push(3)
await next()
console.log('F middleware3 结束')
ctx.data.push(4)
}

Promise.resolve(middleware1(context, async () => {
return Promise.resolve(middleware2(context, async () => {
return Promise.resolve(middleware3(context, async () => {
console.log('======= G =======')
return Promise.resolve()
}));
}));
}))
.then(() => {
console.log('end')
console.log('context = ', context)
})

// A middleware1 开始
// C middleware2 开始
// E middleware3 开始
// ======= G =======
// F middleware3 结束
// D middleware2 结束
// B middleware1 结束
// end
// context = { data: [ 1, 2, 3, 4, 5, 6 ] }

我们可以简单的梳理一下上述示例的执行流程

  • 生命周期就是 Promise.resolve 的嵌套
  • 中间件就是 middleware1middleware2middleware3
  • 中间件在生命周期中,就是 Promise.resolve(middleware) 嵌套中执行中间件
    • middleware1 前置操作是 A
    • 等待嵌套的 middleware2
      • middleware2 前置操作是 C
      • 等待嵌套的 middleware3
        • middleware3 前置操作是 E
        • middleware3 前置操作是 F
      • middleware2 后置操作是 D
    • middleware1 后置操作是 B

实现

通过上一节中的中间件原理,我们可以看出,单纯的使用 Promise 嵌套是可以直接实现中间件流程的,虽然可以实现,但是 Promise 嵌套会产生代码的可读性和可维护性的问题,也带来了中间件扩展问题,所以需要把 Promise 嵌套实现的中间件方式进行高度抽象,达到可以自定义中间件的层数,在这种情况下,我们就可以采用 async/await 来进行实现,我们先来理清实现过程当中需要的步骤

  • 中间件队列
  • 处理中间件队列,并将上下文 Context 传进去
  • 中间件的流程控制器 next()
  • 异常处理

根据上一节的中间的原理,我们可以抽象出

  • 每一个中间件需要封装一个 Promise
  • 利用洋葱模型的先进后出操作,对应 promise.resolve() 的前后操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function compose(middleware) {

if (!Array.isArray(middleware)) {
throw new TypeError(`Middleware stack must be an array!`)
}

return function (ctx, next) {
let index = -1

return dispatch(0)

function dispatch(i) {
if (i < index) {
return Promise.reject(new Error(`next() called multiple times.`));
}

index = i

let fn = middleware[i]

if (i === middleware.length) {
fn = next
}

if (!fn) {
return Promise.resolve()
}

try {
return Promise.resolve(fn(ctx, _ => {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}

下面就让我们来简单是试用一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
let middleware = []
let context = {
data: []
}

middleware.push(async (ctx, next) => {
console.log('A middleware1 开始')
ctx.data.push(1)
await next()
console.log('B middleware1 结束')
ctx.data.push(6)
})

middleware.push(async (ctx, next) => {
console.log('C middleware2 开始')
ctx.data.push(2)
await next()
console.log('D middleware2 结束')
ctx.data.push(5)
})

middleware.push(async (ctx, next) => {
console.log('E middleware3 开始')
ctx.data.push(3)
await next()
console.log('F middleware3 结束')
ctx.data.push(4)
})

const fn = compose(middleware)

fn(context)
.then(_ => {
console.log('end')
console.log('context = ', context)
})

// A middleware1 开始
// C middleware2 开始
// E middleware3 开始
// F middleware3 结束
// D middleware2 结束
// B middleware1 结束
// end
// context = { data: [ 1, 2, 3, 4, 5, 6 ] }

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×