KoaJS

简介: koa致力于成为一个更小、更富有表现力、更健壮的、更轻量的web开发框架。因为它所有功能都通过插件实现,这种插拔式的架构设计模式,很符合unix哲学。

前言
koa致力于成为一个更小、更富有表现力、更健壮的、更轻量的web开发框架。因为它所有功能都通过插件实现,这种插拔式的架构设计模式,很符合unix哲学。
一个简单的服务,如下:
const Koa = require('koa')
let app = new Koa()
app.use((ctx, next) => {

console.log(ctx) 

})
app.listen(4000)
复制代码
然后在浏览器端打开http://127.0.0.1:4000即可访问
若没有指定返回body,koa默认处理成了Not Found
本文内容:

中间件原理(结合代码)

原理
中间件实现思路
理解上述洋葱模型

阅读源码

app.listen()
ctx挂载内容

context.js
request.js
response.js
挂载ctx

next构建的洋葱模型

app.use((ctx, next) =< { ... })
中间件含异步代码如何保证正确执行
返回报文
解决多次调用next导致混乱问题

基于事件驱动去处理异常

koa2, koa1 和 express区别

一、中间件原理(结合代码)
原理

中间件执行就像穿越洋葱一样,最早use的中间件,就放在最外层。处理顺序横穿洋葱,从左到右,左边接收一个request,右边输出返回response;
一般的中间件都会执行两次,调用next之前为第一次,调用next时把控制传递给下游的下一个中间件。当下游不再有中间件或者没有执行next函数时,就将依次恢复上游中间件的行为,让上游中间件执行next之后的代码;

如下代码:
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {

console.log(1)
next()
console.log(3)

})
app.use((ctx) => {

console.log(2)

})
app.listen(9001)

执行结果是1=>2=>3

复制代码
中间件实现思路

注意其中的compose函数,这个函数是实现中间件洋葱模型的关键

// 场景模拟
// 异步 promise 模拟
const delay = async () => {
return new Promise((resolve, reject) => {

setTimeout(() => {
  console.log('delay 2000ms')
  resolve();
}, 2000);

});
}
// 中间间模拟
const fn1 = async (ctx, next) => {
console.log(1);
await next();
console.log(2);
}
const fn2 = async (ctx, next) => {
console.log(3);
await delay();
await next();
console.log(4);
}
const fn3 = async (ctx, next) => {
console.log(5);
}

const middlewares = [fn1, fn2, fn3];

// compose 实现洋葱模型
const compose = (middlewares, ctx) => {
const dispatch = (i) => {

let fn = middlewares[i];
if(!fn){ return Promise.resolve() }
return Promise.resolve(fn(ctx, () => {
  return dispatch(i+1);
}));

}
return dispatch(0);
}

compose(middlewares, 1);
复制代码
理解上述洋葱模型
const fn1 = async (ctx, next) => {

console.log(1); 

const fn2 = async (ctx, next) => { 
    console.log(3); 
    await delay(); 

    const fn3 = async (ctx, next) => { 
        console.log(5); 
    }

    console.log(4); 
}

console.log(2); 

}

1 3 5 4 2

复制代码
看完这个,大概了解koa的中间件原理了吧。
接下来,咱们一起看下源码。
二、阅读源码

核心文件四个

application.js:简单封装http.createServer()并整合context.js
application.js是koa的入口文件,它向外导出了创建class实例的构造函数,
它继承了events,这样就会赋予框架事件监听和事件触发的能力。
application还暴露了一些常用的api,比如toJSON、listen、use等等。

listen的实现原理其实就是对http.createServer进行了一个封装,
重点是这个函数中传入的callback,
它里面包含了中间件的合并,上下文的处理,对res的特殊处理。

use是收集中间件,将多个中间件放入一个缓存队列中,
然后通过koa-compose这个插件进行递归组合调用这一些列的中间件。
复制代码

context.js:代理并整合request.js和response.js
request.js:基于原生req封装的更好用
response.js:基于原生res封装的更好用

koa是用ES6实现的,主要是两个核心方法app.listen()和app.use((ctx, next) => { ... })

  1. app.listen()

在application.js中实现 app.listen()
handleRequest()

application.js

const http = require('http')
class Koa {
  constructor () {
    // ...
  }  
    // 处理用户请求
  handleRequest (req, res) {
    // req & res nodejs native
    // ...
  }  
  listen (...args) {
    let server = http.createServer(this.handleRequest.bind(this))
    server.listen(...args)
  }  
}
module.exports = Koa

复制代码

  1. ctx挂载内容

ctx = {}
ctx.request = {}
ctx.response = {}
ctx.req = ctx.request.req = req
ctx.res = ctx.response.res = res
ctx.xxx = ctx.request.xxx
ctx.yyy = ctx.response.yyy
复制代码
我们需要以上几个对象,最终都代理到ctx对象上。
创建context.js/request.js/response.js三个文件

2.1 request.js内容

request.js

const url = require('url')
let request = {}
module.exports = request
复制代码
在request.js中,使用ES5提供的属性访问器实现封装

request.js

const url = require('url')
let request = {
get url () {

return this.req.url // 此时的this为调用的对象 ctx.request

},
get path () {

let { pathname } = url.parse(this.req.url)
return pathname

},
get query () {

let { query } = url.parse(this.req.url, true)
return query

}
// ...更多待完善
}
module.exports = request
复制代码
以上实现了封装request并代理到ctx上

2.2 response.js内容

response.js

let response = {}
module.exports = response
复制代码
在response.js中,使用ES5提供的属性访问器实现封装

response.js

let response = {
set body (val) {

this._body = val

},
get body () {

return this._body // 此时的this为调用的对象 ctx.response

}
// ...更多待完善
}
module.exports = response
复制代码
以上实现了封装response并代理到ctx上

