使用Go語(yǔ)言自制簡(jiǎn)單易用的Web框架
為什么寫這玩意
有時(shí)我要寫一些很小的應(yīng)用,比如爬蟲后端、服務(wù)器腳本又或者是BFF服務(wù),需要簡(jiǎn)單的Web服務(wù)。用gin感覺有點(diǎn)重,go.mod里會(huì)有很多用不到的依賴,直接用httprouter又很麻煩。所以想整個(gè)簡(jiǎn)單易用的Web框架。在實(shí)現(xiàn)基礎(chǔ)能力的同時(shí),順便集成一些方便開發(fā)的功能(便捷的Websocket連接、SSE推送、自動(dòng)綁定請(qǐng)求數(shù)據(jù)、自動(dòng)序列化返回響應(yīng)數(shù)據(jù)),并且盡量不引入第三方依賴,保持極簡(jiǎn)架構(gòu)。
大概的架構(gòu)圖

技術(shù)選型
- 底層HTTP路由:選擇httprouter。
- 將請(qǐng)求頭/表單/URI參數(shù)綁定到結(jié)構(gòu)體上:選擇mapstructure。
框架目前的go.mod依賴情況:
module github.com/dpwgc/easierweb go 1.21.4 require ( github.com/julienschmidt/httprouter v1.3.0 github.com/mitchellh/mapstructure v1.5.0 golang.org/x/net v0.17.0 gopkg.in/yaml.v3 v3.0.1 )
基本用法
首先,運(yùn)行g(shù)o get,安裝一下框架:
go get github.com/dpwgc/easierweb
然后,編寫路由代碼,寫起來(lái)和gin差不多,沒有學(xué)習(xí)成本:
package main
import (
"github.com/dpwgc/easierweb"
"github.com/dpwgc/easierweb/middlewares"
"log"
"net/http"
)
func main() {
// 新建一個(gè)路由
router := easierweb.New()
// 使用日志中間件
router.Use(middlewares.Logger())
// GET接口
router.GET("/hello/:name", hello)
// 啟動(dòng)服務(wù)
log.Fatal(router.Run(":80"))
}
// 請(qǐng)求處理函數(shù)
func hello(ctx *easierweb.Context) {
// 獲取URI Path里的name參數(shù)
name := ctx.Path.Get("name")
// 綁定URI Query參數(shù)到指定結(jié)構(gòu)體上
req := Request{}
err := ctx.BindQuery(&req)
if err != nil {
panic(err)
}
fmt.Println("request data:", req)
// 響應(yīng):hello {name}
ctx.WriteString(http.StatusOK, "hello "+name)
}
// 請(qǐng)求結(jié)構(gòu)體
type Request struct {
Name string `mapstructure:"name"`
Mobile string `mapstructure:"mobile"`
}
進(jìn)階改造(將繁瑣的流程自動(dòng)化)
實(shí)際在使用的時(shí)候,每次獲取請(qǐng)求數(shù)據(jù)都得調(diào)用一下Bind函數(shù),然后響應(yīng)數(shù)據(jù)時(shí)又得調(diào)用下Write函數(shù)再return,感覺還是不夠簡(jiǎn)單,因此加了個(gè)通過反射自動(dòng)綁定請(qǐng)求數(shù)據(jù)到結(jié)構(gòu)體、自動(dòng)序列化寫入響應(yīng)數(shù)據(jù)的功能。

