使用 Go 發送 HTTP 請求

前言

標準庫 net/http 發送 HTTP 請求時,會執行以下 3 個步驟:

  • 建構請求:調用 http.NewRequesthttp.NewRequestWithContext 函式,根據傳入的 Context、Method、URL 和 Request Body 等參數創建一個請求。
  • 開始事務:調用 http.Transport.RoundTrip 開啟 HTTP 事務、獲取連接並發送請求。
  • 等待響應:在 HTTP 持久連接的 http.persistConn.writeLoop 中等待響應。

介面

標準庫 net/http 實現了以下幾個重要的介面:

  • http.RoundTripper:用於發出一個 HTTP 請求,客戶端將 Request 物件做為參數傳入,就會發出該請求,並從服務端獲取相對應的響應或錯誤。稱之為「往返者」。
1
2
3
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}
  • http.Handler:用於響應客戶端發出的請求,實現了處理請求的實際商業邏輯,最後還會調用 http.ResponseWriter 介面的方法,來創造一個相應的響應或錯誤。稱之為:「處理器」。
1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
  • http.ResponseWriter:提供了三個方法 HeaderWriteWriteHeader 分別用於獲取 HTTP 響應頭、響應主體和設置 Status Code。
1
2
3
4
5
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}

結構體

標準庫 net/http 實現了以下幾個重要的結構體:

  • http.Client:表示 HTTP 客戶端,實現了包括 Cookies 和重定向等協議內容。默認使用 http.DefaultTransport,也可以自訂一個 Client
  • http.Transport:實現了 http.RoundTripper 介面,包括:連接重用、建構請求、發送請求和 HTTP Proxy 等功能。
  • http.persistConn:是 TCP 協議長連接功能的封裝,做為客戶端與服務端交換 HTTP Message(消息)的控制(handle)。

做法

發送一個簡單的 HTTP 請求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"bytes"
"fmt"
"log"
"net/http"
"reflect"
)

func main() {
resp, err := http.Get("http://google.com/")

if err != nil {
log.Println(err)
return
}

defer resp.Body.Close()

headers := resp.Header

for k, v := range headers {
fmt.Printf("k=%v, v=%v\n", k, v)
}

fmt.Printf("resp Status %s\n", resp.Status)
fmt.Printf("resp StatusCode %d\n", resp.StatusCode)
fmt.Printf("resp Proto %s\n", resp.Proto)
fmt.Printf("resp ContentLength %d\n", resp.ContentLength)
fmt.Printf("resp TransferEncoding %v\n", resp.TransferEncoding)
fmt.Printf("resp Uncompressed %t\n", resp.Uncompressed)

fmt.Println(reflect.TypeOf(resp.Body)) // *http.gzipReader

buf := bytes.NewBuffer(make([]byte, 0, 512))
length, _ := buf.ReadFrom(resp.Body)

fmt.Println(len(buf.Bytes()))
fmt.Println(length)
fmt.Println(string(buf.Bytes()))
}

使用 http.NewRequest 創建一個 Request 物件,再透過 http.Client 執行這個 Request 物件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
"bytes"
"fmt"
"log"
"net/http"
"reflect"
)

func main() {
client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, "http://google.com/", nil)

if err != nil {
fmt.Println("Fatal error ", err.Error())
}

resp, err := client.Do(req)

if err != nil {
log.Println(err)
return
}

defer resp.Body.Close()

headers := resp.Header

for k, v := range headers {
fmt.Printf("k=%v, v=%v\n", k, v)
}

fmt.Printf("resp Status %s\n", resp.Status)
fmt.Printf("resp StatusCode %d\n", resp.StatusCode)
fmt.Printf("resp Proto %s\n", resp.Proto)
fmt.Printf("resp ContentLength %d\n", resp.ContentLength)
fmt.Printf("resp TransferEncoding %v\n", resp.TransferEncoding)
fmt.Printf("resp Uncompressed %t\n", resp.Uncompressed)

fmt.Println(reflect.TypeOf(resp.Body)) // *http.gzipReader

buf := bytes.NewBuffer(make([]byte, 0, 512))
length, _ := buf.ReadFrom(resp.Body)

fmt.Println(len(buf.Bytes()))
fmt.Println(length)
fmt.Println(string(buf.Bytes()))
}

參考資料