资讯专栏INFORMATION COLUMN

不到300行代码构建精简的koa和koa-router(mini-koa)

tuomao / 3009人阅读

摘要:详细代码如下追踪赋值里面的是子路由设计子路由设计这个比较简单,每个子路由维护一个路由监听列表,然后通过调用的函数添加到主路由列表上。

前言

鉴于之前使用expresskoa的经验,这两天想尝试构建出一个koa精简版,利用最少的代码实现koa和koa-router,同时也梳理一下Node.js网络框架开发的核心内容。

实现后的核心代码不超过300行,源代码配有详细的注释。

核心设计 API调用

mini-koa的API设计中,参考koa和koa-routerAPI调用方式。

Node.js的网络框架封装其实并不复杂,其核心点在于http/httpscreateServer方法上,这个方法是http请求的入口。

首先,我们先回顾一下用Node.js来启动一个简单服务。

// https://github.com/qzcmask/mini-koa/blob/master/examples/simple.js
const http = require("http")
const app = http.createServer((request, response) => {
  response.end("hello Node.js")
})
app.listen(3333, () => {
  console.log("App is listening at port 3333...")
})
路由原理

既然我们知道Node.js的请求入口在createServer方法上,那么我们可以在这个方法中找出请求的地址,然后根据地址映射出监听函数(通过get/post等方法添加的路由函数)即可。

其中,路由列表的格式设计如下:

// binding的格式
{
"/": [fn1, fn2, ...],
"/user": [fn, ...],
...
}
// fn/fn1/fn2的格式
{
  method: "get/post/use/all",
  fn: "路由处理函数"
}
难点分析 next()方法设计

我们知道在koa中是可以添加多个url监听函数的,其中决定是否传递到下一个监听函数的关键在于是否调用了next()函数。如果调用了next()函数则先把路由权转移到下一个监听函数中,处理完毕再返回当前路由函数。

mini-koa中,我把next()方法设计成了一个返回Promise fullfilled的函数(这里简单设计,不考虑next()传参的情况),用户如果调用了该函数,那么就可以根据它的值来决定是否转移路由函数处理权。

判断是否转移路由函数处理权的代码如下:

let isNext = false
const next = () => {
  isNext = true
  return Promise.resolve()
}
await router.fn(ctx, next)
if (isNext) {
  continue
} else {
  // 没有调用next,直接中止请求处理函数
  return
}
use()方法设计

mini-koa提供use方法,可供扩展日志记录/session/cookie处理等功能。

use方法执行的原理是根据请求地址在执行特定路由函数之前先执行mini-koa调用use监听的函数

所以这里的关键点在于怎么找出use监听的函数列表,假设现有监听情况如下:

app.use("/", fn1)
app.use("/user", fn2)

如果访问的url/user/add,那么fn1和fn2都必须要依次执行。

我采取的做法是先根据/字符来分割请求url,然后循环拼接,查看路由绑定列表(binding)中有没有要use的函数,如果发现有,添加进要use的函数列表中,没有则继续下一次循环。

详细代码如下:

// 默认use函数前缀
let prefix = "/"
// 要预先调用的use函数列表
let useFnList = []

// 分割url,使用use函数
// 比如item为/user/a/b映射成[("user", "a", "b")]
const filterUrl = url.split("/").filter(item => item !== "")
// 该reduce的作用是找出本请求要use的函数列表
filterUrl.reduce((cal, item) => {
  prefix = cal
  if (this.binding[prefix] && this.binding[prefix].length) {
    const filters = this.binding[prefix].filter(router => {
      return router.method === "use"
    })
    useFnList.push(...filters)
  }
  return (
    "/" +
    [cal, item]
      .join("/")
      .split("/")
      .filter(item => item !== "")
      .join("/")
  )
}, prefix)
ctx.body响应

通过ctx.body = "响应内容"的方式可以响应http请求。它的实现原理是利用了ES6Object.defineProperty函数,通过设置它的setter/getter函数来达到数据追踪的目的。

详细代码如下:

// 追踪ctx.body赋值
Object.defineProperty(ctx, "body", {
  set(val) {
    // set()里面的this是ctx
    response.end(val)
  },
  get() {
    throw new Error(`ctx.body can"t read, only support assign value.`)
  }
})
子路由mini-koa-router设计

