GRequests: 讓 HTTP 服務人類

熟悉我的讀者朋友們都知道,我早期是寫 Python 的,現在主力語言是 Go。開始接觸 Go 語言以後,我發現 Go 自帶的 net/http 請求庫不夠好用,好在我沒用 Go 寫過一行爬蟲代碼,平時 net/http 庫用的也就比較少,不是每天都用,也就忍了。

最近我在網上衝浪時,無意間發現了 grequests 這個庫,靈感來源於 Python 生態中大名鼎鼎的 requests 庫。顧名思義,這個庫就是用來發送 HTTP 請求的。grequests 的出現讓我眼前一亮,我的 Go 工具包又增添了一員,本文就來帶大家一起體驗下 grequests 庫的強大之處。

簡介

以前寫 Python 的時候,要說用起來最舒服的 HTTP 請求庫,當屬 requests 庫了,沒有之一。requests 庫官方文檔標題是 Requests: HTTP for Humans™,有人將其翻譯爲讓 HTTP 服務人類,這也是本文標題的由來,可見這個庫旨在降低我們在發送 HTTP 請求時寫代碼的心智負擔。

根據我的實際使用體驗,這個標題絕對不是在吹牛,requests 庫的 API 設計是真的符合直覺,也符合 Python 哲學。

grequests 即 Go 版本的 requests,官方介紹其爲:A Go "clone" of the great and famous Requests library,看來 grequests 庫是 requests 庫的復刻。

使用 net/http 發送 HTTP 請求

在具體介紹 grequests 庫的使用方法之前,我們先來回顧下 net/http 發送 HTTP 請求的用法,以此來對比 grequests 庫的強大。

使用 net/http 發送 HTTP GET 請求示例代碼如下:

// 發起 HTTP GET 請求
resp, _ := http.Get("https://httpbin.org/get")
defer resp.Body.Close() // 確保關閉響應體

// 讀取響應體內容
body, _ := io.ReadAll(resp.Body)

// 將響應體打印爲字符串
fmt.Println(string(body))

NOTE: 爲了保持代碼邏輯清晰,這裏只展示了代碼主邏輯,並且代碼中忽略了所有錯誤處理。後文中所有示例代碼都會如此,完整代碼可以在文末給出的示例代碼 GitHub 鏈接中獲取。

NOTE: 示例中請求的網址 httpbin.org 是一個叫 httpbin 的開源項目,它提供了一個可用於測試和調試 HTTP 客戶端(如瀏覽器、命令行工具、自定義腳本或任何發送 HTTP 請求的軟件)的 HTTP 服務。這個服務模擬了各種 HTTP 場景,可以幫助開發者測試自己的 HTTP 客戶端是否正確處理各種 HTTP 方法、響應等。httpbin 也是 requests 作者 Kenneth Reitz 開發並維護的項目。

使用 net/http 發送 HTTP POST 請求示例代碼如下:

// 創建一個要發送的數據結構並編碼爲 JSON
data := map[string]any{
    "username""user",
    "password""pass",
}
jsonData, _ := json.Marshal(data)

// 創建 POST 請求
url := "https://httpbin.org/post"
request, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonData))

// 添加請求頭,指明發送的內容類型爲 JSON
request.Header.Set("Content-Type""application/json")

// 發送請求並獲取響應
client := &http.Client{}
resp, _ := client.Do(request)
defer resp.Body.Close()

// 讀取響應體內容
body, _ := io.ReadAll(resp.Body)

// 將響應體打印爲字符串
fmt.Println(string(body))

可見,使用 net/http 發送 HTTP GET 請求的代碼還算友好,但是發送 POST 請求真的是過於繁瑣。

好在,現在我們有了 grequests

GRequests

要使用 grequests 庫就要先對其進行安裝:

$ go get -u github.com/levigross/grequests

極速開始

使用 grequests 發送 HTTP GET 請求示例代碼如下:

resp, _ := grequests.Get("https://httpbin.org/get", nil)
defer resp.Close() // 確保關閉響應體

fmt.Println(resp)
fmt.Println(resp.String()) // 實現了 `fmt.Stringer` 接口

沒錯,就是這麼簡單,僅需三行代碼即可打印響應結果。一切都是那麼的自然,符合直覺,這就是 requests API 的強大之處。

我們不再需要使用 io.ReadAll 去讀取 HTTP 響應體內容,*grequests.Response 提供了 String 方法,即實現了 fmt.Stringer 接口。

QueryString

grequests 庫發送 GET 請求時傳遞 QueryString 參數方式如下:

ro := &grequests.RequestOptions{
    Params: map[string]string{"Hello""Goodbye"},
}
resp, _ := grequests.Get("https://httpbin.org/get?Hello=World", ro)
defer resp.Close()

fmt.Println(resp)
fmt.Println(resp.RawResponse.Request.URL)

執行以上示例代碼,打印結果如下:

