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

1,577 阅读6分钟

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 实现的主要方式

从上面代码可以看的出来,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

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

-- ./koa/application.go row:30

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

-- ./koa/context.go

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

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

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

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

3.3 入口方法

好的,贴图为先 ~

-- ./koa/application.go

主要的一些东西都在注释上写了,直接看注释吧。总觉得 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 端口
}

这就是一个简单的应用例子。现在去浏览器打开一下 http://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 了,不过我觉得这只是我的错觉而已。

感谢您的观看 ~~ 谢谢!

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