用 Go 來實現 Koa 的洋蔥模型《Koa for Go》?

winily發表於2021-01-25

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

相關文章