我希望我可以给你讲明白---中间件

本文详细介绍了Koa中间件的工作原理及其使用方法,包括不同类型的中间件写法、执行顺序及洋葱模型,并提供了JWT身份验证的中间件示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

Koa 的中间件就是函数,可以是 async 函数,或是普通函数,通过app.use来进行调用。

// async 函数
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

// 普通函数
app.use((ctx, next) => {
  const start = Date.now();
  return next().then(() => {
    const ms = Date.now() - start;
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
  });
});

express 中间件的写法

app.use((req,res,next) => {
  console.log('Time',Dtae.now());
  next(); // 传递request对象给下一个中间件
})

egg 框架的中间件和 Koa 的中间件写法是一模一样的,所以任何 Koa 的中间件都可以直接被使用。

const isJSON = require('koa-is-json');
const zlib = require('zlib');

async function gzip(ctx, next) {
  await next();

  // 后续中间件执行完成后将响应体转换成 gzip
  let body = ctx.body;
  if (!body) return;
  if (isJSON(body)) body = JSON.stringify(body);

  // 设置 gzip body,修正响应头
  const stream = zlib.createGzip();
  stream.end(body);
  ctx.body = stream;
  ctx.set('Content-Encoding', 'gzip');
}

中间件的执行顺序

app.use(async (ctx, next) => {
    console.log(1)
    await next();
    console.log(1)
});

app.use(async (ctx, next) => {
    console.log(2)
    next();
    console.log(2)
})

app.use(async (ctx, next) => {
    console.log(3)

})

在这里插入图片描述

中间件的执行顺序并不是从头到尾,而是类似于前端的事件流。事件流是先进行事件捕获,到达目标,然后进行事件冒泡。中间件的实现过程也是一样的,先从最外面的中间件开始执行,next() 后进入下一个中间件,一路执行到最里面的中间件,然后再从最里面的中间件开始往外执行。

Koa 中间件采用的是洋葱圈模型,每次执行下一个中间件传入两个参数 ctx 和 next,参数 ctx 是由 koa 传入的封装了 request 和 response 的变量,可以通过它访问 request 和 response,next 就是进入下一个要执行的中间件。

next的作用就像是一个占位符,或者说是执行器,遇到next就会去执行下一个函数。

比方说有三个函数,你希望执行完 console.log(1),然后执行fn2(),很简单嘛,你直接在 fn1() 函数里面调用 fn2() 不就解决了。

function fn1() {
  console.log(1)
  fn2();
}

function fn2() {
  console.log(2)
  fn3();
}

function fn3() {
  console.log(3)
}

fn1();

你希望执行 fn3() 函数之后,可以返回,那简单,直接执行 return 嘛

function fn1() {
  console.log(1)
  fn2()
  console.log(1)
}

function fn2() {
  console.log(2)
  fn3();
  console.log(2)
}

function fn3() {
  console.log(3)
  return;
}

fn1();

但是呢,这样的代码耦合性太高,将来代码要修改,那工作量,等待你的只有加班了。

我们希望将每个函数里面的调用其他的代码抽取出来,在外部调用。将函数包裹起来,其实也就是将函数传给另一个函数。

async function fn1(next) {
  console.log(1);
  await next();
  console.log(1);
}

async function fn2(next) {
  console.log(2);
  await next();
  console.log(2);
}

async function fn3() {
  console.log(3);
}

let next1 = async function () {
  await fn2(next2);
}

let next2 = async function() {
  await fn3();
}
fn1(next1);

之前其实我们一只都在干一件事,就是触发所有函数的执行。那怎么触发所有函数的执行?假设有一个函数数组,那么遍历这个数组,依次执行,不就达到了我们的想法。

第一步:收集函数

const middlewares = [fn1, fn2, fn3];

第二步:传参

function compose(middleware, next) {
  return async function() {
    await middleware(next);
  }
}

第三步:循环传参

定义了一个next,来保存上一个已经接受next的函数。

let next;
for (let i = middlewares.length - 1; i >= 0; i--) {
   next = compose(middlewares[i], next);
}

大概类似的效果

next = async function(){
  await fn1(async function() {
    await fn2(async function() {
      await fn3(async function(){
        return Promise.resolve();
      });
    });
  });
};

完整代码:

const middlewares = [fn1, fn2, fn3];

function compose(middleware, next) {
  return async function () {
    await middleware(next);
  }
}

let next;
for (let i = middlewares.length - 1; i >= 0; i--) {
  next = compose(middlewares[i], next);
}

next();

koa 中使用 use 来收集 fn

constructor() {
  this.middlewares = [];
}

use(fn) {
  this.middlewares.push(fn)
}

编写属于自己的中间件

前后端分离开发,我们常采用 JWT 来进行身份验证,其中 token 一般放在 HTTP 请求中的 Header Authorization 字段中,每次请求后端都要进行校验,Koa 通过编写中间件来实现 token 验证。

const Router = require('koa-router')
// koa-jwt 就是已经封装好的中间件
const jwt = require('koa-jwt')

const auth = jwt({secret})

// 当请求 /center 路由时,需要经过 auth 的验证,验证通过之后,才可以进行下一步处理
router.post('/center', auth, create)

module.exports = router

token.js

// token 中间件
module.exports = (options) => async (ctx, next) {
  // 获取 token
  const token = ctx.header.authorization
  if (verify(token)) {
    // 进入下一个中间件
    await next()
  }
}

app.js

const Koa = require('koa')
const app = new Koa()
const token = require('./token')

app.use(async (ctx, next) => {
  // ctx 是封装了 request 和 response 的上下文
  ctx.body = 'hello koa'
  await next()
  // next 是下一个中间件
})

app.use(token())

app.listen(3000)

可以使用多个中间件

const Koa = require('koa')
const app = new Koa()
const token = require('./token')
const logger = require('./log')
const time = require('./time')

app.use(async (ctx, next) => {
  // ctx 是封装了 request 和 response 的上下文
  ctx.body = 'hello koa'
  await next()
  // next 是下一个中间件
})

app.use(token())

app.use(time())

app.use(logger())

app.listen(3000)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值