類型安全的 Go HTTP 請求

前言

對 Gopher 來說,雖然我們基本都是在寫代碼讓別人來請求,但是有時候,我們也需要去請求第三方提供的 RESTful 接口,這個時候,我們才能感受到前端同學拼接 HTTP 請求參數的痛苦。

比如,我們要發起類似這樣一個請求,看起來很簡單,實際寫起來還是比較繁瑣的。

POST /articles/5/update?device=ios HTTP/1.1
Host: go-zero.dev
Authorization: Bearer <jwt-token>

{"author":"kevin","body":"this is not important!","title":"my title","type":6}

Go 原生寫法

這個 API 其實是蠻簡單的,我們直接上手就可以寫出來。

func main() {
    var buf bytes.Buffer
    encoder := json.NewEncoder(&buf)
    params := map[string]interface{}{
        "title":  "my title",
        "body":   "this is not important!",
        "author""kevin",
        "type":   6,
    }
    if err := encoder.Encode(params); err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    url := fmt.Sprintf("http://localhost:3333/articles/%d/update?device=%s", 5, "ios")
    req, err := http.NewRequest(http.MethodPost, url, &buf)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    req.Header.Add("Authorization""Bearer <jwt-token>")
    cli := http.Client{}
    resp, err := cli.Do(req)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    io.Copy(os.Stdout, resp.Body)
}

我們跑了測試一下,發現沒有得到 200 OK,抓包看一下,請求如下。各位不要往下看,你能想到失敗的原因嗎?

POST /articles/5/update?device=ios HTTP/1.1
Host: go-zero.dev
User-Agent: Go-http-client/1.1
Content-Length: 79
Authorization: Bearer <jwt-token>
Accept-Encoding: gzip

{"author":"kevin","body":"this is not important!","title":"my title","type":6}

具體失敗原因這裏就不細講了,我們先來分析這段代碼。可以看到其中爲了拼接參數使用了 map[string]interface{},對於其中每個字段我們是不能校驗類型是否匹配的,只有發送出去了,收到了服務端的 200 OK,我們才能確認傳對了。比如其中的 type 參數,這裏是使用了 int 類型,我們可能順手寫成 string 類型,但是不請求我們還是很難發現這個參數寫錯了的。

那麼讓我們看看 go-zerohttpc 包是怎麼使用並保證類型安全的。

httpc 實現

我們看看用 httpc 包來請求的代碼怎麼寫。

const url = "http://go-zero.dev/articles/:id/update"

type UpdateArticle struct {
    ID            int    `path:"id"`
    Device        string `form:"device,options=ios,android,web,desktop"`
    Authorization string `header:"Authorization"`
    Title         string `json:"title"`
    Body          string `json:"body"`
    Author        string `json:"author"`
    Type          int    `json:"type"`
}

func main() {
    data := &UpdateArticle{
        ID:            5,
        Device:        "ios",
        Authorization: "Bearer <jwt-token>",
        Title:         "my title",
        Body:          "this is not important!",
        Author:        "kevin",
        Type:          6,
    }

    resp, err := httpc.Do(context.Background(), http.MethodPost, url, data)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    io.Copy(os.Stdout, resp.Body)
}

寫完測試一下,結果正如預期:

POST /articles/5/update?device=ios HTTP/1.1
Host: go-zero.dev
User-Agent: Go-http-client/1.1
Content-Length: 79
Content-Type: application/json; charset=utf-8
Authorization: Bearer <jwt-token>
Accept-Encoding: gzip

{"author":"kevin","body":"this is not important!","title":"my title","type":6}

你發現了沒有,跟前面的對比,其中多了 Content-Type: application/json; charset=utf-8,而我們之前寫法裏忘記設置 Content-Type 了。

httpc 的寫法只要定義好請求的類型,然後通過 httpc.Do 就可以做到類型安全,並且代碼非常精簡。支持瞭如我們代碼所示的 pathformheaderjson,可以非常方便且類型安全的發送 HTTP 請求。

更多能力

除了上面展示的簡單易用和類型安全以外,httpc 包還有以下特點:

  1. context 的超時控制

  2. OpenTelemetry 自動集成,服務端返回的 trace-id, span-id 都會自動被記錄到日誌裏,便於後續客戶端、服務端協同查問題

  3. 可以通過 httpc.Service 來獲得熔斷能力,當服務端有問題,會自動熔斷隔離請求,避免浪費時間等待和加劇服務端壓力

項目地址

https://github.com/zeromicro/go-zero

歡迎使用 go-zerostar 支持我們!

微信交流羣

關注『微服務實踐』公衆號並點擊 交流羣 獲取社區羣二維碼。

如果你有 go-zero 的使用心得文章,或者源碼學習筆記,歡迎通過公衆號聯繫投稿!

微服務實踐 分享微服務的原理和最佳實踐,講透服務治理的底層原理,帶你細讀 go-zero 源碼。go-zero 是一個集成了各種工程實踐的 web 和 rpc 框架,旨在縮短從需求到上線的距離。公衆號文章勘誤在知乎號:萬俊峯 Kevin

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/SjtMZErSPE56VooGHDrlyg