Go 一文帶你喫透 HTTP 客戶端!

1. HTTP 請求簡介

HTTP(Hypertext Transfer Protocol) 是構建 web 應用通信的基石。

HTTP 工作於客戶端 - 服務端架構上。

HTTP 客戶端發起請求, 服務器接收請求並返回響應。

HTTP 請求主要由請求行、請求頭、請求體組成

請求行       GET /search?name=Golang HTTP/1.1
請求頭部     Host: www.baidu.com
            User-Agent: Mozilla/5.0
請求體       username=golang&password=123456

常用的 HTTP 方法

  • GET - 從服務器獲取資源

  • POST - 向服務器發送數據

  • PUT - 更新服務器上的資源

  • DELETE - 刪除服務器上的資源

HTTP 狀態碼

服務器返回的響應中都包含一個 HTTP 狀態碼, 常見的有:

200 OK - 請求成功
301 Moved Permanently - 重定向
404 Not Found - 資源未找到 500 Internal Server Error - 服務器內部錯誤

示例 GET 請求

發送 HTTP GET 請求獲取頁面, golang 標準庫 net/http 可以輕鬆實現

import "net/http"
func main() {
    resp, err := http.Get("http://www.example.com")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    fmt.Println(resp.Status) // HTTP狀態碼
}

2. 發送 HTTP 請求

Go 語言 net/http 包提供 HTTP 客戶端功能。主要使用兩個方法: Get 和 Post。

2.1 http.Get 發送 GET 請求

用 http.Get 發送 GET 請求到指定頁面, 然後打印響應狀態和頁面內容

func httpGetDemo() {
  resp, err := http.Get("https://www.baidu.com")
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()
  bytes, err := ioutil.ReadAll(resp.Body)
  if err != nil {  
      panic(err)
  }
  fmt.Println(resp.Status) 
  fmt.Println(string(bytes))
}

注意程序在讀取完響應 Body 後調用 Close 方法關閉網絡連接。defer 用來保證連接被關閉。

2.2 http.Post 發送 POST 請求

POST 請求通常用於向服務器提交數據, 例如提交表單。下面代碼通過 POST 方式傳遞一個 form 字符串

