Go Http Get 和 Post 工具函式
前言
先說一下為什麼要搞這個小東西?
米攸服務端前期主要是基於 Go 構建的,版本迭代過程中,業務複雜度不斷增加,再加上中員團隊有人員變動,考慮到目前團隊的技術背景,我們開始考慮把介面服務分批遷移到 Java,開發效率和可控程度更高一些。其中有一些介面服務涉及周邊模組較多,遷移的時間成本較高,我們決定暫時繼續維護這些介面。後續介面需要升級時,如果變動較小,我們直接修改 Go 程式碼;如果變動較大,我們在 Go 程式碼中使用 HTTP 的方式呼叫 Java 介面實現,相當於給原有介面加了一個 鉤子。為了減化介面呼叫程式碼編寫的複雜度,我們考慮在 Go 程式碼中內建兩個工具函式:Get 和 Post
本文重點討論 Get 和 Post 函式實現的關鍵細節,並給出核心程式碼。
Result
Java 介面的返回結果是一個 固定格式 的 Json 字串:
-
id
請求ID,字串。
-
code
狀態碼,整數。
-
msg
狀態資訊,字串。
-
data
資料,任意型別。
我們使用 結構體 封裝返回結果:
type Result struct { Id string `json:"id"` Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` }
可以發現,結構體的欄位名稱和返回結果的欄位名稱是不一樣的(首字母大小寫),兩者相互轉換的時候名稱會對應不上,需要在結構體中使用類似 `json:"id"` 的宣告,把結構體中的欄位名稱和返回結果的欄位名稱一一對應起來。
特別注意:宣告欄位名稱時標點符號的使用。
Client
Go 提供的 Http 客戶端(Client)例項是執行緒安全的,一個程序內只需要有一個即可:
var client = http.Client{}
Get
Get 函式的引數應該有兩個:介面路徑(url)和 介面引數(params)。介面路徑比較簡單,就是一個字串(string),我們主要討論介面引數。
我們使用 Query String
- string
- int
- float64
因為引數的值型別是不確定的,所以使用 interface{} 表示任意型別,函式內部判斷具體型別。
因為呼叫介面時需要新增 請求頭 和 請求引數,所以不能直接使用 http.Get 這樣的簡化函式,實現流程:
建立請求
使用 http.NewRequest 建立請求:
req, err := http.NewRequest("GET", url, nil)
新增請求頭
設定請求響應的內容型別為 json:
req.Header.Add("Content-Type", "application/json")
設定呼叫介面時的 Token:
req.Header.Add("token", MEETU_API_TOKEN)
新增請求引數
建立請求引數:
query := req.URL.Query()
逐個新增請求引數:
query.Add(name, value)
注意:新增請求引數時,name(引數名稱) 和 value(引數型別) 型別都是字串。
如前文所述,介面引數是一個字典型別的變數,我們需要遍歷這個變數中的每一個鍵值對,逐個新增引數。遍歷可以使用 Range:
for name, value := range params {
...
}
如前文所述,引數值型別是有限制的,遍歷過程中,我們需要判斷引數值型別是否符合要求。型別判斷可以使用 value.(type):
switch value.(type) {
case string:
query.Add(name, value.(string))
case int:
query.Add(name, strconv.Itoa(value.(int)))
case float64:
query.Add(name, strconv.FormatFloat(value.(float64), 'f', -1, 64))
default:
return Result{}, errors.New("params type only support string, int and float64")
}
使用 value.(string)、value.(int) 和 value.(float64) 把變數 value(型別:interface{}) 分別轉換為型別 string、int 和 float64 的變數。使用 strconv.Itoa 把 int 變數轉換為 string 變數,使用 strconv.FormatFloat 把 float64 變數轉換為 string 變數。
請求引數值可能包含特殊字元,需要轉義:
req.URL.RawQuery = query.Encode()
請求引數新增完成。
執行請求,獲取結果
resp, err := client.Do(req)
defer resp.Body.Close()
defer 表示 Get 函式執行完成之後,關閉 Http 客戶端內部的網路連線。
解析結果
響應體(resp.Body)的資料是位元組流,需要解碼並反序列化成型別為 Result 的變數 result:
json.NewDecoder(resp.Body).Decode(&result)
返回結果
return result, nil
到此,Get 函式實現完成,程式碼如下:
func Get(url string, params map[string]interface{}) (Result, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return Result{}, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("token", MEETU_API_TOKEN)
if params != nil {
query := req.URL.Query()
for name, value := range params {
switch value.(type) {
case string:
query.Add(name, value.(string))
case int:
query.Add(name, strconv.Itoa(value.(int)))
case float64:
query.Add(name, strconv.FormatFloat(value.(float64), 'f', -1, 64))
default:
return Result{}, errors.New("params type only support string, int and float64")
}
}
req.URL.RawQuery = query.Encode()
}
resp, err := client.Do(req)
if err != nil {
return Result{}, err
}
defer resp.Body.Close()
var result Result
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return Result{}, err
}
return result, nil
}
Post
Post 函式的實現過程整體和 Get 是類似的,唯一不同的就是請求引數的處理。我們使用 Body 傳遞 Post 引數。
其餘內容請參考:Go Http Get 和 Post 工具函式。