(轉)解密 Golang 的 Request 物件:深入理解 HTTP 請求的關鍵

liujiacai發表於2024-03-20

原文:https://juejin.cn/post/7234322338824552505

在 Golang 中,net/http 包是用於構建 HTTP 客戶端和伺服器的重要包之一。在處理 HTTP 請求時,Request 物件是不可或缺的。本文將深入探討 Golang 中的 Request 物件,並從多個方面介紹其功能、結構和使用方法。

1. Request 物件簡介

Request 物件代表一個 HTTP 請求,它包含了請求的所有資訊,如請求方法、URL、頭部資訊、請求體等。在 Golang 中,Request 物件是透過 net/http 包中的 NewRequest 函式建立的。我們可以透過以下方式建立一個 Request 物件:

css
複製程式碼
req, err := http.NewRequest(method, url, body)

其中,method 是一個字串,表示 HTTP 請求方法,如 GET、POST、PUT 等;url 是一個字串,表示請求的 URL;body 是一個 io.Reader 介面型別的物件,用於提供請求體的內容。這個函式返回一個 Request 物件和一個錯誤。

一旦建立了 Request 物件,我們就可以使用它來進行各種操作,如設定請求頭、讀取請求引數、獲取請求體等。接下來,我們將深入探討這些操作。

2. Request 物件的結構

在理解 Request 物件的功能之前,我們首先來看一下它的結構。在 Golang 中,Request 物件的定義如下:

go
複製程式碼
type Request struct {
    Method           string
    URL              *url.URL
    Proto            string
    ProtoMajor       int
    ProtoMinor       int
    Header           Header
    Body             io.ReadCloser
    ContentLength    int64
    TransferEncoding []string
    Close            bool
    Host             string
    Form             url.Values
    PostForm         url.Values
    MultipartForm    *multipart.Form
    Trailer          Header
    RemoteAddr       string
    RequestURI       string
    TLS              *tls.ConnectionState
    Cancel           <-chan struct{}
    Response         *Response
    ctx              context.Context
}

Request 物件的結構中包含了多個欄位,每個欄位都承載著不同的資訊。下面我們將逐一介紹這些欄位的含義和用法。

  • Method 欄位表示請求的方法,如 GET、POST、PUT 等。可以透過 req.Method 獲取該欄位的值。
  • URL 欄位表示請求的 URL。可以透過 req.URL 獲取該欄位的值,它是一個指向 url.URL 型別的指標。url.URL 型別提供了一系列方法來獲取 URL 的各個部分,如 Scheme、Host、Path 等。
  • Proto 欄位表示 HTTP 協議的版本,如 "HTTP/1.1"。可以透過 req.Proto 獲取該欄位的值。
  • ProtoMajor 和 ProtoMinor 欄位表示 HTTP 協議版本的主要和次要版本號,如 1 和 1。可以透過 req.ProtoMajor 和 req.ProtoMinor 獲取這兩個欄位的值。
  • Header 欄位表示請求頭資訊,它是一個 http.Header 型別的物件。我們可以使用 req.Header 物件來設定、獲取和刪除請求頭的各個欄位。
  • Body 欄位表示請求體,它是一個實現了 io.ReadCloser 介面的物件。可以透過 req.Body 獲取該欄位的值,並使用相應的方法來讀取請求體的內容。
  • ContentLength 欄位表示請求體的長度,單位是位元組。可以透過 req.ContentLength 獲取該欄位的值。
  • TransferEncoding 欄位表示請求體的傳輸編碼方式,如 "chunked"。可以透過 req.TransferEncoding 獲取該欄位的值。
  • Close 欄位表示請求是否需要關閉連線。可以透過 req.Close 獲取該欄位的值。
  • Host 欄位表示請求的主機名。可以透過 req.Host 獲取該欄位的值。
  • Form 和 PostForm 欄位用於儲存請求的表單資料。它們都是 url.Values 型別的物件,提供了一系列方法來獲取表單資料。
  • MultipartForm 欄位用於儲存請求的多部分表單資料。它是一個 multipart.Form 型別的指標,提供了一系列方法來獲取多部分表單資料。
  • Trailer 欄位表示請求尾部的頭部資訊。它是一個 http.Header 型別的物件,與 Header 欄位類似。
  • RemoteAddr 欄位表示請求的遠端地址。可以透過 req.RemoteAddr 獲取該欄位的值。
  • RequestURI 欄位表示請求的 URI。可以透過 req.RequestURI 獲取該欄位的值。
  • TLS 欄位表示請求的 TLS 連線狀態。它是一個 *tls.ConnectionState 型別的指標,可以用於獲取有關 TLS 連線的資訊。
  • Cancel 欄位表示一個通道,用於取消請求。可以透過 req.Cancel 獲取該欄位的值。
  • Response 欄位表示與請求關聯的響應物件。它是一個 *http.Response 型別的指標。
  • ctx 欄位表示請求的上下文。可以透過 req.Context() 獲取該欄位的值。

