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:
1
2
3
4
|
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1 Host: www.w3.org User-Agent: Mozilla/5.0 (empty line) |
如果是POST方法,在empty line后还包含请求体。
一个http Response:
1
2
3
4
5
|
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服务端,处理请求)常需要关注的几项是:
1
2
3
4
|
URL Header Body Form,、PostForm、MultipartForm |
以下是完整的Request结构以及相关的函数、方法:混个眼熟就好了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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结构以及相关的函数、方法:混个眼熟就好了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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结构。
1
2
3
4
5
6
7
8
9
|
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
排除不写的字段。
下面是一个示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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
的结果:
1
2
3
4
5
6
7
8
9
10
|
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并输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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:
1
2
3
4
5
6
7
|
$ 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有关的字段:
1
2
3
4
5
6
7
8
9
10
11
|
// 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表单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
< 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的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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文件,点击"提交"后,将输出:
1
2
|
map [name:[longshuai xiaofang] age:[ 23 ] boyfriend:[longshuai]] map [name:[longshuai] age:[ 23 ]] |
如果这时,将application/x-www-form-urlencoded
改成multipart/form-data
,再点击提交,将输出:
1
2
|
map [name:[xiaofang] boyfriend:[longshuai]] map [] |
显然,使用multipart/form-data
编码form的时候,编码的内容没有放进Form和PostForm字段中,或者说编码的结果没法放进这两个字段中。
取MultipartForm字段
要取MultipartForm字段的数据,先使用ParseMultipartForm()方法解析Form,解析时会读取所有数据,但需要指定保存在内存中的最大字节数,剩余的字节数会保存在临时磁盘文件中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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文件,将输出:
1
2
3
|
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的保存顺序是不一致的:
1
2
3
4
5
|
// 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个字段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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(),输出结果:
1
2
3
4
5
6
|
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(),输出结果:
1
2
3
4
5
6
|
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()方法的使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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(),输出结果:
1
2
3
4
5
6
|
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(),输出结果:
1
2
3
4
5
6
|
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文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
< 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 > |
下面是服务端接收上传文件数据的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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文件中:
1
2
3
4
5
6
7
|
$ 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
类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ 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部分。即:
1
2
|
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也有快捷方式:
1
2
3
4
|
$ 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接口,可以直接读取这个文件。
修改上一节的示例:
1
2
3
4
5
6
7
8
9
10
11
12
|
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个方法:
1
2
3
4
5
6
7
8
9
10
|
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字段:
1
|
w.Header().Set( "Location" , "http://google.com" ) |
Write()用于发送响应数据,例如发送html格式的数据,json格式的数据等。
1
2
3
4
5
|
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。
下面是一个示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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状态码。访问结果:
1
2
3
4
5
6
7
|
$ 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页面请求的方法请查看下面的相关链接
原文链接:https://www.cnblogs.com/f-ck-need-u/p/10035801.html#request%E5%92%8Cresponse