get請求可以傳body嗎_詳解用 Go 語言解析各種 HTTP 請求的方法


之前這個系列的文章一直在講用 Go 語言怎么編寫HTTP服務器來提供服務,如何給服務器配置路由來匹配請求到對應的處理程序,如何添加中間件把一些通用的處理任務從具體的Handler中解耦出來,以及如何更規范地在項目中應用數據庫。不過一直漏掉了一個環節是服務器接收到請求后如何解析請求拿到想要的數據, Go 語言使用 net/http 包中的 Request 結構體對象來表示 HTTP 請求,通過 Request 結構對象上定義的方法和數據字段,應用程序能夠便捷地訪問和設置 HTTP 請求中的數據。

95ec6c67091b842953418f143856b977.png

一般服務端解析請求的需求有如下幾種

  • HTTP請求頭中的字段值
  • URL 查詢字符串中的字段值
  • 請求體中的 Form 表單數據
  • 請求體中的 JSON 格式數據
  • 讀取客戶端的上傳的文件

今天這篇文章我們就按照這幾種常見的服務端對 HTTP 請求的操作來說一下服務器應用程序如何通過 Request 對象解析請求頭和請求體。

Request 結構定義

在說具體操作的使用方法之前我們先來看看 net/http 包中 Request 結構體的定義,了解一下 Request 擁有什么樣的數據結構。 Request 結構在源碼中的定義如下。

我們快速地了解一下每個字段大致的含義,了解了每個字段的含義在不同的應用場景下需要讀取訪問 HTTP 請求的不同部分時就能夠有的放矢了。

Method

指定HTTP方法(GET,POST,PUT等)。

URL

URL指定要請求的URI(對於服務器請求)或要訪問的URL(用於客戶請求)。它是一個表示 URL 的類型 url.URL 的指針, url.URL 的結構定義如下:

Proto

Proto , ProtoMajor , ProtoMinor 三個字段表示傳入服務器請求的協議版本。對於客戶請求,這些字段將被忽略。 HTTP 客戶端代碼始終使用 HTTP/1.1 或 HTTP/2 。

Header

Header 包含服務端收到或者由客戶端發送的 HTTP 請求頭,該字段是一個 http.Header 類型的指針, http.Header 類型的聲明如下:

是 map[string][]string 類型的別名, http.Header 類型實現了 GET , SET , Add 等方法用於存取請求頭。如果服務端收到帶有如下請求頭的請求:

那么 Header 的值為:

對於傳入的請求, Host 標頭被提升為 Request.Host 字段,並將其從 Header 對象中刪除。 HTTP 定義頭部的名稱是不區分大小寫的。 Go 使用 CanonicalHeaderKey 實現的請求解析器使得請求頭名稱第一個字母以及跟隨在短橫線后的第一個字母大寫其他都為小寫形式,比如: Content-Length 。對於客戶端請求,某些標頭,例如 Content-Length 和 Connection 會在需要時自動寫入,並且標頭中的值可能會被忽略。

Body

這個字段的類型是 io.ReadCloser , Body 是請求的主體。對於客戶端發出的請求, nil 主體表示該請求沒有 Body ,例如 GET 請求。 HTTP 客戶端的傳輸會負責調用 Close 方法。對於服務器接收的請求,請求主體始終為非 nil ,但如果請求沒有主體,則將立即返回 EOF 。服務器將自動關閉請求主體。服務器端的處理程序不需要關心此操作。

GetBody

客戶端使用的方法的類型,其聲明為:

ContentLength

ContentLength 記錄請求關聯內容的長度。值-1表示長度未知。值>=0表示從 Body 中讀取到的字節數。對於客戶請求,值為0且非 nil 的 Body 也會被視為長度未知。

TransferEncoding

TransferEncoding 為字符串切片,其中會列出從最外層到最內層的傳輸編碼, TransferEncoding 通常可以忽略;在發送和接收請求時,分塊編碼會在需要時自動被添加或者刪除。

Close

Close 表示在服務端回復請求或者客戶端讀取到響應后是否要關閉連接。對於服務器請求,HTTP服務器會自動處理 並且處理程序不需要此字段。對於客戶請求,設置此字段為 true 可防止重復使用到相同主機的請求之間的TCP連接,就像已設置 Transport.DisableKeepAlives 一樣。

Host

對於服務器請求, Host 指定URL所在的主機,為防止DNS重新綁定攻擊,服務器處理程序應驗證 Host 標頭具有的值。 http 庫中的 ServeMux (復用器)支持注冊到特定 Host 的模式,從而保護其注冊的處理程序。對於客戶端請求, Host 可以用來選擇性地覆蓋請求頭中的 Host ,如果不設置, Request.Write 使用 URL.Host 來設置請求頭中的 Host 。

Form

Form 包含已解析的表單數據,包括 URL 字段的查詢參數以及 PATCH , POST 或 PUT 表單數據。此字段僅在調用 Request.ParseForm 之后可用。 HTTP 客戶端會忽略 Form 並改用 Body 。 Form 字段的類型是 url.Values 類型的指針。 url.Values 類型的聲明如下:

也是 map[string][]string 類型的別名。 url.Values 類型實現了 GET , SET , Add , Del 等方法用於存取表單數據。

PostForm

PostForm 類型與 Form 字段一樣,包含來自 PATCH , POST 的已解析表單數據或PUT主體參數。此字段僅在調用 ParseForm 之后可用。 HTTP 客戶端會忽略 PostForm 並改用 Body 。

MultipartForm

MultipartForm 是已解析的多部分表單數據,包括文件上傳。僅在調用 Request.ParseMultipartForm 之后,此字段才可用。 HTTP 客戶端會忽略 MultipartForm 並改用 Body 。該字段的類型是 *multipart.Form 。