子路由mini-koa-router设计这个比较简单,每个子路由维护一个路由监听列表,然后通过调用mini-koaaddRoutes函数添加到主路由列表上。

mini-koaaddRoutes实现如下:

addRoutes(router) {
  if (!this.binding[router.prefix]) {
    this.binding[router.prefix] = []
  }
  // 路由拷贝
  Object.keys(router.binding).forEach(url => {
    if (!this.binding[url]) {
      this.binding[url] = []
    }
    this.binding[url].push(...router.binding[url])
  })
}
用法

使用示例如下,源代码可以在github上找到:

// examples/server.js
// const { Koa, KoaRouter } = require("mini-koa")
const { Koa, KoaRouter } = require("../index")
const app = new Koa()
// 路由用法
const userRouter = new KoaRouter({
  prefix: "/user"
})

// 中间件函数
app.use(async (ctx, next) => {
  console.log(`请求url, 请求method: `, ctx.req.url, ctx.req.method)
  await next()
})

// 方法示例
app.get("/get", async ctx => {
  ctx.body = "hello ,app get"
})

app.post("/post", async ctx => {
  ctx.body = "hello ,app post"
})

app.all("/all", async ctx => {
  ctx.body = "hello ,/all 支持所有方法"
})

// 子路由使用示例
userRouter.post("/login", async ctx => {
  ctx.body = "user login success"
})

userRouter.get("/logout", async ctx => {
  ctx.body = "user logout success"
})

userRouter.get("/:id", async ctx => {
  ctx.body = "用户id: " + ctx.params.id
})

// 添加路由
app.addRoutes(userRouter)

// 监听端口
app.listen(3000, () => {
  console.log("> App is listening at port 3000...")
})
总结

挺久没有造轮子了,这次突发奇想造了个精简版的koa,虽然跟常用的koa框架有很大差别,但是也实现了最基本的API调用和原理。

造轮子是一件难能可贵的事,程序员在学习过程中不应该崇尚拿来主义,学习到一定程度后,要秉持能造就造的态度,去尝试理解和挖掘轮子背后的原理和思想。

当然,通常来说,自己造的轮子本身不具备多大的实用性,没有经历过社区大量的测试和实际应用场景的打磨,但是能加深自己的理解和提高自己的能力也是一件值得坚持的事。

人生是一段不断攀登的高峰,只有坚持向前,才能看到新奇的东西。

最后附上项目的Github地址,欢迎Star或Fork支持,谢谢。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103631.html

相关文章

  • 从零搭建Koa2 Server

    摘要:于是翻遍与各大网站,都没找到一个好用的轻一点的脚手架,也找不到一个清晰些的搭建介绍。现在把搭建过程介绍下,看能不能方便下入门的同学。创建一个文件夹,命名。记得先装好以上版本一路回车,根据提示输入信息。但这只是初步的搭建了下。 前几天想写个小爬虫程序,准备后端就用koa2。于是翻遍github与各大网站,都没找到一个好用的、轻一点的koa2脚手架,也找不到一个清晰些的搭建介绍。githu...

    JellyBool 评论0 收藏0
  • iKcamp团队制作|基于Koa2搭建Node.js实战(含视频)☞ 路由koa-router

    路由koa-router——MVC 中重要的环节:Url 处理器 ?? iKcamp 制作团队 原创作者:大哼、阿干、三三、小虎、胖子、小哈、DDU、可木、晃晃 文案校对:李益、大力萌、Au、DDU、小溪里、小哈 风采主播:可木、阿干、Au、DDU、小哈 视频剪辑:小溪里 主站运营:给力xi、xty 教程主编:张利涛 视频地址:https://www.cctalk.com/v/151...

    netmou 评论0 收藏0
  • Koa v2.x 中文文档 Koa 对比 Express

    摘要:使用承诺和异步功能来摆脱回调地狱的应用程序,并简化错误处理。它暴露了自己的和对象,而不是的和对象。因此,可被视为的模块的抽象,其中是的应用程序框架。这使得中间件对于整个堆栈而言不仅仅是最终应用程序代码,而且更易于书写,并更不容易出错。 Koa 与 Express 此系列文章的应用示例已发布于 GitHub: koa-docs-Zh-CN. 可以 Fork 帮助改进或 Star 关注更新...

    summerpxy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<