用 Go 来实现 Koa 的洋葱模型《Koa for Go》🧅

1. 简述

⚜️ 本项目是更好这两天接触到 GO 突发奇想的想要实现一个
GO 版的 Koa 玩玩, 这里就只是实现了 Koa 的核心思想
洋葱模型以及简单的路由中间件,其他没有继续深入的继续进行了。

👉 仓库地址:Koa for go 欢迎 Star

2. 项目结构

先来看一下项目的目录树结构和功能划分

KoaForGo
├── README.md
├── main.go             // 入口,实现了使用例子
├── koa                 // 核心实现 core
│   ├── application.go  // koa 入口 主要用于开启 Http 链接,设置中间件
│   ├── compose.go      // 用于组装中间件
│   └── context.go      // context 没啥好说的,简单的上下文
└── router              // 路由中间件
    ├── method          // 请求方式目录
    │   └── method.go   // 常量方式存储 Http 请求方式 GET,POST,PUT,DELETE
    └── router.go       // 路由中间件实现

3. 具体实现

事实上 Go 是一门强类型语言,所以我一开始直接参考(照搬) Koa.js 的方式
来到 Go 这里尝试的时候发现根本行不通。我也就这两天才接触到 Go 语言了解到 Go 的写法
所以一开始到处碰壁写出一堆报错的代码可难受了。

3.1 Koa.js 洋葱模型实现

我先把 Koa.js 的代码贴上来我们来分析一下 Koa.js 实现的主要方式

d7e2221125a943419d64c5aaaaa16d97~tplv-k3u1fbpfcp-zoom-1.image

从上面代码可以看的出来,Koa.js 中主要实现洋葱模型的方式其实挺简单的。

首先是一进来就是一通判断,参数是不是数组,数组里面的元素是不是都是 function,
不过这些对于我们现在要研究的东西并不重要,接下来的才是重点。

在 compose 这个 function 中返回了一个函数提供外部调用,这是开启运行中间件的入口。

compose 中返回的 function 还存在一个 dispatch 的函数,这个函数是实现洋葱模型执行的核心 function,
dispatch function 会递归的被调用,每次调用都会从 middleware 中获取一个中间件进行调用,
并递归返回将下一个中间件作为当前中间件的 next 参数传入。

3.2 Go 实现洋葱模型的具体办法

来到 Go 这边,我一开始用照搬的方式,写是写出来了就是运行起来一点都不洋葱 🧅,
按照洋葱模型按照中间件的执行顺序应该是 1 -> 2 -> 3 -> 2 -> 1 这种。
然鹅 🦢 我实现出来的执行出来的是 3 -> 2 -> 1 emmmm ~ ~ 这就难受了,暂时没搞明白是为嘛,
大概是因为 js 和 Go 的执行机制不大一样把。

然后我换了很多方式最后使用了链表的方式实现了洋葱模型,废话说了很多了,好!上代码。

– ./koa/compose.go

724b5df7dd4b4d788563f3ed4fc9851d~tplv-k3u1fbpfcp-zoom-1.image

具体的看注释,核心就是链表模式实现,哦对了看看 MiddlewareType 这个结构体

– ./koa/application.go row:30

2b16aa918e924625a984c7e781d13682~tplv-k3u1fbpfcp-zoom-1.image

反正就是一个最简单的链表结构。接下来看看 context。

– ./koa/context.go

d0016944806742df98b71af3cc23512d~tplv-k3u1fbpfcp-zoom-1.image

Context 的话因为时间问题没有进行太多的封装,就只是简单的组合了一些东西形成了 Context 结构体。

主要给 Context 实现了一个 Next 的一个方法,
主要就是实现了执行下一个中间件的操作,然后将中间件链表载入下一个,然后等待下一个调用,
如果下一个中间件为空那就不操作。

差不多就是上面的几个组件支撑了我这个 Koa for Go 的洋葱模型的实现了。

因为接触 Go 的时间太短了,没进行深入的学习了解 Go 的其他骚操作只能按照自己能想象到的方式实现了出来,
广大哥哥评论区指点迷津,非常感谢。

3.3 入口方法

好的,贴图为先 ~

– ./koa/application.go

eed80a6cc3654de081bc1018cc4d6413~tplv-k3u1fbpfcp-zoom-1.image

主要的一些东西都在注释上写了,直接看注释吧。总觉得 application.go 这里一个 Use 和
compose.go 中的一个 Use 呃 ~ 怪怪的不过我起名困难那就先这样子吧,知道就好了,将就。