RemoteAddr

RemoteAddr 允許 HTTP 服務器和其他軟件記錄發送請求的網絡地址,通常用於記錄。 net/http 包中的HTTP服務器在調用處理程序之前將 RemoteAddr 設置為“ IP:端口”, HTTP客戶端會忽略此字段。

RequestURI

RequestURI 是未修改的 request-target 客戶端發送的請求行(RFC 7230,第3.1.1節)。在服務器端,通常應改用URL字段。在HTTP客戶端請求中設置此字段是錯誤的。

Response

Response 字段類型為 *Response ,它指定了導致此請求被創建的重定向響應,此字段僅在客戶端發生重定向時被填充。

ctx

ctx 是客戶端上下文或服務器上下文。它應該只通過使用 WithContext 復制整個 Request 進行修改。這個字段未導出以防止人們錯誤使用 Context 並更改同一請求的調用方所擁有的上下文。

讀取請求頭

上面分析了 Go 將 HTTP 請求頭存儲在 Request 結構體對象的 Header 字段里, Header 字段實質上是一個 Map ,請求頭的名稱為Map key , MapValue 的類型為字符串切片,有的請求頭像 Accept 會有多個值,在切片中就對應多個元素。

Header 類型的 Get 方法可以獲取請求頭的第一個值,

或者是獲取值時直接通過 key 獲取對應的切片值就好,比如將上面的改為:

下面我們寫個遍歷請求頭信息的示例程序,同時也會通上面介紹的 Request 結構中定義的 Method , URL , Host , RemoteAddr 等字段把請求的通用信息打印出來。在我們一直使用的 http_demo 項目中增加一個 DisplayHeadersHandler ,其源碼如下:

將其處理程序綁定到 /index/display_headers 路由上:

然后啟動項目,打開瀏覽器訪問:

可以看到如下輸出:

34b6895fdd9180535e713132edf5cb0c.png

http_demo 項目中已經添加了本文中所有示例的源碼,公眾號內回復 gohttp06 可以獲取源碼的下載鏈接。

獲取URL參數值

GET 請求中的 URL 查詢字符串中的參數可以通過 url.Query() ,我們來看一下啊 url.Query() 函數的源碼:

它通過 ParseQuery 函數解析 URL 參數然后返回一個 url.Values 類型的值。 url.Values 類型上面我們已經介紹過了是 map[string][]string 類型的別名,實現了 GET , SET , Add , Del 等方法用於存取數據。

所以我們可以使用 r.URL.Query().Get("ParamName") 獲取參數值,也可以使用 r.URL.Query()["ParamName"] 。兩者的區別是 Get 只返回切片中的第一個值,如果參數對應多個值時(比如復選框表單那種請求就是一個 name 對應多個值),記住要使用第二種方式。

我們通過運行一個示例程序 display_url_params.go 來看一下兩種獲取 URL 參數的區別

將其處理程序綁定到 /index/display_url_params 路由上:

打開瀏覽器訪問

瀏覽器會輸出:

我們為參數 a 傳遞了兩個參數值,可以看到通過 url.Query.Get() 只能讀取到第一個參數值。

獲取表單中的參數值

Request 結構的 Form 字段包含已解析的表單數據,包括 URL 字段的查詢參數以及 PATCH , POST 或 PUT 表單數據。此字段僅在調用 Request.ParseForm 之后可用。不過 Request 對象提供一個 FormValue 方法來獲取指定名稱的表單數據, FormValue 方法會根據 Form 字段是否有設置來自動執行 ParseForm 方法。

可以看到 FormValue 方法也是只返回切片中的第一個值。如果需要獲取字段對應的所有值,那么需要通過字段名訪問 Form 字段。如下:

獲取表單字段的單個值

獲取表單字段的多個值

下面是我們的示例程序,以及對應的路由:

我們在命令行中使用 cURL 命令發送表單數據到處理程序,看看效果。

返回的響應如下:

獲取Cookie

Request 對象專門提供了一個 Cookie 方法用來訪問請求中攜帶的 Cookie 數據,方法會返回一個 *Cookie 類型的值以及 error 。 Cookie 類型的定義如下:

所以要讀取請求中指定名稱的 Cookie 值,只需要

Request.Cookies() 方法會返回 []*Cookie 切片,其中會包含請求中所有的 Cookie

下面的示例程序,會打印請求中所有的 Cookie

我們通過 cURL 在命令行請求 http://localhost:8000/index/read_cookie

執行命令后會返回:

解析請求體中的JSON數據

現在前端都傾向於把請求數據以 JSON 格式放到請求主體中傳給服務器,針對這個使用場景,我們需要把請求體作為 json.NewDecoder() 的輸入流,然后將請求體中攜帶的 JSON 格式的數據解析到聲明的結構體變量中

在命令行里用 cURL 命令測試我們的程序:

返回響應如下:

讀取上傳文件

服務器接收客戶端上傳的文件,使用 Request 定義的 FormFile() 方法。該方法會自動調用 r.ParseMultipartForm(32<<20) 方法解析請求多部表單中的上傳文件,並把文件可讀入內存的大小設置為 32M (32向左位移20位),如果內存大小需要單獨設置,就要在程序里單獨調用 ParseMultipartForm() 方法才行。

Go語言解析 HTTP 請求比較常用的方法我們都介紹的差不多了。因為想總結全一點,篇幅還是有點長,不過整體不難懂,而且也可以下載程序中的源碼自己運行調試,動手實踐一下更有助於理解吸收。 HTTP 客戶端發送請求要設置的內容也只今天講的 Request 結構體的字段, Request 對象也提供了一些設置相關的方法供開發人員使用,今天就先說這么多了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM