深入理解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)
  }
}
相关推荐
有梦想的攻城狮21 分钟前
spring中的BeanFactoryAware接口详解
java·后端·spring·beanfactory
liubo666_32 分钟前
JVM梳理(逻辑清晰)
java·jvm·后端
PWRJOY1 小时前
Flask 路由跳转机制:url_for生成动态URL、redirect页面重定向
后端·python·flask
dog shit2 小时前
web第六次课后作业--使用ApiFox实现请求响应操作
后端
waterHBO5 小时前
人脸识别,使用 deepface + api + flask, 改写 + 调试
后端·python·flask
Asus.Blogs5 小时前
Go 语言中的 Struct Tag 的用法详解
开发语言·后端·golang
码农爱java7 小时前
Java 调用 GitLab API
java·开发语言·后端·gitlab·gitapi
小徐Chao努力7 小时前
【项目】SpringBoot +MybatisPlus集成多数据源
java·spring boot·后端·mybatis
神秘的t7 小时前
Spring Web MVC————入门(3)
前端·后端·spring·mvc
毕小宝8 小时前
SpringBoot微服务编写Dockerfile流程及问题汇总
spring boot·后端·docker·微服务