3.4 Router 路由中间件

这个的话其实就是简单的实现了一下路由的分发而已,感兴趣的可以自己去看看源码,应该不难。
如果有需要我在解释一下的话,那就评论区留言吧,要是需要我在另外写一下介绍一下。

4. 使用方式

说完源码,现在来说说怎么用吧。用法我个人感觉其实跟 Koa.js 也差不多。

4.1 简单应用

先实例化一个 Koa 然后写自己的中间件实现,中间件会接收一个 Context
里面有 Next 方法可以调用下一个中间件
最后监听一下端口,就正式启动了一个 http 服务了,还是实现了这个洋葱模型的,

这里的话我写了三个中间件

第一个,按照已知洋葱模型的 1 -> 2 -> 3 -> 2 -> 1
的原因知道第一个中间件在调用 Next 之后的代码将在最后被调用,所以说,我就在第一个中间件后面
调用 Response.Write 把 Body 的数据返回去。

第二个,就是简单的设置了一下 Body 的数据

第三个,因为 Body 是结构体,通常来说我们都是要返回的是一个 json 结构的数据给前端,
所以这里就把结构体进行了转换并且赋值到了 JsonBody 中方便第一个中间件返回数据。

func main() {
    app := koa.Koa{}

    // 将数据写入到返回
    app.Use(koa.MiddlewareType{ Action: func(context *koa.Context) {
        context.Next()
        context.Response.Write(context.JsonBody)
    }})

    // 往 body 中装入返回信息
    app.Use(koa.MiddlewareType{ Action: func(context *koa.Context) {
        context.Body = VO{ Message: "hello koa for go!" }
        context.Next()
    }})

    // 将 返回的对象转为 json 对象
    app.Use(koa.MiddlewareType{ Action: func(context *koa.Context) {
        context.JsonBody, _ = json.Marshal(context.Body)
    }})

    app.Listen(5000) // 监听 5000 端口
}

这就是一个简单的应用例子。现在去浏览器打开一下 localhost:5000/
就可以成功的请求并返回了消息

{
  "Message": "hello koa for go!"
}

4.2 使用 Router

上面简单的使用上了,但是还有一个问题就是没办法让不同的路由匹配到不同的函数中去。这个时候就要用上路由了。

下面是一个使用路由的栗子 🌰

跟比上面的那个 🌰 多了一点的就是多实例化了一个路由的结构体,这个结构体里面实现了常用的
GET, POST, Put, DELETE 这几个常用的请求方式提供调用,在这里我分别给 /home 这个路由把这几种请求
都实现了,然后返回的消息都不一样。

在最后调用 router.ToMiddleware 函数将会返回一个 koa of go 的中间,只要把这个挂载到 app
中这个路由就应用上了,现在用不同的请求去请求 /home 这个 uri 都会返回不同的结果出来了。

func main() {
    app := koa.Koa{}
    router := router.Router{}
    router.Get("/home", func(context *koa.Context) {
        context.Body = success(MessageData{ Info: "hello this is home Get"})
    })
    router.Post("/home", func(context *koa.Context) {
        context.Body = success(MessageData{ Info: "hello this is home Post"})
    })
    router.Put("/home", func(context *koa.Context) {
        context.Body = success(MessageData{ Info: "hello this is home Put"})
    })
    router.Delete("/home", func(context *koa.Context) {
        context.Body = success(MessageData{ Info: "hello this is home Delete"})
    })

    // 将数据写入到响应
    app.Use(koa.MiddlewareType{ Action: func(context *koa.Context) {
        context.Next()
        context.JsonBody, _ = json.Marshal(context.Body)
        context.Response.Write(context.JsonBody)
    }})
    // 装载路由
    app.Use(router.ToMiddleware())
    app.Listen(5000)
}

5. 结束语

好的以上就是这次我要介绍的 Koa fo Go 的全部内容了,作为一个正经的 node 程序员我现在的主业是 C#
现在对于 Go 其实也没太多的深入,所以哪里有问题的还请大家多多指教。

经过这次写这个 Koa for Go 我感觉我貌似又可以搞 Go 了,不过我觉得这只是我的错觉而已。

感谢您的观看 ~~ 谢谢!

你的点赞是我更新的动力,我将不定期为的搞事情为大家带来有趣好玩的东西~

本作品采用《CC 协议》,转载必须注明作者和本文链接
winily
winily
讨论数量: 2

貌似koa作者转向Go了

4年前 评论
winily (楼主) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!