Go http 包詳解

Go 語言中的 http 包提供了創建 http 服務或者訪問 http 服務所需要的能力,不需要額外的依賴。在這篇文章中,我們會介紹這些功能的使用,以及看一下 http 包的設計思路。

  1. http 的客戶端

1.1 發送普通請求

在 Go 語言中發送請求很簡單,如果不需要額外的配置,可以直接使用 http 包封裝的 http Client 發送請求,比如發送 GET 請求:

resp, _ := http.Get("https://golang.org")
defer resp.Body.Close()

發送 POST ,並攜帶 JSON 數據的請求:

data := make(map[string]string)
dataJson, _ := json.Marshal(data)
reader := bytes.NewBuffer(dataJson)
resp, _ := http.Post("https://golang.org", "application/json;charset=utf-8", reader)
defer resp.Body.Close()

發送 POST 表單請求:

resp, _ := http.PostForm("https://golang.org", url.Values{"username":{"rayjun"}, "password":{"password"}})
defer resp.Body.Close()

在每個請求發完之後,需要手動關閉響應。

1.2 客戶端配置

在實際使用的過程中,我們通常不會直接上面的方法,而是會自己做一些 Client 的配置,比如調整超時時間:

client := &http.Client{
  Timeout: 5 * time.Second,
}
resp, _ := client.Get("https://golang.org")
defer resp.Body.Close()

另外在很多時候,我們需要使用 GET 和 POST 之外的 http 方法,那就需要下面這樣的配置:

client := &http.Client{
  Timeout: 5 * time.Second,
}
req, _ := http.NewRequest("PUT", "https://golang.org", nil)
resp, _ := client.Do(req)
defer resp.Body.Close()

比如還需要在請求的 Header 中增加一些字段:

client := &http.Client{
  Timeout: 5 * time.Second,
}
req, _ := http.NewRequest("GET", "https://golang.org", nil)
req.Header.Add("User-Id", "userid123456")
resp, _ := client.Do(req)
defer resp.Body.Close()

或者更進一步,我們需要自定義傳輸層的一些配置:

tr := &http.Transport{
  MaxIdleConns:       10,
  IdleConnTimeout:    30 * time.Second,
  DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, _ := client.Get("https://golang.org")
defer resp.Body.Close()

http 包中發送請求,提供了不同層次的配置,滿足不同場景的使用。

  1. http 的服務端

除了客戶端,使用 http 包來創建 http 服務也很方便。

2.1 一行代碼創建 http 服務

創建一個 http 服務,在 Go 代碼中,只需要一行代碼:

func main() {
  http.ListenAndServe(":8080", nil)
}

在 main 方法中,寫下上面那行代碼,然後運行 main 方法,端口號爲 8080 的 http 服務就運行起來了, 但目前還處理不了任何請求。

2.2 添加請求路徑

在上面代碼的基礎上,需要添加一個路徑,這樣服務纔可以開始處理請求:

func main() {
  http.Handle("/index", &CustomerHandler{})
  http.ListenAndServe(":8080", nil)
}
type CustomerHandler struct {
}
func (c *CustomerHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
  fmt.Println("implement http server by self")
  writer.Write([]byte("server echo"))
}

添加了 /index 路徑,在這種方式下,需要爲每一個請求都定義一個 Handler,然後 Handler 需要實現 ServeHttp 方法。

Handler 是一個請求處理器,我們如果使用這種方式,就需要爲每一個請求的 url 實現一個 Handler,這樣實現很繁瑣。

但我們還有另一個選擇,就是使用 HandlerFunc,添加另外一個路徑:

func main() {
  http.HandleFunc("/index", func(writer http.ResponseWriter, request *http.Request) {
    writer.Write([]byte("HandleFunc implement"))
  })
  http.ListenAndServe(":8080", nil)
}

使用這種方式很簡潔,值需要實現 HandlerFunc 類型的一個匿名方法就可以了,HandlerFunc 是一個適配器,可以讓我們把一個與 ServeHTTP 簽名相同的函數作爲一個處理器。

Handler 和 HandlerFunc 都是通過 DefaultServeMux 來實現的。DefaultServeMux 纔是上面服務的核心。

在上面的代碼,http.ListenAndServe 的第二個參數傳入的是 nil,通常情況下,這個參數都是 nil,跟進代碼,發現這個參數爲 nil 的時候,就是使用 DefaultServeMux 來作爲服務端的實現:

// server.go
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  handler := sh.srv.Handler
  if handler == nil {
    handler = DefaultServeMux
  }
  if req.RequestURI == "*" && req.Method == "OPTIONS" {
    handler = globalOptionsHandler{}
  }
  handler.ServeHTTP(rw, req)
}

DefaultServeMux 的類型是 ServeMux,這是 Go 語言原生包中 http 服務端的默認實現。ServeMux 同樣實現了 ServeHttp 這個方法。

ServeHttp 方法纔是整個 http 服務的核心,只要需要處理請求,就必須實現這個方法。Handler 和 HandlerFunc 只是 Go 語言提供的兩種實現。

  1. http 的反向代理

反向代理在開發 Web 應用,特別是開發網關類應用的時候會經常用到, Go 也提供了實現,基本上開箱即用。

func main() {
  http.HandleFunc("/formawd", func(writer http.ResponseWriter, request *http.Request) {
    director := func(req *http.Request) {
      req.URL.Scheme = "https"
      req.URL.Host = "golang.org"
      req.URL.Path = "upload"
    }
    proxy := &httputil.ReverseProxy{Director: director}
    proxy.ServeHTTP(writer, request)
  })
  http.ListenAndServe(":8080", nil)
}

上面的代碼會把所有的請求都轉發到一個地方,當然也可以通過配置,將請求轉發到不同的地方。

  1. 小結

Go 語言原生的包就自帶了 http 包,這個包提供 http 編程所需要的基礎能力,開箱即用,不需要額外的依賴。在實際項目中使用,做個簡單的封裝即可。而且還自帶反向代理的能力,可以很方便的寫出一個 API 網關。

文 / Rayjun

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