func httpPostDemo() {
  data := strings.NewReader(")
  resp, err := http.Post("http://www.example.com/apply", 
                   "application/x-www-form-urlencoded", data)
}

除了 form 表單, 還可以將 json、xml、protobuf 等格式的數據通過 body POST 到服務器。

2.3 自定義請求頭

可通過 Header 字段設置自定義頭信息

req, _ := http.NewRequest("GET", "http://example.com", nil)
// 設置Header
req.Header.Add("If-None-Match", `W/"wyzzy"`) 
resp, _ := http.DefaultClient.Do(req)

添加多個 header 也很簡單

req.Header.Add("Accept", "text/html") 
req.Header.Add("Connection", "close")

2.4 設置請求超時

Go 語言客戶端默認沒有超時時間, 可能導致長時間等待響應。利用 Timeout 字段可以設置超時

client := http.Client {
  Timeout: 5 * time.Second, // 設置超時時間
}
resp, err := client.Get("https://www.slowwly.rocks")

以上 client 在發出請求後最多等待 5 秒, 如果服務器沒回應會返回錯誤。

3. 處理 HTTP 響應

發送請求後 equally 要正確處理服務器返回的響應數據。

解析響應

解析 HTTP 響應主要包括以下步驟

  1. 檢查狀態碼 StatusCode

  2. 讀取響應頭 Headers

  3. 讀取響應體 Body

代碼示例:

resp, _ := http.Get("https://www.google.com")
fmt.Println(resp.StatusCode)
for k, v := range resp.Header {
    fmt.Println(k, v)
}
body, _ := ioutil.ReadAll(resp.Body) 
defer resp.Body.Close()

響應體解析

很多 Web API 返回 JSON 格式的數據

{"error": 0, "msg": "success"}

Go 語言 解析 JSON 響應體很簡單

import "encoding/json"  
var result map[string]interface{} 
json.NewDecoder(resp.Body).Decode(&result)

可方便訪問結果數據, 類似的, 也可以將響應體解析爲 XML。

fmt.Println(result["error"]) 
fmt.Println(result["msg"])

4. 客戶端配置

4.1 Cookie

用 Cookie 字段可以管理和發送 HTTP cookie

client := &http.Client{}
var cookies []*http.Cookie
cookies = append(cookies, &http.Cookie{Name:"cookie1", Value:"val1"})
cookies = append(cookies, &http.Cookie{Name:"cookie2", Value:"val2"})
client.Jar = &cookiejar.Jar{}
client.Jar.SetCookies(&url.URL{}, cookies)

以後 client 發出的所有請求都會帶上這些 cookie。

4.2 代理

可以爲 client 配置 HTTP 代理

proxyURL, _ := url.Parse("http://proxy_host:port")
client := &http.Client{Transport: &http.Transport{
    Proxy: http.ProxyURL(proxyURL)}}

4.3 Redirect 策略

通過 CheckRedirect 函數可以自定義處理重定向的邏輯,允許重定向指定次數。

client := &http.Client{
  CheckRedirect: func(
        req *http.Request, via []*http.Request) error {
    // 自定義Redirect策略
    return http.ErrUseLastResponse
  },  
}

4.4 TLS 配置

自定義 Transport 可以控制 TLS 版本、密鑰、證書等設置

tr := &http.Transport{
    TLSClientConfig:    &tls.Config{RootCAs: pool},
    DisableCompression: true,
}
client := &http.Client{Transport: tr}

5. 高級技巧

5.1 連接複用

每次請求都建立新的 TCP 連接效率較低。爲此 HTTP 提供了連接複用機制。用 Client.Do 方法可以手動實現請求複用。

client := &http.Client{}
req1, _ := http.NewRequest("GET", "http://example.com", nil)
resp1, _ := client.Do(req1)
// 處理響應1
req2, _ := http.NewRequest("GET", "http://example.com/about", nil)
resp2, _ := client.Do(req2) 
// 處理響應2

5.2 請求重試

要實現請求重試, 可在發送請求前檢查狀態, 錯誤超過一定次數則停止重試。這就可以包裝系統 HTTP client 實現請求重試邏輯。

// 自定義Client
type RetryClient struct {
    HttpClient *http.Client
    Retries int
}
func (c *RetryClient) Get(url string) (*http.Response, error) {
   for i := 0; i <= c.Retries; i++ {
      resp, err := c.HttpClient.Get(url)
      // 如果沒有錯誤就返回響應
      if err == nil { 
          return resp, nil 
      }
   }
   return nil, fmt.Errorf("request failed after %d retries", retries)
}

5.3 異步處理

對於耗時較長的請求, 可以啓動 Goroutine 異步發送, 這樣不會阻塞進程

func makeAsyncRequest() {
    go func() {
        resp, err := http.Get("http://example.com/data")
        // 處理響應
    }()
}

5.4 文件上傳

上傳文件需要設置請求爲 POST, 並將文件數據放到請求 body 發送

file, _ := os.Open("test.png")  
fileContents, _ := ioutil.ReadAll(file)
resp, _ := http.Post("http://example.com/upload", "image/png", bytes.NewReader(fileContents))

5.5 分塊上傳大文件

上傳超大文件時, 可以分塊逐步上傳

file, _ := os.Open("bigfile.iso") 
defer file.Close()
url := "http://example.com/upload"
buf := make([]byte, 1024) 
for {
  n, _ := file.Read(buf)
  if n == 0 {
      break
  }
}

6. 請求示例

6.1 POST JSON 數據

程序構造一個 JSON 對象, 以 POST 方式提交到服務器

func postJSON() {
    type Item struct {
        Name string `json:"name"`
        Price int `json:"price"`
    }
    itm := Item{"Apple", 5}
    jsonData, _ := json.Marshal(itm)
    resp, err := http.Post("https://api.example.com/create", 
        "application/json", 
        bytes.NewBuffer(jsonData))
}

6.2 文件下載

下載文件實際上就是發送 GET 請求, 然後保存響應 Body 到文件

func downloadFile() error {
    resp, err := http.Get("http://example.com/file.zip")
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    // 創建文件
    outFile, err := os.Create("./file.zip")
    if err != nil {
        return err
    }
    defer outFile.Close()
    // 寫入響應流到文件
    _, err = io.Copy(outFile, resp.Body)
    return err
}

7. HTTP 客戶端庫比較

除了官方的 net/http 包, Go 語言還有很多優秀的 HTTP 客戶端庫

  • resty: 簡單好用的 HTTP 客戶端, 使用方法類似於 jQuery。

  • heimdallr: 提供重試機制和超時控制的強大 HTTP 客戶端。

  • sling: 靈活構造 HTTP 請求的客戶端工具。

nKwWjB

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