以上就是 Request 物件的結構及其各個欄位的含義。接下來,我們將介紹如何使用 Request 物件進行操作。

3. Request 物件的操作

3.1 請求 URL

Request 物件中的 URL 屬性表示請求的 URL。它是一個指向 url.URL 型別的指標,用於儲存和操作 URL 相關的資訊。

url.URL 型別是 Golang 標準庫中的一個結構體,它包含了 URL 的各個組成部分,如協議 scheme、主機 host、路徑 path、查詢引數 query 等。url.URL 型別提供了一系列方法來訪問和修改 URL 的各個部分。

下面我們來看一下 url.URL 型別的定義:

go
複製程式碼
type URL struct {
    Scheme     string
    Opaque     string    // 不透明的部分,用於協議特定的解析
    User       *Userinfo // 使用者名稱和密碼資訊
    Host       string    // 主機(host 或 host:port)
    Path       string    // 路徑
    RawPath    string    // 編碼的路徑(含特殊字元)
    RawQuery   string    // 編碼的查詢引數(含特殊字元)
    Fragment   string    // 片段識別符號
    ForceQuery bool      // 強制使用查詢引數
}

URL 結構體中的欄位表示了 URL 的各個部分,下面我們逐一介紹這些欄位的含義和用法:

  • Scheme 欄位表示 URL 的協議部分,如 http、https 等。可以透過 url.Scheme 來獲取或設定該欄位的值。
  • Opaque 欄位用於協議特定的解析,一般用於非標準協議。對於常見的 HTTP 和 HTTPS 協議,該欄位為空字串。
  • User 欄位表示 URL 中的使用者名稱和密碼資訊,是一個指向 url.Userinfo 型別的指標。可以透過 url.User 來獲取或設定該欄位的值。
  • Host 欄位表示 URL 的主機部分,可以包含主機名和埠號。可以透過 url.Host 來獲取或設定該欄位的值。
  • Path 欄位表示 URL 的路徑部分,如 /path/to/resource。可以透過 url.Path 來獲取或設定該欄位的值。
  • RawPath 欄位表示編碼後的路徑部分,包含特殊字元的轉義表示。可以透過 url.RawPath 來獲取或設定該欄位的值。
  • RawQuery 欄位表示編碼後的查詢引數部分,包含特殊字元的轉義表示。可以透過 url.RawQuery 來獲取或設定該欄位的值。
  • Fragment 欄位表示 URL 的片段識別符號,如 #fragment。可以透過 url.Fragment 來獲取或設定該欄位的值。
  • ForceQuery 欄位表示是否強制使用查詢引數。當該欄位為 true 時,即使沒有查詢引數,URL 也會以 ? 開頭。預設情況下,該欄位為 false。

過 URL 結構體的欄位,我們可以輕鬆地訪問和操作 URL 的各個部分。這在處理請求中的 URL 引數、構建新的 URL 等場景中非常有用。

下面是一些示例程式碼,展示瞭如何使用 URL 屬性的不同方法:

go
複製程式碼
package main
import (
    "fmt"
    "net/http"
)
func main() {
    // 建立一個 GET 請求
    req, err := http.NewRequest("GET", "https://example.com/path?param=value", nil)
    if err != nil {
        fmt.Println("建立請求失敗:", err)
        return
    }
    // 獲取 URL 的各個部分
    fmt.Println("協議:", req.URL.Scheme)             // 輸出: https
    fmt.Println("主機:", req.URL.Host)               // 輸出: example.com
    fmt.Println("路徑:", req.URL.Path)               // 輸出: /path
    fmt.Println("查詢引數:", req.URL.RawQuery)       // 輸出: param=value
    fmt.Println("片段識別符號:", req.URL.Fragment)     // 輸出: 空字串
    fmt.Println("使用者名稱:", req.URL.User.Username()) // 輸出: 空字串
    // 修改 URL 的部分內容
    req.URL.Scheme = "http"
    req.URL.Host = "google.com"
    req.URL.Path = "/search"
    req.URL.RawQuery = "q=golang"
    req.URL.Fragment = "top"
    // 獲取修改後的 URL
    fmt.Println("修改後的 URL:", req.URL.String()) // 輸出: http://google.com/search?q=golang#top
}

在上述示例中,我們首先建立了一個 GET 請求物件 req,其 URL 為 example.com/path?param=…。然後,我們使用 req.URL 屬性來訪問和操作 URL 的各個部分。透過 req.URL.Scheme、req.URL.Host、req.URL.Path、req.URL.RawQuery 和 req.URL.Fragment,我們分別獲取了 URL 的協議、主機、路徑、查詢引數和片段識別符號的值。此外,我們還透過 req.URL.User.Username() 獲取了 URL 中的使用者名稱(在這個示例中為空字串)。

接下來,我們對 URL 的各個部分進行了修改。透過賦值操作,我們改變了 URL 的協議為 http,主機為 google.com,路徑為 /search,查詢引數為 q=golang,片段識別符號為 top。最後,我們使用 req.URL.String() 方法獲取修改後的 URL 的字串表示。

