<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
http Requset和Response的內容包括以下幾項:
例如一個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
其實有些直接從字面意思看就知道了。
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: %sn", key, r.Header[key]) } fmt.Fprintf(w, "--------------n") fmt.Fprintf(w, "the key: %sn", r.Header["Accept-Encoding"]) fmt.Fprintf(w, "the key: %sn", 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
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, "%sn", 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
在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
所以,一般的邏輯是:
除了先解析再存取欄位的方式,還可以直接使用Request的方法:
稍後解釋這兩個方法。
給定一個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, "%sn", r.Form) fmt.Fprintf(w, "%sn", 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欄位的資料,先使用ParseMultipartForm()方法解析Form,解析時會讀取所有資料,但需要指定儲存在記憶體中的最大位元組數,剩餘的位元組數會儲存在臨時磁碟檔案中。
package main import ( "fmt" "net/http" ) func form(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(1024) fmt.Fprintf(w,"%sn",r.Form) fmt.Fprintf(w,"%sn",r.PostForm) fmt.Fprintf(w,"%sn",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]]
前面都是先呼叫ParseForm()或ParseMultipartForm()解析Form後再呼叫Request中對應欄位的。還可以直接呼叫FormValue()或PostFormValue()方法。
這兩個方法在需要時會自動呼叫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,"%sn",r.FormValue("name")) fmt.Fprintf(w,"%sn",r.FormValue("age")) fmt.Fprintf(w,"%sn",r.FormValue("boyfriend")) fmt.Fprintf(w,"%sn",r.Form) fmt.Fprintf(w,"%sn",r.PostForm) fmt.Fprintf(w,"%sn",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,"%sn",r.PostFormValue("name")) fmt.Fprintf(w,"%sn",r.PostFormValue("age")) fmt.Fprintf(w,"%sn",r.PostFormValue("boyfriend")) fmt.Fprintf(w,"%sn",r.Form) fmt.Fprintf(w,"%sn",r.PostForm) fmt.Fprintf(w,"%sn",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中的欄位都合理設定了。
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, "%sn", dataFromFile) } } fmt.Fprintf(w, "%sn", 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的內容。
整個返回結果如下:
類似於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, "%sn", dataFromFile) }
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
。
更多關於Go語言使用Request,Response處理web頁面請求的方法請檢視下面的相關連結
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45