{
 "args"{
   "Hello""Goodbye"
 },
 "headers"{
   "Accept-Encoding""gzip",
   "Host""httpbin.org",
   "User-Agent""GRequests/0.10",
   "X-Amzn-Trace-Id""Root=1-6657cca7-715b811a08dff4420f487570"
 },
 "origin""69.28.52.250",
 "url""https://httpbin.org/get?Hello=Goodbye"
}

https://httpbin.org/get?Hello=Goodbye

可以發現,現在 url 已經被替換成了 https://httpbin.org/get?Hello=Goodbye ,可見 ro 會覆蓋 url 中的同名參數。

略顯遺憾的是,打印請求 URL 的代碼稍顯冗長:resp.RawResponse.Request.URL,如果能夠像 requests 庫那樣提供 resp.Url 屬性直接返回請求的 url 值就更友好了。

POST 請求

發送 POST 請求更能體現 grequests 庫的方便之處,示例代碼如下:

// 創建一個要發送的數據結構
postData := map[string]string{
    "username""user",
    "password""pass",
}

// 將數據結構編碼爲 JSON 並準備請求選項
ro := &grequests.RequestOptions{
    JSON: postData, // grequests 自動處理 JSON 編碼
    // Data: postData,
}

// 發起 POST 請求
resp, _ := grequests.Post("https://httpbin.org/post", ro)
defer resp.Close()

// 輸出響應體內容
fmt.Println("Response:", resp.String())

這個示例代碼明顯比使用 net/http 發送 POST 請求的示例代碼整潔不少。

grequests 會根據 *grequests.RequestOptions 的屬性值,自動處理請求頭中的 Content-Type

你可以自行嘗試把 grequests.RequestOptions 中的 JSON 屬性改爲 Data,即如下寫法:

ro := &grequests.RequestOptions{
    Data: postData,
}

看看打印結果如何。

HTTP Basic Auth

requests 庫官方文檔首頁,有一個示例展示瞭如何使用 requests 設置 Basic Auth 認證,簡潔優雅,截圖如下:

requests Basic Auth

現在我們嘗試用 grequests 來實現此功能:

ro := &grequests.RequestOptions{Auth: []string{"user""pass"}}
resp, _ := grequests.Get("https://httpbin.org/basic-auth/user/pass", ro)
defer resp.Close()

if resp.Ok != true {
    log.Println("Request did not return OK")
}

fmt.Println(resp.StatusCode)
fmt.Println(resp.Header.Get("content-type"))
fmt.Println(resp.String())

m := make(map[string]any)
_ = resp.JSON(&m)
fmt.Println(m)

可以發現,grequests 庫同樣能夠通過簡潔友好的方式來設置 Basic Auth 認證信息。

*grequests.Response 提供了 JSON 方法,可以將響應體的 JSON 信息反序列化到對象中。

相較於 requests 示例,grequests 唯一不足的是沒有提供 r.encoding 屬性可以方便獲取響應結果編碼格式。

上傳和下載文件

grequests 庫上傳和下載文件同樣非常簡單。

下載內容到文件:

resp, _ := grequests.Get("https://httpbin.org/get", nil)
defer resp.Close()

if resp.Ok != true {
    log.Println("Request did not return OK")
}

// 下載響應體內容到 result.json
_ = resp.DownloadToFile("result.json")

*grequests.Response 提供了 DownloadToFile 方法可以直接將響應體內容寫入指定文件。

上傳文件內容到服務端:

// 從 result.json 讀取文件內容並上傳到服務端
fd, _ := grequests.FileUploadFromDisk("result.json")

// This will upload the file as a multipart mime request
resp, _ := grequests.Post("https://httpbin.org/post",
    &grequests.RequestOptions{
        Files: fd,
        Data:  map[string]string{"One""Two"},
    })
defer resp.Close()

if resp.Ok != true {
    log.Println("Request did not return OK")
}

fmt.Println(resp)

上傳文件可以通過 *grequests.RequestOptionsFiles 屬性來傳遞,並且可以同時使用 Data 參數傳遞表單數據(form)。

總結

本文帶大家一起體驗了下 grequests 庫,展示了其強大之處。

相較於 Go 語言內置的 net/http 庫,grequests 的 API 設計更爲優雅易用,尤其從 POST 請求的對比中可以發現 grequests 的 API 要簡潔不少。

grequests 庫能夠以簡潔友好的方式設置 Basic Auth 認證信息。並且,上傳和下載文件同樣只需要很少的代碼即可實現。

這些強大之處的靈感,都來源於 Python 生態中的 requests 庫。

當然,grequests 也有一些小遺憾,它並沒有將 requests 庫的全部便捷功能複製過來。但總體而言,grequests 的出現讓我們在 Go 語言中發送 HTTP 請求方便了不少。

grequests 庫更多用法可以參考官方文檔。

本文示例源碼我都放在了 GitHub 中,歡迎點擊查看。

希望此文能對你有所啓發。

延伸閱讀

聯繫我

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