2.3 context.js内容

context.js 初始化

let context = {}

module.exports = context
复制代码
在context.js中,使用__defineGetter__ / __defineSetter__实现代理,他是Object.defineProperty()方法的变种,可以单独设置get/set,不会覆盖设置。

context.js

let context = {}
// 定义获取器
function defineGetter (key, property) {
context.__defineGetter__ (property, function () {

return this[key][property]

})
}
// 定义设置器
function defineSetter (key, property) {
context.__defineSetter__ (property, function (val) {

this[key][property] = val

})
}

// 🌰
// 代理 request
defineGetter('request', 'path')
defineGetter('request', 'url')
defineGetter('request', 'query')
// 代理 response
defineGetter('response', 'body')
defineSetter('response', 'body')
module.exports = context
复制代码

2.4 application.js 挂载ctx
在application.js中引入上面三个文件并放到实例上
const context = require('./context')
const request = require('./request')
const response = require('./response')
class Koa extends Emitter{
constructor () {

super()
// Object.create 切断原型链, 深拷贝配置
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)

}
}
复制代码
然后处理用户请求并在ctx上代理request / response
createContext()
# application.js

// 创建上下文
createContext (req, res) {

let ctx = this.context
// 请求
ctx.request = this.request
ctx.req = ctx.request.req = req
// 响应
ctx.response = this.response
ctx.res = ctx.response.res = res
return ctx

}

// server connection 回调
handleRequest (req, res) {

let ctx = this.createContext(req, res)
return ctx

}
复制代码

哎嘿,有个req还有个request,两个一样吗?

console.log('native req ----') // node原生的req
console.log(ctx.req.url)
console.log(ctx.request.req.url)
console.log('koa request ----') // koa封装了request
console.log(ctx.url)
console.log(ctx.request.url)
复制代码

  1. next构建的洋葱模型

接下来实现koa中第二个方法app.use((ctx, next) =< { ... })
app.use((ctx, next) =< { ... })
use中存放着一个个中间件,如cookie、session、static...等等一堆处理函数,并且以洋葱式的形式执行。
# application.js

constructor () {

// ...
// 存放中间件数组
this.middlewares = []

}
// 使用中间件
use (fn) {

this.middlewares.push(fn)

}
复制代码
当处理用户请求时,期望执行所注册的一堆中间件
compose、dispatch
# application.js

// 组合中间件
compose (middlewares, ctx) {

function dispatch (index) {
  // 迭代终止条件 取完中间件
  // 然后返回成功的promise
  if (index === middlewares.length) return Promise.resolve()
  let middleware = middlewares[index]
  // 让第一个函数执行完,如果有异步的话,需要看看有没有await
  // 必须返回一个promise
  return Promise.resolve(middleware(ctx, () => dispatch(index + 1)))
}
return dispatch(0)

}

// 处理用户请求
handleRequest (req, res) {

let ctx = this.createContext(req, res)

this.compose(this.middlewares, ctx)

return ctx

}
复制代码
以上的dispatch迭代函数在很多地方都有运用,比如递归删除目录,也是koa的核心。
中间件含异步代码如何保证正确执行
返回的promise主要是为了处理中间件中含有异步代码的情况
返回报文
在所有中间件执行完毕后,需要渲染页面。
# application.js

// 处理用户请求
handleRequest (req, res) {

let ctx = this.createContext(req, res)

res.statusCode = 404 // 默认404 当设置body再做修改

let ret = this.compose(this.middlewares, ctx)

ret.then(_ => {
  if (!ctx.body) { // 没设置body
    res.end(`Not Found`)
  } else if (ctx.body instanceof Stream) { // 流
    res.setHeader('Content-Type', 'text/html;charset=utf-8')
    
    ctx.body.pipe(res)
  } else if (typeof ctx.body === 'object') { // 对象
    res.setHeader('Content-Type', 'text/josn;charset=utf-8')
    
    res.end(JSON.stringify(ctx.body))
  } else { // 字符串
    res.setHeader('Content-Type', 'text/html;charset=utf-8')
    
    res.end(ctx.body)
  }
})
return ctx

}
复制代码
需要考虑多种情况做兼容。

相关文章
|
8天前
|
数据采集 人工智能 安全
|
4天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
298 164
|
3天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
307 155
|
11天前
|
SQL 自然语言处理 调度
Agent Skills 的一次工程实践
**本文采用 Agent Skills 实现整体智能体**,开发框架采用 AgentScope,模型使用 **qwen3-max**。Agent Skills 是 Anthropic 新推出的一种有别于mcp server的一种开发方式,用于为 AI **引入可共享的专业技能**。经验封装到**可发现、可复用的能力单元**中,每个技能以文件夹形式存在,包含特定任务的指导性说明(SKILL.md 文件)、脚本代码和资源等 。大模型可以根据需要动态加载这些技能,从而扩展自身的功能。目前不少国内外的一些框架也开始支持此种的开发方式,详细介绍如下。
846 6
|
5天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:六十九、Bootstrap采样在大模型评估中的应用:从置信区间到模型稳定性
Bootstrap采样是一种通过有放回重抽样来评估模型性能的统计方法。它通过从原始数据集中随机抽取样本形成多个Bootstrap数据集,计算统计量(如均值、标准差)的分布,适用于小样本和非参数场景。该方法能估计标准误、构建置信区间,并量化模型不确定性,但对计算资源要求较高。Bootstrap特别适合评估大模型的泛化能力和稳定性,在集成学习、假设检验等领域也有广泛应用。与传统方法相比,Bootstrap不依赖分布假设,在非正态数据中表现更稳健。
239 113