實(shí)現(xiàn)方式參考: github.com/MikeLINGxZ/simple-server-runner 這是一個(gè)基于Gin的自動(dòng)綁定請(qǐng)求數(shù)據(jù)+自動(dòng)寫入響應(yīng)數(shù)據(jù)的庫(kù)。
最終成品有點(diǎn)像Spring Boot那種接口方法樣式:
package main
import (
"fmt"
"github.com/dpwgc/easierweb"
"github.com/dpwgc/easierweb/middlewares"
"log"
)
func main() {
// 還是老樣子,先創(chuàng)建一個(gè)路由
router := easierweb.New().Use(middlewares.Logger())
// 使用EasyPOST函數(shù)(EasyXXX函數(shù)內(nèi)置了自動(dòng)綁定+自動(dòng)寫入),整一個(gè)POST提交接口
router.EasyPOST("/submit", submit)
// 啟動(dòng)服務(wù)
log.Fatal(router.Run(":80"))
}
// 請(qǐng)求處理函數(shù)(自動(dòng)綁定請(qǐng)求數(shù)據(jù)+自動(dòng)寫入響應(yīng)數(shù)據(jù))
// 這個(gè)請(qǐng)求處理函數(shù)和上文的基礎(chǔ)版不大一樣,除了上下文入?yún)⒁酝?,還有個(gè)請(qǐng)求數(shù)據(jù)入?yún)⒑晚憫?yīng)數(shù)據(jù)返回體
// 當(dāng)這個(gè)函數(shù)被調(diào)用時(shí),會(huì)通過反射將POST請(qǐng)求的Body數(shù)據(jù)自動(dòng)解析并綁定到req參數(shù)上(如果是GET請(qǐng)求就綁定Query參數(shù))
// 當(dāng)這個(gè)函數(shù)返回時(shí),同樣通過反射獲取到返回的結(jié)構(gòu)體,將其序列化成Json字符串后,寫入響應(yīng)
func submit(ctx *easierweb.Context, req Request) *Response {
// 打印req的參數(shù)
fmt.Printf("json body -> name: %s, mobile: %s \n", req.Name, req.Mobile)
// 直接return結(jié)構(gòu)體
return &Response{Code: 1000, Msg: "hello"}
}
type Request struct {
Name string `json:"name"`
Mobile string `json:"mobile"`
}
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
請(qǐng)求入?yún)⒑晚憫?yīng)返回值在反射賦值/取值時(shí)都做了動(dòng)態(tài)化識(shí)別,可傳可不傳,不傳req入?yún)r(shí)就不會(huì)自動(dòng)綁定請(qǐng)求數(shù)據(jù),不返回Response且沒有報(bào)錯(cuò)時(shí)就響應(yīng)204,返回了error或者函數(shù)拋出異常了就返回400/500,Response可以是對(duì)象也可以是切片。
func TestAPI(ctx *easierweb.Context, req Request) (*Response, error) func TestAPI(ctx *easierweb.Context, req Request) *Response func TestAPI(ctx *easierweb.Context, req Request) error func TestAPI(ctx *easierweb.Context, req Request) func TestAPI(ctx *easierweb.Context) (*Response, error) func TestAPI(ctx *easierweb.Context) *Response func TestAPI(ctx *easierweb.Context) error func TestAPI(ctx *easierweb.Context)
實(shí)際開發(fā)中,不一定以Json格式來(lái)接收/呈現(xiàn)數(shù)據(jù),所以留了個(gè)插件口子,可以讓用戶自定義自動(dòng)綁定/寫入數(shù)據(jù)的序列化與反序列化方式。
// 使用XML格式來(lái)處理自動(dòng)綁定和自動(dòng)寫入的數(shù)據(jù)
router := easierweb.New(easierweb.RouterOptions{
RequestHandle: plugins.XMLRequestHandle(),
ResponseHandle: plugins.XMLResponseHandle(),
})
追加功能(常用方法封裝)
我寫瀏覽器Js爬蟲經(jīng)常要用Websocket來(lái)連接后端并持續(xù)傳輸數(shù)據(jù),還有些表單動(dòng)態(tài)賦值的需求經(jīng)常要用到SSE,那就順便給框架整個(gè)websocket和SSE的快捷使用方式,同時(shí)把連接升級(jí)、跨域、SSE請(qǐng)求頭設(shè)置、連接關(guān)閉之類的操作全部都封裝到底層,不需要使用者操心這么多事情。
func main() {
// 新建路由
router := easierweb.New()
// 快速使用websocket
router.WS("/demoWS/:id", DemoWS)
// 快速使用SSE
router.SSE("/demoSSE/:id", DemoSSE)
// 啟動(dòng)
log.Fatal(router.Run(":80"))
}
// 快速使用websocket
func DemoWS(ctx *easierweb.Context) {
// 處理websocket連接
for {
// 接收客戶端消息
msg, err := ctx.ReceiveString()
if err != nil {
panic(err)
}
fmt.Println("read websocket msg:", msg)
// 發(fā)送消息給客戶端
err = ctx.SendString("hello")
if err != nil {
panic(err)
}
time.Sleep(1 * time.Second)
// 函數(shù)返回時(shí),websocket連接會(huì)自動(dòng)關(guān)閉,不勞煩用戶手動(dòng)調(diào)close了
return
}
}
// 快速使用SSE
func DemoSSE(ctx *easierweb.Context) {
// 循環(huán)推送數(shù)據(jù)
for i := 0; i < 5; i++ {
// SSE推送數(shù)據(jù), data: hello, id: {i}
err := ctx.Push(fmt.Sprintf("data: hello\nid: %v\n\n", i))
if err != nil {
panic(err)
}
time.Sleep(1 * time.Second)
}
}


以上就是使用Go語(yǔ)言自制簡(jiǎn)單易用的Web框架的詳細(xì)內(nèi)容,更多關(guān)于Go Web框架的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何在Go語(yǔ)言中高效使用Redis的Pipeline
在 Redis 中,Pipeline 就像一條流水線,它允許我們將多個(gè)命令一次性發(fā)送到服務(wù)器,下面我們就來(lái)看看如何在Go語(yǔ)言中高效使用Redis的Pipeline吧2024-11-11
Golang定時(shí)任務(wù)框架GoCron的源碼分析
本文主要介紹了Golang定時(shí)任務(wù)框架GoCron的源碼分析,原生的gocron存在一些問題,如任務(wù)列表維護(hù)不當(dāng)、并發(fā)操作不可預(yù)測(cè)等,經(jīng)過改進(jìn)的gocron解決了這些問題,感興趣的可以了解一下2025-03-03
go使用SQLX操作MySQL數(shù)據(jù)庫(kù)的教程詳解
sqlx 是 Go 語(yǔ)言中一個(gè)流行的操作數(shù)據(jù)庫(kù)的第三方包,它提供了對(duì) Go 標(biāo)準(zhǔn)庫(kù) database/sql 的擴(kuò)展,簡(jiǎn)化了操作數(shù)據(jù)庫(kù)的步驟,下面我們就來(lái)學(xué)習(xí)一下go如何使用SQLX實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)的一些基本操作吧2023-11-11
Go基礎(chǔ)教程系列之import導(dǎo)入包(遠(yuǎn)程包)和變量初始化詳解
這篇文章主要介紹了Go基礎(chǔ)教程系列之import導(dǎo)包和初始化詳解,需要的朋友可以參考下2022-04-04
golang版本升級(jí)的簡(jiǎn)單實(shí)現(xiàn)步驟
個(gè)人感覺Go在眾多高級(jí)語(yǔ)言中,是在各方面都比較高效的,下面這篇文章主要給大家介紹了關(guān)于golang版本升級(jí)的簡(jiǎn)單實(shí)現(xiàn)步驟,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
go語(yǔ)言中如何使用select的實(shí)現(xiàn)示例
本文主要介紹了go語(yǔ)言中如何使用select的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05

