中间件

中间件是 Koa 框架的核心概念之一,也是许多现代 Web 框架中广泛使用的设计模式。中间件的主要作用是对请求和响应进行预处理、后处理或拦截操作。

通过中间件机制,Koa提供了一个强大且灵活的方式来处理 HTTP 请求,使得开发者可以轻松地扩展和定制应用程序的行为。

基本概念

中间件是在请求和响应中间的处理程序,是一个功能独立的函数,它可以访问请求对象(request)、响应对象(response)和应用程序上下文(context)。

在 Koa 中,中间件是通过 app.use() 方法来使用的。每个中间件都是一个函数,通常是异步的,并且接受两个参数:

  • ctx:上下文对象,是context的简写,代表 http 请求的上下文

  • next:用于调用下一个中间件的函数

中间件的执行顺序

Koa的中间件按顺序执行,类似于一个栈结构。每个中间件可以选择在调用 await next() 之前或之后执行的代码。

通过调用 await next(),中间件将控制权交给下一个中间件,待其执行完毕后再返回当前中间件继续执行。

中间件的作用

  • 日志记录:记录每个请求的详细信息,如请求路径、方法、响应时间等。

  • 错误处理:捕获和处理请求过程中可能出现的错误,避免未处理的异常导致应用崩溃。

  • 请求解析:解析请求体中的数据,如 JSON、表单数据等。

  • 认证和授权:验证用户身份和权限,确保只有授权用户可以访问特定资源

  • 响应修改:在响应发送给客户端之前对其进行修改,如添加响应头、设置响应状态码等。

基本使用

1const Koa = require("koa")
2const app = new Koa()
3app.use(async (ctx, next) => {
4	console.log("我来组成头部")
5	next()
6})
7app.use(async (ctx, next) => {
8	console.log("我来组成身体")
9	next()
10})
11app.use(async (ctx, next) => {
12	console.log("我来组成尾部")
13	next()
14})
15app.use(async (ctx) => {
16	console.log("组装完成")
17	ctx.body = "Hello, Koa!"
18})
19app.listen(3000, () => {
20	console.log("Server running on http://localhost:3000")
21})

链式调用

app.use() 返回 this,因此可以链式调用。

1app.use(middleware1)
2app.use(middleware2)
3app.listen(3000)

它等同于

1app.use(middleware1)
2  .use(middleware2)
3  .listen(3000)

洋葱圈模型

在 Koa 中,中间件的执行顺序通常比喻为「洋葱圈」模型。这种模型形象的描述了中间件的执行顺序和流程。

先看一个 demo:

1const Koa = require("koa")
2const app = new Koa()
3
4// 中间件1
5app.use(async (ctx, next) => {
6	console.log("1")
7	next()
8	console.log("2")
9})
10
11// 中间件2
12app.use(async (ctx, next) => {
13	console.log("3")
14	next()
15	console.log("4")
16})
17
18app.listen(3000, () => {
19	console.log("Server running on http://localhost:3000")
20})

输出的结果是

1 3 4 5

在 Koa 中,中间件被 next() 方法分成了两部分。next() 方法上面部分的代码先执行,下面部分的代码会在后续中间件执行全部结束之后再执行,可以通过下图直观看出:

在洋葱模型中,每一层相当于一个中间件,用来处理特定的功能,比如错误处理、认证授权等等。其处理顺序先是 next()前的请求(request,从外层到内层)然后执行 next() 函数,最后是 next()后的响应(response,从内层到外层),也就是说每一个中间件都有两次处理时机。

异步处理

如果中间件中存在一些异步的代码,Koa也提供了统一的处理方式。

async / await

  • async 声明一个异步函数
  • await 后面跟一个 Promise 对象

如果要使用 await,需要在函数声明前加上 async