深入理解Koa:中间件模型、API全览和与Express的对比(中)

  1. 中间件示例代码讲解
  • 这段Koa代码使用三个中间件,分别用于记录请求的URL和响应时间,计算请求处理时间,以及设置实际的响应体。
  • 中间件是按照顺序执行的函数,每个中间件可以访问请求对象(ctx.request)和响应对象(ctx.response)
ts 复制代码
import Koa from 'koa'
const app = new Koa()

// 中间件1,next是一个函数,用于调用下一个中间件。在这个中间件中,它等待下一个中间件执行完成,然后记录请求的URL和响应时间。
app.use(async (ctx, next) => {
  await next();
  const time = ctx.response.get('X-Response-Time');
  console.log(`${ctx.url} - ${time}`);
});

// 中间件2:它记录请求处理开始的时间(start),然后调用下一个中间件。当下一个中间件执行完成后,计算请求处理时间,并将其设置为响应头的X-Response-Time字段。
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const time = Date.now() - start; 
  ctx.set('X-Response-Time', `${time}ms`);
});

// 这是最后一个中间件,它设置响应体为字符串'Hello World'。在这个例子中,这是最终的响应。
app.use(async ctx => {
  ctx.body = 'Hello World';
//   await next()
});

app.listen(3000)
  • 代码运行顺序如图
  • await next()是什么意思
    • next()表示进入下一个函数
    • 下一个函数会返回一个Promise对象,称为p
    • 下一个函数所有代码执行完毕后,将p置为成功
    • await会等待p成功后,再回头执行剩余的代码
    • 上图中1后面的await next()会等待4执行完毕
    • 2后面的await next()会等待3执行完毕
js 复制代码
app.use(async (ctx, next) => {
  const start = Date.now();
  await next(); // 等待 下一个中间件()
  const time = Date.now() - start; 
  ctx.set('X-Response-Time', `${time}ms`);
});
  • await next()改写成Promise
js 复制代码
app.use(async (ctx,next)=> {
  const start = Date.now();
  return next().then(()=> {
    const time = Date.now() - start;
    ctx.set('X-Response-Time',${time}ms)
  });
}
  • 一定要写return,因为中间件必返回Promise对象
  • 错误处理在这里写有点反模式,用app.on('error')更方便一点
  1. express如何实现相同的计时功能
  • 思路一:用两个app.use加res.locals
js 复制代码
const express = require('express');
const app = express();

// 中间件1
app.use((req, res, next) => {
  res.locals.start = new Date()
  next()
});


// 中间件2
app.get('/', (req, res,next) => {
  for (let i = 0; i < 10000; i++) {
    res.write(i.toString())
  }
  res.write('Hello World');
  res.end()
  next()
});

// 中间件3
app.use((req, res, next) => {
  const time = new Date().valueOf() - res.locals.start
  console.log(`time: ${time}`)
  next();
});


// 启动应用
app.listen(3001, () => {
  console.log('Server listening on port 3001');
});
  • 思路二: 搜索业界方案response-time
js 复制代码
const express = require('express');
const app = express();
const responseTime = require('response-time')

app.use(responseTime())

app.get('/', (req, res,next) => {
  for (let i = 0; i < 10000; i++) {
    res.write(i.toString())
  }
  res.write('Hello World');
  res.end()
  next()
});

// 这里只是打印一下时间
app.use((req, res, next) => {
  console.log(res)
  console.log(res.get('X-Response-Time'))
  next()
});

// 启动应用
app.listen(3001, () => {
  console.log('Server listening on port 3001');
});
  • 通过阅读response-time的源码,我们可以发现它用了on-headers的库
js 复制代码
var onHeaders = require('on-headers')

return function responseTime (req, res, next) {
    var startAt = process.hrtime()
    // 监听了开始写header,只要开始write header就认为任务结束了
    onHeaders(res, function onHeaders () {
      var diff = process.hrtime(startAt)
      var time = diff[0] * 1e3 + diff[1] * 1e-6

      fn(req, res, time)
    })

    next()
  }
  • 查看on-headers库的源码
    • 它的作用会在一个响应即将写入头部时执行监听器
    • 它的实现是改写了res.writeHead,创建一个新的 writeHead 函数(可以理解为一个假的writeHeader),中间实现了监听的逻辑
    • 最终它还是会使用之前原始的writeHeader,
    • 可以理解成面向切面编程,关注点是在调用 writeHead 函数时执行的一些逻辑,
    • 而这个关注点被分离到了 listener 函数中。通过创建一个新的 writeHead 函数,
    • 代码确保在执行 listener 之后仍能够调用原始的 writeHead 函数。
    • 这种模式允许在不直接修改主业务逻辑的情况下,插入额外的逻辑。
js 复制代码
function createWriteHead (prevWriteHead, listener) {
  var fired = false

  // return function with core name and argument list
  return function writeHead (statusCode) {
    // set headers from arguments
    var args = setWriteHeadHeaders.apply(this, arguments)

    // fire listener
    if (!fired) {
      fired = true
      listener.call(this)

      // pass-along an updated status code
      if (typeof args[0] === 'number' && this.statusCode !== args[0]) {
        args[0] = this.statusCode
        args.length = 1
      }
    }

    return prevWriteHead.apply( , args)
  }
}
相关推荐
她说彩礼65万2 分钟前
Asp.net core appsettings.json` 和 `appsettings.Development.json`文件区别
后端·json·asp.net
用户21411832636027 分钟前
国产化算力实战:手把手教你在魔乐社区用华为昇腾 NPU 跑通模型推理
后端
IT_陈寒7 分钟前
SpringBoot性能飞跃:5个关键优化让你的应用吞吐量提升300%
前端·人工智能·后端
M1A110 分钟前
你的认知模式,决定了你的人生高度
后端
追逐时光者1 小时前
Everything替代工具,一款基于 .NET 开源免费、高效且用户友好文件搜索工具!
后端·.net
QX_hao1 小时前
【Go】--数据类型
开发语言·后端·golang
桦说编程1 小时前
线程池拒绝策略避坑:谨慎使用抛弃策略,可能导致系统卡死
java·后端
BingoGo2 小时前
PHP 15 个高效开发的小技巧
后端·php
锥栗2 小时前
【Redis】【缓存】理解缓存三大问题:缓存穿透、缓存击穿与缓存雪崩及解决方案
java·后端·面试
9号达人2 小时前
泛型+函数式:让策略模式不再是复制粘贴地狱
java·后端·面试