go語言的 net/http包提供了一系列用於表示HTTP報文的結構,可以使用它處理請求和發送響應,其中Request結構代表了客戶端發送的請求報文
type Request
type Request struct {
// Method指定HTTP方法(GET、POST、PUT等)。對客戶端,""代表GET。
Method string
// URL在服務端表示被請求的URI,在客戶端表示要訪問的URL。
//
// 在服務端,URL字段是解析請求行的URI(保存在RequestURI字段)得到的,
// 對大多數請求來說,除了Path和RawQuery之外的字段都是空字符串。
// (參見RFC 2616, Section 5.1.2)
//
// 在客戶端,URL的Host字段指定了要連接的服務器,
// 而Request的Host字段(可選地)指定要發送的HTTP請求的Host頭的值。
URL *url.URL
// 接收到的請求的協議版本。本包生產的Request總是使用HTTP/1.1
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
// Header字段用來表示HTTP請求的頭域。如果頭域(多行鍵值對格式)爲:
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// Connection: keep-alive
// 則:
// Header = map[string][]string{
// "Accept-Encoding": {"gzip, deflate"},
// "Accept-Language": {"en-us"},
// "Connection": {"keep-alive"},
// }
// HTTP規定頭域的鍵名(頭名)是大小寫敏感的,請求的解析器通過規範化頭域的鍵名來實現這點。
// 在客戶端的請求,可能會被自動添加或重寫Header中的特定的頭,參見Request.Write方法。
Header Header
// Body是請求的主體。
//
// 在客戶端,如果Body是nil表示該請求沒有主體買入GET請求。
// Client的Transport字段會負責調用Body的Close方法。
//
// 在服務端,Body字段總是非nil的;但在沒有主體時,讀取Body會立刻返回EOF。
// Server會關閉請求的主體,ServeHTTP處理器不需要關閉Body字段。
Body io.ReadCloser
// ContentLength記錄相關內容的長度。
// 如果爲-1,表示長度未知,如果>=0,表示可以從Body字段讀取ContentLength字節數據。
// 在客戶端,如果Body非nil而該字段爲0,表示不知道Body的長度。
ContentLength int64
// TransferEncoding按從最外到最裏的順序列出傳輸編碼,空切片表示"identity"編碼。
// 本字段一般會被忽略。當發送或接受請求時,會自動添加或移除"chunked"傳輸編碼。
TransferEncoding []string
// Close在服務端指定是否在回覆請求後關閉連接,在客戶端指定是否在發送請求後關閉連接。
Close bool
// 在服務端,Host指定URL會在其上尋找資源的主機。
// 根據RFC 2616,該值可以是Host頭的值,或者URL自身提供的主機名。
// Host的格式可以是"host:port"。
//
// 在客戶端,請求的Host字段(可選地)用來重寫請求的Host頭。
// 如過該字段爲"",Request.Write方法會使用URL字段的Host。
Host string
// Form是解析好的表單數據,包括URL字段的query參數和POST或PUT的表單數據。
// 本字段只有在調用ParseForm後纔有效。在客戶端,會忽略請求中的本字段而使用Body替代。
Form url.Values
// PostForm是解析好的POST或PUT的表單數據。
// 本字段只有在調用ParseForm後纔有效。在客戶端,會忽略請求中的本字段而使用Body替代。
PostForm url.Values
// MultipartForm是解析好的多部件表單,包括上傳的文件。
// 本字段只有在調用ParseMultipartForm後纔有效。
// 在客戶端,會忽略請求中的本字段而使用Body替代。
MultipartForm *multipart.Form
// Trailer指定了會在請求主體之後發送的額外的頭域。
//
// 在服務端,Trailer字段必須初始化爲只有trailer鍵,所有鍵都對應nil值。
// (客戶端會聲明哪些trailer會發送)
// 在處理器從Body讀取時,不能使用本字段。
// 在從Body的讀取返回EOF後,Trailer字段會被更新完畢幷包含非nil的值。
// (如果客戶端發送了這些鍵值對),此時纔可以訪問本字段。
//
// 在客戶端,Trail必須初始化爲一個包含將要發送的鍵值對的映射。(值可以是nil或其終值)
// ContentLength字段必須是0或-1,以啓用"chunked"傳輸編碼發送請求。
// 在開始發送請求後,Trailer可以在讀取請求主體期間被修改,
// 一旦請求主體返回EOF,調用者就不可再修改Trailer。
//
// 很少有HTTP客戶端、服務端或代理支持HTTP trailer。
Trailer Header
// RemoteAddr允許HTTP服務器和其他軟件記錄該請求的來源地址,一般用於日誌。
// 本字段不是ReadRequest函數填寫的,也沒有定義格式。
// 本包的HTTP服務器會在調用處理器之前設置RemoteAddr爲"IP:port"格式的地址。
// 客戶端會忽略請求中的RemoteAddr字段。
RemoteAddr string
// RequestURI是被客戶端發送到服務端的請求的請求行中未修改的請求URI
// (參見RFC 2616, Section 5.1)
// 一般應使用URI字段,在客戶端設置請求的本字段會導致錯誤。
RequestURI string
// TLS字段允許HTTP服務器和其他軟件記錄接收到該請求的TLS連接的信息
// 本字段不是ReadRequest函數填寫的。
// 對啓用了TLS的連接,本包的HTTP服務器會在調用處理器之前設置TLS字段,否則將設TLS爲nil。
// 客戶端會忽略請求中的TLS字段。
TLS *tls.ConnectionState
}
Request類型代表一個服務端接受到的或者客戶端發送出去的HTTP請求。
Request各字段的意義和用途在服務端和客戶端是不同的。除了字段本身上方文檔,還可參見Request.Write方法和RoundTripper接口的文檔
獲取請求URL
Request結構中的URL字段用於表示請求行中包含的URL,改字段是一個指向url.URL結構的指針
type URL
type URL struct {
Scheme string
Opaque string //編碼的不透明數據
User *Userinfo //用戶名和密碼信息
Host string //主機或主機:端口
Path string //路徑(相對路徑可能省略前斜槓)
RawPath string //編碼路徑提示(請參閱EscapedPath方法)
ForceQuery bool //追加查詢('?'),即使RawQuery爲空
RawQuery string //編碼的查詢值,不帶'?'
Fragment string //用於引用的片段,不帶'#'
}
表示的一般形式是
[scheme:][//[userinfo@]host][/]path[?query][#fragment]
scheme 後 不以斜槓開頭的URL解釋爲
scheme:opaque[?query][#fragment]
請注意,Path字段以解碼形式存儲:/%47%6f%2f變爲/ Go /。結果是,無法確定路徑中的斜線是原始URL中的斜線,以及哪個是%2f。這種區別很少很重要,但是在這種情況下,代碼應使用RawPath,這是一個可選字段,僅當默認編碼與Path不同時才設置該字段
- Path字段
通過 r.URL.Path 只能得到 /hello
- RawQuery字段
- 獲取請求的URL後面?後面的查詢字符串
http://localhost:8080/hello?username=admin&password=123456
通過 r.URL.RawQuery 得到的是 username=admin&password=123456
package main
import (
"fmt"
"net/http"
)
//創建處理器函數
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "你發送的請求的請求地址是:", r.URL.Path)
fmt.Fprintln(w, "你發送的請求的請求地址後的查詢字符串是:", r.URL.RawQuery)
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":8080", nil)
}
獲取請求頭中的信息
通過Request結果中的Header字段用來獲取請求頭中的所有信息,Header字段的類型Header類型,而Header類型是一個map[string][]string, string類型的key,string切片類型的值
- type Header
type Header map[string][]string
Header代表HTTP頭域的鍵值對。
- func (Header) Get
func (h Header) Get(key string) string
Get返回鍵對應的第一個值,如果鍵不存在會返回""。如要獲取該鍵對應的值切片,請直接用規範格式的鍵訪問map。
- func (Header) Set
func (h Header) Set(key, value string)
Set添加鍵值對到h,如鍵已存在則會用只有新值一個元素的切片取代舊值切片。
- func (Header) Add
func (h Header) Add(key, value string)
Add添加鍵值對到h,如鍵已存在則會將新的值附加到舊值切片後面。
- func (Header) Del
func (h Header) Del(key string)
Del刪除鍵值對。
- func (Header) Write
func (h Header) Write(w io.Writer) error
Write以有線格式將頭域寫入w
- 獲取請求頭中的所有信息
r.Header
結果
map[Connection:[keep-alive] Cache-Control:[max-age=0] User-Agent:[Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36] Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8] Accept-Encoding:[gzip, deflate, br] Upgrade-Insecure-Requests:[1] Accept-Language:[zh-CN,zh;q=0.9]]
- 獲取請求頭中的某個具體屬性的值,如 獲取 Accept-Encoding的值
- 方式1: r.Header[“Accept-Encoding”]
得到的是一個字符串切片
結果
[gzip, deflate, br]
- 方式2: r.Header.Get(“Accept-Encoding”)
得到的是字符串形式的值,多個值使用逗號分隔
結果
gzip, deflate, br
package main
import (
"fmt"
"net/http"
)
//創建處理器函數
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "你發送的請求的請求地址是:", r.URL.Path)
fmt.Fprintln(w, "你發送的請求的請求地址後的查詢字符串是:", r.URL.RawQuery)
fmt.Fprintln(w, "請求頭中的所有信息:", r.Header)
fmt.Fprintln(w, "請求頭中Accept-Encoding的信息:", r.Header["Accept-Encoding"])
fmt.Fprintln(w, "請求頭中Accept-Encoding的屬性值是:", r.Header.Get("Accept-Encoding"))
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":8080", nil)
}
獲取請求體中的信息
請求和響應的主體都是有Request結構中的Body字段表示,這個字段的類型是 io.ReadCloser接口,該接口包含了Reader接口和Closer接口,Reader接口擁有Read方法,Closer接口擁有Close方法
- type ReadCloser
type ReadCloser interface {
Reader
Closer
}
ReadCloser 接口組合了基本的 Read 和 Close 方法
- type Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader 接口包裝了基本的 Read 方法。
Read 將 len§ 個字節讀取到 p 中。它返回讀取的字節數 n(0 <= n <= len§) 以及任何遇到的錯誤。即使 Read 返回的 n < len§,它也會在調用過程中使用 p 的全部作爲暫存空間。若一些數據可用但不到 len§ 個字節,Read 會照例返回可用的東西, 而不是等待更多。
當 Read 在成功讀取 n > 0 個字節後遇到一個錯誤或 EOF 情況,它就會返回讀取的字節數。 它會從相同的調用中返回(非nil的)錯誤或從隨後的調用中返回錯誤(和 n == 0)。 這種一般情況的一個例子就是 Reader 在輸入流結束時會返回一個非零的字節數, 可能的返回不是 err == EOF 就是 err == nil。無論如何,下一個 Read 都應當返回 0, EOF。
調用者應當總在考慮到錯誤 err 前處理 n > 0 的字節。這樣做可以在讀取一些字節, 以及允許的 EOF 行爲後正確地處理I/O錯誤。
Read 的實現在 len§ == 0 以外的情況下會阻止返回零字節的計數和 nil 錯誤, 調用者應將返回 0 和 nil 視作什麼也沒有發生;特別是它並不表示 EOF
- type Closer
type Closer interface {
Close() error
}
Closer 接口包裝了基本的 Close 方法。
Close 的行爲在第一次調用後沒有定義。具體實現可能有自己的行爲描述
1.由於GET請求沒有請求體,所以需要在HTML頁面中創建一個form表單,通過指定 method=“post” 來發送一個POST請求
表單
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<form action="http://localhost:8080/hello" method="POST">
用戶名: <input type="text" name="username" /><br/>
密碼: <input type="password" name="password" /><br/>
<input type="submit" />
</form>
</body>
</html>
服務器處理請求代碼
package main
import (
"fmt"
"net/http"
)
//創建處理器函數
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "你發送的請求的請求地址是:", r.URL.Path)
fmt.Fprintln(w, "你發送的請求的請求地址後的查詢字符串是:", r.URL.RawQuery)
fmt.Fprintln(w, "請求頭中的所有信息:", r.Header)
fmt.Fprintln(w, "請求頭中Accept-Encoding的信息:", r.Header["Accept-Encoding"])
fmt.Fprintln(w, "請求頭中Accept-Encoding的屬性值是:", r.Header.Get("Accept-Encoding"))
//獲取請求體內容的長度
len := r.ContentLength
//創建byte切片
body := make([]byte, len)
//將請求體中的內容讀到body中
r.Body.Read(body)
//在瀏覽器中顯示請求體中的內容
fmt.Fprintln(w, "請求體中的內容有: ", string(body))
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":8080", nil)
}
瀏覽器顯示結果
請求體中的內容有: username=admin&password=123456
獲取請求參數
form字段
- 類型是 url.Values類型,form是解析好的表單數據,包括URL字段的query參數和POST或PUT的表單數據
- type Values
type Values map[string][]string
值將字符串鍵映射到值列表。它通常用於查詢參數和表單值。與http.Header映射不同,Values映射中的鍵區分大小寫
- form字段只有在調用Request的ParseForm方法後纔有效。在客戶端,會忽略請求中的本字段而使用Body替代
- func (*Request) ParseForm
func (r *Request) ParseForm() error
ParseForm解析URL中的查詢字符串,並將解析結果更新到r.Form字段。
對於POST或PUT請求,ParseForm還會將body當作表單解析,並將結果既更新到r.PostForm也更新到r.Form。解析結果中,POST或PUT請求主體要優先於URL查詢字符串(同名變量,主體的值在查詢字符串的值前面)
如果請求的主體的大小沒有被MaxBytesReader函數設定限制,其大小默認限制爲開頭10MB
ParseMultipartForm會自動調用ParseForm。重複調用本方法是無意義的
- 獲取表單中提交的請求參數(username和password)
package main
import (
"fmt"
"net/http"
)
//創建處理器函數
func handler(w http.ResponseWriter, r *http.Request) {
//解析表單,在調用r.Form之前必須執行該操作
r.ParseForm()
//獲取請求參數
fmt.Fprintln(w, "請求參數有:", r.Form)
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":8080", nil)
}
表單
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<form action="http://localhost:8080/hello?username=admin&password=123456" method="POST">
用戶名: <input type="text" name="username"><br/>
密碼: <input type="password" name="password"><br/>
<input type="submit" />
</form>
</body>
</html>
運行結果:
請求參數有: map[username:[linux admin] password:[888 123456]]
發現: 表單中的請求參數username和URL中的請求參數username都獲取到了,而且表單中的請求參數的值排在URL請求參數值的前面
PostForm字段
- 類型也是 url.Values 類型,用來獲取表單中的請求參數
package main
import (
"fmt"
"net/http"
)
//創建處理器函數
func handler(w http.ResponseWriter, r *http.Request) {
//解析表單,在調用r.Form之前必須執行該操作
r.ParseForm()
//獲取請求參數
fmt.Fprintln(w, "請求參數有:", r.Form)
fmt.Fprintln(w, "POST請求的form表單中的請求參數有:", r.PostForm)
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":8080", nil)
}
運行結果:
請求參數有: map[username:[admin admin] password:[123 123456]]
POST請求的form表單中的請求參數有: map[username:[admin] password:[123]]
- 但是PostForm字段只支持 application/x-www-form-urlencoded 編碼,如果form表單的enctype屬性值爲 multipart/form-data, 那麼使用PostForm字段無法獲取表單中的數據,此時需要使用 MultipartForm 字段
說明: form表單的 enctype 屬性的默認值是 application/x-www-form-urlencoded 編碼,實現上傳文件時需要將該屬性的值設置爲 multipart/form-data 編碼格式
FormValue方法和 PostFormValue方法
- FormValue方法
可以通過FormValue 方法快速獲取某一個請求參數,該方法調用之前會自動調用 ParseMultipartForm 和 ParseForm方法對錶單進行解析
- func (*Request) FormValue
func (r *Request) FormValue(key string) string
FormValue返回key爲鍵查詢r.Form字段得到結果[]string切片的第一個值。POST和PUT主體中的同名參數優先於URL查詢字符串。如果必要,本函數會隱式調用ParseMultipartForm和ParseForm
- PostFormValue方法
可以通過PostFormValue方法快速獲取表單中的某一個請求參數,該方法調用之前會自動調用ParseMultipartForm 和 ParseForm方法對錶單進行解析
- func (*Request) PostFormValue
func (r *Request) PostFormValue(key string) string
PostFormValue返回key爲鍵查詢r.PostForm字段得到結果[]string切片的第一個值。如果必要,本函數會隱式調用ParseMultipartForm和ParseForm
代碼
package main
import (
"fmt"
"net/http"
)
//創建處理器函數
func handler(w http.ResponseWriter, r *http.Request) {
//通過直接調用FormValue方法和PostFormValue方法直接獲取請求參數的值
fmt.Fprintln(w, "URL中的user請求參數的值:", r.FormValue("user"))
fmt.Fprintln(w, "Form表單中的username請求參數的值:", r.PostFormValue("username"))
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":8080", nil)
}
表單
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<form action="http://localhost:8080/hello?user=wuxing&password=666" method="POST" enctype="application/x-www-form-urlencoded">
用戶名: <input type="text" name="username"><br/>
密碼: <input type="password" name="password"><br/>
<input type="submit" />
</form>
</body>
</html>
運行結果:
URL中的user請求參數的值: wuxing
Form表單中的username請求參數的值: admin
MultipartForm字段
爲了取得 multipart/form-data編碼的表單數據,需要用到Request結構的 ParseMultipartForm方法和MultipartForm字段,通常上傳文件時會將form表單的 enctype屬性值設置爲 multipart/form-data
- func (*Request) ParseMultipartForm
func (r *Request) ParseMultipartForm(maxMemory int64) error
ParseMultipartForm將請求的主體作爲multipart/form-data解析。請求的整個主體都會被解析,得到的文件記錄最多maxMemery字節保存在內存,其餘部分保存在硬盤的temp文件裏。如果必要,ParseMultipartForm會自行調用ParseForm。重複調用本方法是無意義的
給客戶端響應
- type ResponseWriter
type ResponseWriter interface {
// Header返回一個Header類型值,該值會被WriteHeader方法發送。
// 在調用WriteHeader或Write方法後再改變該對象是沒有意義的。
Header() Header
// WriteHeader該方法發送HTTP回覆的頭域和狀態碼。
// 如果沒有被顯式調用,第一次調用Write時會觸發隱式調用WriteHeader(http.StatusOK)
// WriterHeader的顯式調用主要用於發送錯誤碼。
WriteHeader(int)
// Write向連接中寫入作爲HTTP的一部分回覆的數據。
// 如果被調用時還未調用WriteHeader,本方法會先調用WriteHeader(http.StatusOK)
// 如果Header中沒有"Content-Type"鍵,
// 本方法會使用包函數DetectContentType檢查數據的前512字節,將返回值作爲該鍵的值。
Write([]byte) (int, error)
}
ResponseWriter接口被HTTP處理器用於構造HTTP回覆
- 給客戶端響應一個字符串
- 處理器中的代碼
package main
import (
"net/http"
)
//創建處理器函數
func testString(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("你的請求我已經收到!"))
}
func main() {
http.HandleFunc("/testStr", testString)
http.ListenAndServe(":8080", nil)
}
- 瀏覽器中的結果
你的請求我已經收到!
- 響應報文中的內容
HTTP/1.1 200 OK
Date: Thu, 02 Apr 2020 05:18:15 GMT
Content-Length: 30
Content-Type: text/plain; charset=utf-8
- 給客戶端響應一個HTML頁面
- 處理器中的代碼
package main
import (
"net/http"
)
//創建處理器函數
func testHtml(w http.ResponseWriter, r *http.Request) {
html := `
<html>
<head>
<title>測試響應內容爲網頁</title>
<meta charset="utf-8"/>
</head>
<body>
我是以網頁的形式響應過來的!
</body>
</html>`
w.Write([]byte(html))
}
func main() {
http.HandleFunc("/html", testHtml)
http.ListenAndServe(":8080", nil)
}
- 瀏覽器中的結果
我是以網頁的形式響應過來的!
- 響應報文中的內容
HTTP/1.1 200 OK
Date: Thu, 02 Apr 2020 05:25:31 GMT
Content-Length: 174
Content-Type: text/html; charset=utf-8
- 給客戶端響應JSON格式數據
- 處理器端代碼
user.go
package model
//User 結構體
type User struct {
ID int
Username string
Password string
Email string
}
main.go
package main
import (
"encoding/json"
"fmt"
"goweb/web02_req/model"
"net/http"
)
//創建處理器函數
func testJsonRes(w http.ResponseWriter, r *http.Request) {
//設置響應內容的類型
w.Header().Set("Content-Type", "application/json")
//創建User
user := model.User{
ID: 1,
Username: "admin",
Password: "123456",
Email: "[email protected]",
}
//將User轉換爲Json格式
json, _ := json.Marshal(user)
//將json格式的數據響應給客戶端
w.Write(json)
}
func main() {
http.HandleFunc("/testJson", testJsonRes)
http.ListenAndServe(":8080", nil)
}
- 瀏覽器中的結果
{"ID":1,"Username":"admin","Password":"123456","Email":"[email protected]"}
- 響應報文中的內容
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 02 Apr 2020 04:17:34 GMT
Content-Length: 74
- 讓客戶端重定向
- 處理器端代碼
package main
import (
"net/http"
)
//創建處理器函數
func testRedire(w http.ResponseWriter, r *http.Request) {
//設置響應頭中的Location屬性
w.Header().Set("Location", "https://www.baidu.com")
//設置響應的狀態碼
w.WriteHeader(302)
}
func main() {
http.HandleFunc("/testRedirect", testRedire)
http.ListenAndServe(":8080", nil)
}
- 響應報文中的內容
HTTP/1.1 302 Found
Location: https://www.baidu.com
Date: Thu, 02 Apr 2020 04:30:42 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8