3.2 設定請求頭

Request 物件的 Header 欄位用於儲存請求頭資訊。我們可以使用該欄位來設定、獲取和刪除請求頭的各個欄位。

javascript
複製程式碼
req.Header.Set("Content-Type", "application/json")
req.Header.Add("Authorization", "Bearer token")
req.Header.Del("User-Agent")

上述程式碼示例中,Set 方法用於設定指定欄位的值,Add 方法用於新增新的欄位和值,Del 方法用於刪除指定欄位。透過這些方法,我們可以輕鬆地操作請求頭資訊。

以下是一個示例程式碼,演示如何處理請求頭:

go
複製程式碼
import (
    "net/http"
    "fmt"
)
func main() {
    req, err := http.NewRequest("GET", "https://example.com", nil)
    if err != nil {
        fmt.Println("建立請求失敗:", err)
        return
    }
    req.Header.Set("Content-Type", "application/json")
    req.Header.Add("Authorization", "Bearer mytoken")
    fmt.Println("Content-Type:", req.Header.Get("Content-Type"))
    fmt.Println("Authorization:", req.Header.Get("Authorization"))
    // 處理請求...
}

在上述程式碼中,我們首先使用 Set 方法設定了 Content-Type 和 Authorization 兩個請求頭的值。然後使用 Get 方法獲取了這兩個請求頭的值,並列印輸出。

3.3 讀取請求引數

在 HTTP 請求中,引數通常以查詢字串或表單的形式傳送。我們可以透過 URL 欄位來獲取請求的查詢引數,透過 Form 和 PostForm 欄位來獲取表單引數。

css
複製程式碼
query := req.URL.Query().Get("key")
formValue := req.Form.Get("field")
postFormValue := req.PostForm.Get("field")

以上程式碼示例演示瞭如何讀取請求的查詢引數、表單引數和 POST 表單引數的值。透過這些方法,我們可以方便地處理請求中的引數。

3.4 獲取請求體內容

有些 HTTP 請求需要在請求體中傳輸資料,例如 POST 請求。Request 物件的 Body 屬性是一個 io.ReadCloser 型別,顧名思義,這個型別實現了 io.Reader 和 io.Closer 介面,表示請求的主體資料。我們可以透過 Body 屬性來讀取請求體的內容。以下是一個示例程式碼:

go
複製程式碼
import (
    "net/http"
    "fmt"
    "io/ioutil"
)
func main() {
    req, err := http.NewRequest("POST", "https://example.com", nil)
    if err != nil {
        fmt.Println("建立請求失敗:", err)
        return
    }
    req.Header.Set("Content-Type", "application/json")
    // 模擬請求體資料
    requestBody := `{"name": "John", "age": 30}`
    req.Body = ioutil.NopCloser(strings.NewReader(requestBody))
    // 讀取請求體
    body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        fmt.Println("讀取請求體失敗:", err)
        return
    }
    fmt.Println("請求體:", string(body))
    // 處理請求...
}

在上述程式碼中,我們首先使用 ioutil.NopCloser 函式將請求體資料封裝成一個 io.ReadCloser 型別,然後將其賦值給 Request 物件的 Body 屬性。接著使用 ioutil.ReadAll 函式讀取請求體的內容,並列印輸出。

3.5 其他操作

除了上述介紹的操作之外,Request 物件還提供了其他一些方法和欄位,如:

  • WithContext 方法用於返回一個新的 Request 物件,該物件與原始請求物件共享相同的上下文。
  • WithCancel 方法用於返回一個新的 Request 物件,該物件與原始請求物件共享相同的取消通道。
  • WithContext 和 WithCancel 方法可以用於處理請求的上下文和取消操作。
  • Close 方法用於關閉請求的主體。當不再需要讀取請求主體時,我們可以呼叫該方法來關閉連線。
  • ParseMultipartForm 方法用於解析多部分表單資料。在處理包含檔案上傳的表單時,我們可以使用該方法來解析請求體。
  • FormFile 方法用於獲取上傳的檔案。在處理包含檔案上傳的表單時,我們可以使用該方法來獲取上傳的檔案。
  • Referer 和 UserAgent 欄位分別表示請求的引用頁面和使用者代理資訊。
  • 透過上述方法和欄位,我們可以更靈活地操作和處理 Request 物件。

4. 總結

透過本文的介紹,我們從多個方面深入理解了 Golang 中的 Request 物件。我們詳細介紹了 Request 物件的結構、欄位含義和用法,並演示瞭如何設定請求頭、讀取請求引數和獲取請求體內容。此外,我們還透過原始碼分析的方式進一步瞭解了 Request 物件的實現原理。

Request 物件在 Golang 的 HTTP 程式設計中起著重要的作用,它提供了處理和操作 HTTP 請求的豐富功能。深入理解 Request 物件將幫助我們更好地使用 Golang 構建高效的 HTTP 客戶端和伺服器。

希望本文對你理解和應用 Golang 的 Request 物件有所幫助。

相關文章