1. 程式人生 > 其它 >Go Http Get 和 Post 工具函式

Go Http Get 和 Post 工具函式

前言

先說一下為什麼要搞這個小東西?

米攸服務端前期主要是基於 Go 構建的,版本迭代過程中,業務複雜度不斷增加,再加上中員團隊有人員變動,考慮到目前團隊的技術背景,我們開始考慮把介面服務分批遷移到 Java,開發效率和可控程度更高一些。其中有一些介面服務涉及周邊模組較多,遷移的時間成本較高,我們決定暫時繼續維護這些介面。後續介面需要升級時,如果變動較小,我們直接修改 Go 程式碼;如果變動較大,我們在 Go 程式碼中使用 HTTP 的方式呼叫 Java 介面實現,相當於給原有介面加了一個 鉤子。為了減化介面呼叫程式碼編寫的複雜度,我們考慮在 Go 程式碼中內建兩個工具函式:GetPost

,方便呼叫 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&param2=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 工具函式