多圖詳解萬(wàn)星 Restful 框架原理與實(shí)現(xiàn)
rest框架概覽
我們先通過(guò) go-zero 自帶的命令行工具 goctl 來(lái)生成一個(gè) api service,其 main 函數(shù)如下:
func?main()?{
?flag.Parse()
?var?c?config.Config
?conf.MustLoad(*configFile,?&c)
?ctx?:=?svc.NewServiceContext(c)
?server?:=?rest.MustNewServer(c.RestConf)
?defer?server.Stop()
?handler.RegisterHandlers(server,?ctx)
?fmt.Printf("Starting?server?at?%s:%d...\n",?c.Host,?c.Port)
?server.Start()
}
解析配置文件 將配置文件傳入,初始化 serviceContext初始化 rest server將 context注入server中:注冊(cè)路由 將 context中的啟動(dòng)的endpoint同時(shí)注入到router當(dāng)中啟動(dòng) server
接下來(lái)我們來(lái)一步步講解其設(shè)計(jì)原理!Let's Go!
web框架
從日常開發(fā)經(jīng)驗(yàn)來(lái)說(shuō),一個(gè)好的 web 框架大致需要滿足以下特性:
路由匹配/多路由支持 支持自定義中間件 框架和業(yè)務(wù)開發(fā)完全解耦,方便開發(fā)者快速開發(fā) 參數(shù)校驗(yàn)/匹配 監(jiān)控/日志/指標(biāo)等服務(wù)自查功能 服務(wù)自保護(hù)(熔斷/限流)
go-zero rest設(shè)計(jì)
https://github.com/zeromicro/go-zero/tree/master/rest
概覽
借助 context (不同于 gin 的 context),將資源初始化好 → 保存在 serviveCtx中,在 handler 中共享(至于資源池化,交給資源自己處理,serviveCtx只是入口和共享點(diǎn))獨(dú)立 router 聲明文件,同時(shí)加入 router group 的概念,方便開發(fā)者整理代碼結(jié)構(gòu) 內(nèi)置若干中間件:監(jiān)控/熔斷/鑒權(quán)等 利用 goctl codegen + option 設(shè)計(jì)模式,方便開發(fā)者自己控制部分中間件的接入

上圖描述了 rest 處理請(qǐng)求的模式和大部分處理路徑。
框架內(nèi)置的中間件已經(jīng)幫開發(fā)者解決了大部分服務(wù)自處理的邏輯 同時(shí) go-zero 在 business logic處也給予開發(fā)者開箱即用的組件(dq、fx 等)從開發(fā)模式上幫助開發(fā)者只需要關(guān)注自己的 business logic以及所需資源準(zhǔn)備
下面我們來(lái)細(xì)說(shuō)一下整個(gè) rest 是如何啟動(dòng)的?
啟動(dòng)流程

上圖描述了整體 server 啟動(dòng)經(jīng)過(guò)的模塊和大致流程。準(zhǔn)備按照如下流程分析 rest 實(shí)現(xiàn):
基于 http.server 封裝以及改造:把 engine(web框架核心) 和 option 隔離開 多路由匹配采取 radix-tree 構(gòu)造 中間件采用洋蔥模型 → []Middlewarehttp parse 解析以及匹配校驗(yàn) → httpx.Parse()在請(qǐng)求過(guò)程會(huì)收集指標(biāo) ( createMetrics()) 以及監(jiān)控埋點(diǎn) (prometheus)
server engine封裝

點(diǎn)開大圖觀看
engine 貫穿整個(gè) server 生命周期中:
router 會(huì)攜帶開發(fā)者定義的 path/handler,會(huì)在最后的 router.handle() 執(zhí)行 注冊(cè)的自定義中間件 + 框架中間件,在 router handler logic 前執(zhí)行
在這里:go-zero 處理的粒度在 route 上,封裝和處理都在 route 一層層執(zhí)行
路由匹配
那么當(dāng) request 到來(lái),首先是如何到路由這一層的?
首先在開發(fā)最原始的 http server ,都有這么一段代碼:
type?helloHandler?struct{}
func?(h?*helloHandler)?ServeHTTP(w?http.ResponseWriter,?r?*http.Request)?{
????w.Write([]byte("Hello,?world!"))
}
func?main()?{
????http.Handle("/",?&helloHandler{})
????http.ListenAndServe(":12345",?nil)
}
http.ListenAndServe() ?內(nèi)部會(huì)執(zhí)行到:server.ListenAndServe()
我們看看在 rest 里面是怎么運(yùn)用的:

而傳入的 handler 其實(shí)就是:router.NewRouter() 生成的 router。這個(gè) router 承載了整個(gè) server 的處理函數(shù)集合。
同時(shí) http.Server 結(jié)構(gòu)在初始化時(shí),是把 handler 注入到里面的:
type?Server?struct?{
?...
?Handler?Handler
}
func?start(...,?handler?http.Handler,?run?func(srv?*http.Server)?error)?(err?error)?{
?server?:=?&http.Server{
??Addr:????fmt.Sprintf("%s:%d",?host,?port),
??Handler:?handler,
?}
?...
?return?run(server)
}
在 http.Server 接收 req 后,最終執(zhí)行的也是:handler.ServeHTTP(rw, req)

所以內(nèi)置的 router 也需要實(shí)現(xiàn) ServeHTTP 。至于 router 自己是怎么實(shí)現(xiàn) ServeHTTP :無(wú)外乎就是尋找匹配路由,然后執(zhí)行路由對(duì)應(yīng)的 handle logic。
解析參數(shù)
解析參數(shù)是 http 框架需要提供的基本能力。在 goctl code gen 生成的代碼中,handler 層已經(jīng)集成了 req argument parse 函數(shù):
//?generate?by?goctl
func?QueryAllTaskHandler(ctx?*svc.ServiceContext)?http.HandlerFunc?{
?return?func(w?http.ResponseWriter,?r?*http.Request)?{
??//?custom?request?in?.api?file
??var?req?types.QueryAllTaskRequest
??//?parse?http?request
??if?err?:=?httpx.Parse(r,?&req);?err?!=?nil?{
???httpx.Error(w,?err)
???return
??}
??l?:=?logic.NewEventLogic(r.Context(),?ctx)
??resp,?err?:=?l.QueryAllTask(req)
??baseresponse.FormatResponseWithRequest(resp,?err,?w,?r)
?}
}
進(jìn)入到 httpx.Parse() ,主要解析以下幾塊:
https://github.com/zeromicro/go-zero/blob/master/rest/httpx/requests.go#L32:6
解析path 解析form表單 解析http header 解析json
Parse() 中的 參數(shù)校驗(yàn) 的功能見:
https://go-zero.dev/cn/api-grammar.html 中的
tag修飾符
Tips
學(xué)習(xí)源碼推薦 fork 出來(lái)邊看邊寫注釋和心得,可以加深理解,以后用到這塊功能的時(shí)候也可以回頭翻閱。
項(xiàng)目地址
https://github.com/zeromicro/go-zero
歡迎使用 go-zero 并 star 支持我們!
推薦閱讀
