天天看點

多圖詳解萬星 Restful 架構原理與實作

rest架構概覽

我們先通過

go-zero

自帶的指令行工具

goctl

來生成一個

api service

,其

main

函數如下:

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()
}           
  1. 解析配置檔案
  2. 将配置檔案傳入,初始化

    serviceContext

  3. 初始化

    rest server

  4. context

    注入

    server

    中:
    1. 注冊路由
    2. context

      中的啟動的

      endpoint

      同時注入到

      router

      當中
  5. 啟動

    server

接下來我們來一步步講解其設計原理!Let's Go!

web架構

從日常開發經驗來說,一個好的 web 架構大緻需要滿足以下特性:

  1. 路由比對/多路由支援
  2. 支援自定義中間件
  3. 架構和業務開發完全解耦,友善開發者快速開發
  4. 參數校驗/比對
  5. 監控/日志/名額等服務自查功能
  6. 服務自保護(熔斷/限流)

go-zero rest設計

https://github.com/zeromicro/go-zero/tree/master/rest

概覽

  1. 借助 context (不同于 gin 的 context),将資源初始化好 → 儲存在

    serviveCtx

    中,在 handler 中共享(至于資源池化,交給資源自己處理,

    serviveCtx

    隻是入口和共享點)
  2. 獨立 router 聲明檔案,同時加入 router group 的概念,友善開發者整理代碼結構
  3. 内置若幹中間件:監控/熔斷/鑒權等
  4. 利用 goctl codegen + option 設計模式,友善開發者自己控制部分中間件的接入
多圖詳解萬星 Restful 架構原理與實作

上圖描述了 rest 處理請求的模式和大部分處理路徑。

  1. 架構内置的中間件已經幫開發者解決了大部分服務自處理的邏輯
  2. 同時 go-zero 在

    business logic

    處也給予開發者開箱即用的元件(dq、fx 等)
  3. 從開發模式上幫助開發者隻需要關注自己的

    business logic

    以及所需資源準備

下面我們來細說一下整個 rest 是如何啟動的?

啟動流程

多圖詳解萬星 Restful 架構原理與實作

上圖描述了整體 server 啟動經過的子產品和大緻流程。準備按照如下流程分析 rest 實作:

  1. 基于 http.server 封裝以及改造:把 engine(web架構核心) 和 option 隔離開
  2. 多路由比對采取 radix-tree 構造
  3. 中間件采用洋蔥模型 →

    []Middleware

  4. http parse 解析以及比對校驗 →

    httpx.Parse()

  5. 在請求過程會收集名額 (

    createMetrics()

    ) 以及監控埋點 (prometheus)

server engine封裝

多圖詳解萬星 Restful 架構原理與實作
點開大圖觀看

engine 貫穿整個 server 生命周期中:

  1. router 會攜帶開發者定義的 path/handler,會在最後的 router.handle() 執行
  2. 注冊的自定義中間件 + 架構中間件,在 router handler logic 前執行

在這裡:go-zero 處理的粒度在 route 上,封裝和處理都在 route 一層層執行

路由比對

那麼當 request 到來,首先是如何到路由這一層的?

首先在開發最原始的 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()

内部會執行到:

server.ListenAndServe()

我們看看在 rest 裡面是怎麼運用的:

多圖詳解萬星 Restful 架構原理與實作

而傳入的 handler 其實就是:router.NewRouter() 生成的 router。這個 router 承載了整個 server 的處理函數集合。

同時 http.Server 結構在初始化時,是把 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 後,最終執行的也是:

handler.ServeHTTP(rw, req)

多圖詳解萬星 Restful 架構原理與實作

是以内置的 router 也需要實作

ServeHTTP

。至于 router 自己是怎麼實作

ServeHTTP

:無外乎就是尋找比對路由,然後執行路由對應的 handle logic。

解析參數

解析參數是 http 架構需要提供的基本能力。在 goctl code gen 生成的代碼中,handler 層已經內建了 req argument parse 函數:

// 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)
    }
}           

進入到

httpx.Parse()

,主要解析以下幾塊:

https://github.com/zeromicro/go-zero/blob/master/rest/httpx/requests.go#L32:6
  1. 解析path
  2. 解析form表單
  3. 解析http header
  4. 解析json

Parse() 中的 參數校驗 的功能見:

https://go-zero.dev/cn/api-grammar.html 中的

tag修飾符

Tips

學習源碼推薦 fork 出來邊看邊寫注釋和心得,可以加深了解,以後用到這塊功能的時候也可以回頭翻閱。

項目位址

微信交流群