Go Http Get 和 Post 工具函數
前言
先說一下為什麼要搞這個小東西?
米攸服務端前期主要是基於 Go 構建的,版本迭代過程中,業務複雜度不斷增加,再加上中員團隊有人員變動,考慮到目前團隊的技術背景,我們開始考慮把介面服務分批遷移到 Java,開發效率和可控程度更高一些。其中有一些介面服務涉及周邊模組較多,遷移的時間成本較高,我們決定暫時繼續維護這些介面。後續介面需要升級時,如果變動較小,我們直接修改 Go 程式碼;如果變動較大,我們在 Go 程式碼中使用 HTTP 的方式調用 Java 介面實現,相當於給原有介面加了一個 鉤子。為了減化介面調用程式碼編寫的複雜度,我們考慮在 Go 程式碼中內置兩個工具函數:Get 和 Post,方便調用 Java 介面。
本文重點討論 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 的方式傳遞 Get 參數,如:/interface/param1=value1¶m2=value2,介面參數的類型應該是一個內部包含多個鍵值對的 字典(map[string]interface{}),鍵名稱是參數名稱,鍵值是參數值;考慮到實際使用場景,參數值的類型限制為三種:
- 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 工具函數。
