中间件

中间件

中间件 (Middleware),又称中介层,是提供系统软件和应用软件之间连接的软件,以便于软件各部件之间的沟通,特别是应用软件对于系统软件的集中的逻辑。中间件在企业架构中表示各种软件套件,有助于抽象底层机制,比如操作系统 API、网络通信、内存管理等,开发者只需要关注应用中的业务模块。

从更广义的角度来看,中间件也可以定义为链接底层服务和应用的软件层。后文我们主要使用 Node.js 里最近很热门的框架 Koa2 里的中间件概念为例,并且自己实现一个中间件来加深理解。

1. 什么是中间件

  • ExpressKoa2 中,中间件代表一系列以管道形式被连接起来,以处理 HTTP 请求和响应的函数。换句话说,中间件其实就是一个函数,一个执行特定逻辑的函数。前端中类似的概念还有拦截器、Vue 中的过滤器、vue-router 中的路由守卫等。
  • 工作原理就是进入具体业务之前,先对其进行预处理(在这一点上有点类似于装饰器模式),或者在进行业务之后,对其进行后处理。

示意图如下:

image

当接受到一个请求,对这个请求的处理过程可以看作是一个串联的管道,比如对于每个请求,我们都想插入一些相同的逻辑比如权限验证、数据过滤、日志统计、参数验证、异常处理等功能。对于开发者而言,自然不希望对于每个请求都特殊处理,因此引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,让开发者能够关注在业务的开发上,以达到提升开发效率的目的。

2. Koa 里的中间件 2.1 Koa2 里的中间件使用

Koa2 中的中间件形式为:

app.use(async function middleware(context, next){ 
    // ... 前处理
    await next()     // 下一个中间件
    // ... 后处理
})

  • 其中第一个参数 context 作为上下文封装了 requestresponse 信息,我们可以通过它来访问 requestresponsenext 是下一个中间件,当一个中间件处理完毕,调用 next() 就可以执行下一个中间件,下一个中间件处理完- 再使用 next(),从而实现中间件的管道化,对消息的依次处理。

一般中间件模式都约定有个 use 方法来注册中间件,Koa2 也是如此。千言万语不及一行代码,这里写一个简单的中间件:

const koa = require('koa')
const app = new koa()

app.use((ctx, next) => {      // 没错,这就是中间件
    console.log('in 中间件1')
})

app.listen(10001)

// 输出: in 中间件1

Koa2 中的中间件有多种类型:

  • 应用级中间件;
  • 路由级中间件;
  • 错误处理中间件;
  • 第三方中间件;

除了使用第三方中间件比如 koa-routerkoa-bodyparserkoa-statickoa-logger 等提供一些通用的路由、序列化、反序列化、日志记录等功能外,我们还可以编写自己的应用级中间件,来完成业务相关的逻辑。

通过引入各种功能各异的中间件,可以完成很多业务相关的功能:

  • equestresponse 的解析和处理;
  • 生成访问日志;
  • 管理 sessioncookie 等;
  • 提供网络安全防护;

2.2 洋葱模型

在使用多个中间件时,引用一张著名的洋葱模型图:

image

正如上面的洋葱图所示,请求在进入业务逻辑时,会依次经过一系列中间件,对数据进行有序处理,业务逻辑之后,又像栈的先入后出一样,倒序经过之前的中间件。洋葱模型允许当应用执行完主要逻辑之后进行一些后处理,再将响应返回给用户。

使用如下:

const Koa = require('koa')
const app = new Koa()

// 中间件1
app.use(async (ctx, next) => {
    console.log('in 中间件1')
    await next()
    console.log('out 中间件1')
})

// 中间件2
app.use(async (ctx, next) => {
    console.log('in 中间件2')
    await next()
    console.log('out 中间件2')
})

// response
app.use(async ctx => { ctx.body = 'Hello World' })

app.listen(10001)
console.log('app started at port http://localhost:10001')

// 输出:  in  中间件1
// 输出:  in  中间件2
// 输出:  out 中间件2
// 输出:  out 中间件1

我们可以引入 setTimeout 来模拟异步请求的过程:

const Koa = require('koa')
const app = new Koa()

// 中间件1
app.use(async (ctx, next) => {
    console.log('in 中间件1')
    await next()
    console.log('out 中间件1')
})

// 中间件2
app.use(async (ctx, next) => {
    console.log('in 中间件2')
    await new Promise((resolve, reject) => {
          ctx.zjj_start2 = Date.now()
          setTimeout(() => resolve(), 1000 + Math.random() * 1000)
      }
    )
    await next()
    const duration = Date.now() - ctx.zjj_start2
    console.log('out 中间件2 耗时:' + duration + 'ms')
})

// 中间件3
app.use(async (ctx, next) => {
    console.log('in 中间件3')
    await new Promise((resolve, reject) => {
          ctx.zjj_start3 = Date.now()
          setTimeout(() => resolve(), 1000 + Math.random() * 1000)
      }
    )
    await next()
    const duration = Date.now() - ctx.zjj_start3
    console.log('out 中间件3 耗时:' + duration + 'ms')
})

// response
app.use(async ctx => {
    console.log(' ... 业务逻辑处理过程 ... ')
})

app.listen(10001)
console.log('app started at port http://localhost:10001')

在使用多个中间件时,特别是存在异步的场景,一般要 await 来调用 next 来保证在异步场景中,中间件仍按照洋葱模型的顺序来执行,因此别忘了 next 也要通过 await 调用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容