網站首頁 編程語言 正文
Go語言處理web頁面請求
Request和Response
http Requset和Response的內容包括以下幾項:
- Request or response line
- Zero or more headers
- An empty line, followed by …
- … an optional message body
例如一個http Request:
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1 Host: www.w3.org User-Agent: Mozilla/5.0 (empty line)
如果是POST方法,在empty line后還包含請求體。
一個http Response:
HTTP/1.1 200 OK Content-type: text/html Content-length: 24204 (empty line) and then 24,204 bytes of HTML code
go http包分為兩種角色:http Client和http Server。http Client可以發送請求,比如寫爬蟲程序時語言扮演的角色就是http Client;http Server用來提供web服務,可以處理http請求并響應。
對于Request,作為http客戶端(如編寫爬蟲類工具)常需要關注的是URL和User-Agent以及其它幾個Header;作為http服務端(web服務端,處理請求)常需要關注的幾項是:
URL Header Body Form,、PostForm、MultipartForm
以下是完整的Request結構以及相關的函數、方法:混個眼熟就好了
type Request struct { Method string URL *url.URL Header Header Body io.ReadCloser GetBody func() (io.ReadCloser, error) // Server: x, Cleint: √ ContentLength int64 TransferEncoding []string Close bool // Server: x, Cleint: √ Host string Form url.Values PostForm url.Values MultipartForm *multipart.Form Trailer Header RemoteAddr string RequestURI string // x TLS *tls.ConnectionState Cancel <-chan struct{} // x Response *Response // x } func NewRequest(method, url string, body io.Reader) (*Request, error) func ReadRequest(b *bufio.Reader) (*Request, error) func (r *Request) AddCookie(c *Cookie) func (r *Request) BasicAuth() (username, password string, ok bool) func (r *Request) Context() context.Context func (r *Request) Cookie(name string) (*Cookie, error) func (r *Request) Cookies() []*Cookie func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) func (r *Request) FormValue(key string) string func (r *Request) MultipartReader() (*multipart.Reader, error) func (r *Request) ParseForm() error func (r *Request) ParseMultipartForm(maxMemory int64) error func (r *Request) PostFormValue(key string) string func (r *Request) ProtoAtLeast(major, minor int) bool func (r *Request) Referer() string func (r *Request) SetBasicAuth(username, password string) func (r *Request) UserAgent() string func (r *Request) WithContext(ctx context.Context) *Request func (r *Request) Write(w io.Writer) error func (r *Request) WriteProxy(w io.Writer) error
注意有哪些字段和方法,字段的詳細說明見go doc http.Request
。上面打了"x"的表示不需要了解的或者廢棄的。
有一個特殊的字段Trailer
,它是Header類型的,顯然它存放的是一個個請求header,它表示請求發送完成之后再發送的額外的header。對于Server來說,讀取了request.Body之后才會讀取Trailer。很少有瀏覽器支持HTTP Trailer功能。
以下是完整的Response結構以及相關的函數、方法:混個眼熟就好了
type Response struct { Status string // e.g. "200 OK" StatusCode int // e.g. 200 Proto string // e.g. "HTTP/1.0" ProtoMajor int // e.g. 1 ProtoMinor int // e.g. 0 Header Header Body io.ReadCloser ContentLength int64 TransferEncoding []string Close bool Uncompressed bool Trailer Header Request *Request TLS *tls.ConnectionState } func Get(url string) (resp *Response, err error) func Head(url string) (resp *Response, err error) func Post(url string, contentType string, body io.Reader) (resp *Response, err error) func PostForm(url string, data url.Values) (resp *Response, err error) func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) func (r *Response) Cookies() []*Cookie func (r *Response) Location() (*url.URL, error) func (r *Response) ProtoAtLeast(major, minor int) bool func (r *Response) Write(w io.Writer) error
其實有些直接從字面意思看就知道了。
Http Header
Request和Response結構中都有Header字段,Header是一個map結構。
type Header map[string][]string A Header represents the key-value pairs in an HTTP header. func (h Header) Add(key, value string) func (h Header) Del(key string) func (h Header) Get(key string) string func (h Header) Set(key, value string) func (h Header) Write(w io.Writer) error func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error
key是Header字段名,value是Header字段的值,同個字段多個值放在string的slice中。
Add()、Del()、Get()、Set()意義都很明確。
Write()是將Header寫進Writer中,比如從網絡連接中發送出去。WriteSubSet()和Write()類似,但可以指定exclude[headerkey]==true
排除不寫的字段。
下面是一個示例:
package main import ( "fmt" "net/http" ) func headers(w http.ResponseWriter, r *http.Request) { for key := range r.Header { fmt.Fprintf(w, "%s: %s\n", key, r.Header[key]) } fmt.Fprintf(w, "--------------\n") fmt.Fprintf(w, "the key: %s\n", r.Header["Accept-Encoding"]) fmt.Fprintf(w, "the key: %s\n", r.Header.Get("Accept-Encoding")) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/headers", headers) server.ListenAndServe() }
瀏覽器中訪問http://127.0.0.1:8080/headers
的結果:
Connection: [keep-alive] Cache-Control: [max-age=0] Upgrade-Insecure-Requests: [1] User-Agent: [Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 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] Accept-Language: [zh-CN,zh;q=0.9,en;q=0.8] -------------- the key: [gzip, deflate, br] the key: gzip, deflate, br
Http Body
Request和Response結構中都有Body字段,它們都是io.ReadCloser接口類型。從名字可以看出,io.ReadCloser由兩個接口組成:Reader和Closer,意味著它實現了Reader接口的Read()方法,也實現了Closer接口的Close()方法。這意味著Body的實例可以調用Read()方法,也可以調用Close()方法。
例如,下面寫一個handler,從請求中讀取Body并輸出:
package main import ( "fmt" "net/http" ) func body(w http.ResponseWriter, r *http.Request) { len := r.ContentLength body := make([]byte, len) r.Body.Read(body) fmt.Fprintf(w, "%s\n", string(body)) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/body", body) server.ListenAndServe() }
因為使用HTTP Get方法的Request沒有Body,所以這里使用curl的"-d"選項來構造一個POST請求,并發送Request Body:
$ curl -id "name=lognshuai&age=23" 127.0.0.1:8080/body HTTP/1.1 200 OK Date: Mon, 26 Nov 2018 09:04:40 GMT Content-Length: 22 Content-Type: text/plain; charset=utf-8 name=lognshuai&age=23
Go和HTML Form
在Request結構中,有3個和form有關的字段:
// Form字段包含了解析后的form數據,包括URL的query、POST/PUT提交的form數據 // 該字段只有在調用了ParseForm()之后才有數據 Form url.Values // PostForm字段不包含URL的query,只包括POST/PATCH/PUT提交的form數據 // 該字段只有在調用了ParseForm()之后才有數據 PostForm url.Values // Go 1.1 // MultipartForm字段包含multipart form的數據 // 該字段只有在調用了ParseMultipartForm()之后才有數據 MultipartForm *multipart.Form
所以,一般的邏輯是:
- 先調用ParseForm()或ParseMultipartForm()解析請求中的數據
- 按需訪問Request結構中的Form、PostForm或MultipartForm字段
除了先解析再訪問字段的方式,還可以直接使用Request的方法:
- FormValue(key)
- PostFormValue(key)
稍后解釋這兩個方法。
取Form和PostForm字段
給定一個html文件,這個html文件里是form表單:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Go Web</title> </head> <body> <form action=http://127.0.0.1:8080/process?name=xiaofang&boyfriend=longshuai method="post" enctype="application/x-www-form-urlencoded"> <input type="text" name="name" value="longshuai"/> <input type="text" name="age" value="23"/> <input type="submit"/> </form> </body> </html>
在這個form里,action指定了要訪問的url,其中path=process,query包含name和boyfriend兩個key。除此之外,form表單的input屬性里,也定義了name和age兩個key,由于method為post,這兩個key是作為request body發送的,且因為enctype指定為application/x-www-form-urlencoded
,這兩個key會按照URL編碼的格式進行組織。
下面是web handler的代碼:
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Fprintf(w, "%s\n", r.Form) fmt.Fprintf(w, "%s\n", r.PostForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
上面先使用ParseForm()方法解析Form,再訪問Request中的Form字段和PostForm字段。
打開前面的Html文件,點擊"提交"后,將輸出:
map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]] map[name:[longshuai] age:[23]]
如果這時,將application/x-www-form-urlencoded
改成multipart/form-data
,再點擊提交,將輸出:
map[name:[xiaofang] boyfriend:[longshuai]] map[]
顯然,使用multipart/form-data
編碼form的時候,編碼的內容沒有放進Form和PostForm字段中,或者說編碼的結果沒法放進這兩個字段中。
取MultipartForm字段
要取MultipartForm字段的數據,先使用ParseMultipartForm()方法解析Form,解析時會讀取所有數據,但需要指定保存在內存中的最大字節數,剩余的字節數會保存在臨時磁盤文件中。
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(1024) fmt.Fprintf(w,"%s\n",r.Form) fmt.Fprintf(w,"%s\n",r.PostForm) fmt.Fprintf(w,"%s\n",r.MultipartForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
將html文件的enctype改為multipart/form-data
后,重新點開html文件,將輸出:
map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]] map[name:[longshuai] age:[23]] &{map[name:[longshuai] age:[23]] map[]}
前兩行結果意味著ParseMultipartForm()方法也調用了ParseForm()方法,使得除了設置MultipartForm字段,也會設置Form字段和PostForm字段。
注意上面的第三行,返回的是一個struct,這個struct中有兩個map,第一個map是來自form的key/value,第二個map為空,這個見后面的File。
最后還需注意的是,enctype=multipart/form-data
和enctype=application/x-www-form-urlencoded
時,Request.Form字段中key的保存順序是不一致的:
// application/x-www-form-urlencoded map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]] // multipart/form-data map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]]
FormValue()和PostFormValue()
前面都是先調用ParseForm()或ParseMultipartForm()解析Form后再調用Request中對應字段的。還可以直接調用FormValue()或PostFormValue()方法。
- FormValue(key)
- PostFormValue(key)
這兩個方法在需要時會自動調用ParseForm()或ParseMultipartForm(),所以使用這兩個方法取Form數據的時候,可以不用顯式解析Form。
FormValue()返回form數據和url query組合后的第一個值。要取得完整的值,還是需要訪問Request.Form或Request.PostForm字段。但因為FormValue()已經解析過Form了,所以無需再顯式調用ParseForm()再訪問request中Form相關字段。
PostFormValue()返回form數據的第一個值,因為它只能訪問form數據,所以忽略URL的query部分。
先看FormValue()方法的使用。注意,下面調用了FormValue()之后沒有調用ParseForm()和ParseMultipartForm()解析Form,就可以直接訪問Request中和Form相關的3個字段。
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w,"%s\n",r.FormValue("name")) fmt.Fprintf(w,"%s\n",r.FormValue("age")) fmt.Fprintf(w,"%s\n",r.FormValue("boyfriend")) fmt.Fprintf(w,"%s\n",r.Form) fmt.Fprintf(w,"%s\n",r.PostForm) fmt.Fprintf(w,"%s\n",r.MultipartForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
當enctype=multipart/form-data
時,會自動調用ParseMultipartForm(),輸出結果:
xiaofang 23 longshuai map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]] map[name:[longshuai] age:[23]] &{map[name:[longshuai] age:[23]] map[]}
當enctype=application/x-www-form-urlencoded
時,會自動調用ParseForm(),輸出結果:
longshuai 23 longshuai map[name:[longshuai xiaofang] age:[23] boyfriend:[longshuai]] map[name:[longshuai] age:[23]] %!s(*multipart.Form=<nil>)
仍然注意,因為兩種enctype導致的Request.Form存儲key時的順序不一致,使得訪問有多個值的key得到的結果不一致。
再看PostFormValue()方法的使用。
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w,"%s\n",r.PostFormValue("name")) fmt.Fprintf(w,"%s\n",r.PostFormValue("age")) fmt.Fprintf(w,"%s\n",r.PostFormValue("boyfriend")) fmt.Fprintf(w,"%s\n",r.Form) fmt.Fprintf(w,"%s\n",r.PostForm) fmt.Fprintf(w,"%s\n",r.MultipartForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
當enctype=multipart/form-data
時,會自動調用ParseMultipartForm(),輸出結果:
longshuai 23 map[name:[xiaofang longshuai] boyfriend:[longshuai] age:[23]] map[name:[longshuai] age:[23]] &{map[name:[longshuai] age:[23]] map[]}
當enctype=application/x-www-form-urlencoded
時,會自動調用ParseForm(),輸出結果:
longshuai 23 map[age:[23] boyfriend:[longshuai] name:[longshuai xiaofang]] map[name:[longshuai] age:[23]] %!s(*multipart.Form=<nil>)
注意,由于PostFormValue()方法只能訪問form數據,上面調用了PostFormValue()后,無法使用PostFormValue()訪問URL中的query的Key/value,盡管request中的字段都合理設置了。
Files
multipart/form-data
最常用的場景可能是上傳文件,比如在form中使用file標簽。以下是html文件:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Go Web Programming</title> </head> <body> <form action=http://127.0.0.1:8080/process?name=xiaofang&boyfriend=longshuai method="post" enctype="multipart/form-data"> <input type="text" name="name" value="longshuai"/> <input type="text" name="age" value="23"/> <input type="file" name="file_to_upload"> <input type="submit"/> </form> </body> </html>
下面是服務端接收上傳文件數據的代碼:
package main import ( "fmt" "io/ioutil" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(1024) fileHeader := r.MultipartForm.File["file_to_upload"][0] file, err := fileHeader.Open() if err == nil { dataFromFile, err := ioutil.ReadAll(file) if err == nil { fmt.Fprintf(w, "%s\n", dataFromFile) } } fmt.Fprintf(w, "%s\n", r.MultipartForm) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/process", form) server.ListenAndServe() }
上面先調用ParseMultipartForm()解析multipart form,然后訪問request的MultipartForm字段,這個字段的類型是*multipart.Form
,該類型定義在mime/multipart/formdata.go文件中:
$ go doc multipart.Form package multipart // import "mime/multipart" type Form struct { Value map[string][]string File map[string][]*FileHeader }
Form類型表示解析后的multipart form,字段File和Value都是map類型的,其中File的map value是*FileHeader
類型:
$ go doc multipart.fileheader package multipart // import "mime/multipart" type FileHeader struct { Filename string Header textproto.MIMEHeader Size int64 // Has unexported fields. } A FileHeader describes a file part of a multipart request. func (fh *FileHeader) Open() (File, error)
它實現了Open()方法,所以可以直接調用Open()來打開multipart.Form的File部分。即:
fileHeader := r.MultipartForm.File["file_to_upload"][0] file, err := fileHeader.Open()
然后讀取這段數據,響應給客戶端。注意,有了File后,request.MultipartForm字段的第二個map就有了值,第二個map對應的就是multipart.Form.File的內容。
整個返回結果如下:
FormFile()
類似于FormValue()和PostFormValue()方法的便捷,讀取multipart.Form也有快捷方式:
$ go doc http.formfile func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) FormFile returns the first file for the provided form key. FormFile calls ParseMultipartForm and ParseForm if necessary.
FormFile()方法會在需要的時候自動調用parseMultipartForm()或ParseForm()。注意它的返回值。因為第一個返回值為multipart.File
,說明至少實現了io.Reader接口,可以直接讀取這個文件。
修改上一節的示例:
func form(w http.ResponseWriter, r *http.Request) { file, _, err := r.FormFile("file_to_upload") if err != nil { panic(err) } dataFromFile, err := ioutil.ReadAll(file) if err != nil { panic(err) } fmt.Fprintf(w, "%s\n", dataFromFile) }
ResponseWriter
ResponseWriter接口用于發送響應數據、響應header。它有3個方法:
type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(statusCode int) } A ResponseWriter interface is used by an HTTP handler to construct an HTTP response. A ResponseWriter may not be used after the Handler.ServeHTTP method has returned.
Header()用于構造response header,構造好的header會在稍后自動被WriteHeader()發送出去。比如設置一個Location字段:
w.Header().Set("Location", "http://google.com")
Write()用于發送響應數據,例如發送html格式的數據,json格式的數據等。
str := `<html> <head><title>Go Web Programming</title></head> <body><h1>Hello World</h1></body> </html>` w.Write([]byte(str))
WriteHeader(int)可以接一個數值HTTP狀態碼,同時它會將構造好的Header自動發送出去。如果不顯式調用WriteHeader(),會自動隱式調用并發送200 OK。
下面是一個示例:
package main import ( "fmt" "encoding/json" "net/http" ) func commonWrite(w http.ResponseWriter, r *http.Request) { str := `<html> <head> <title>Go Web</title> </head> <body> <h1>Hello World</h1> </body> </html>` w.Write([]byte(str)) } func writeHeader(w http.ResponseWriter,r *http.Request){ w.WriteHeader(501) fmt.Fprintln(w,"not implemented service") } func header(w http.ResponseWriter,r *http.Request){ w.Header().Set("Location","http://www.baidu.com") w.WriteHeader(302) } type User struct { Name string Friends []string } func jsonWrite(w http.ResponseWriter, r *http.Request) { var user = &User{ Name: "longshuai", Friends: []string{"personA", "personB", "personC"}, } w.Header().Set("Content-Type", "application/json") jsonData, _ := json.Marshal(user) w.Write(jsonData) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/commonwrite", commonWrite) http.HandleFunc("/writeheader", writeHeader) http.HandleFunc("/header", header) http.HandleFunc("/jsonwrite", jsonWrite) server.ListenAndServe() }
commonWrite()這個handler用于輸出帶html格式的數據。訪問結果:
writeheader()這個handler用于顯式發送501狀態碼。訪問結果:
$ curl -i 127.0.0.1:8080/writeheader HTTP/1.1 501 Not Implemented Date: Tue, 27 Nov 2018 03:36:57 GMT Content-Length: 24 Content-Type: text/plain; charset=utf-8 not implemented service
header()這個handler用于設置響應的Header,這里設置了302重定向,客戶端收到302狀態碼后會找Location字段的值,然后重定向到http://www.baidu.com
。
jsonWrite()這個handler用于發送json數據,所以發送之前先設置了Content-Type: application/json
。
原文鏈接:https://www.cnblogs.com/f-ck-need-u/p/10035801.html#request%E5%92%8Cresponse
相關推薦
- 2022-06-29 tomcat正常啟動但網頁卻無法訪問的幾種解決方法_Tomcat
- 2022-05-31 Python學習之yaml文件的讀取詳解_python
- 2023-02-25 如何用redis?setNX命令來加鎖_Redis
- 2022-09-08 Prometheus和NodeExporter安裝監控數據說明_其它綜合
- 2022-05-10 手寫Promise中all、race、any方法
- 2022-10-02 SQL堆疊注入簡介_MsSql
- 2023-02-15 Python二進制轉化為十進制數學算法詳解_python
- 2022-04-27 為ABP框架配置數據庫_基礎應用
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支