《Go Web 编程》之第4章 处理请求
《Go Web 编程》之第4章 处理请求
- 第4章 处理请求
- 4.1 请求和响应
- 4.1.1 Request结构
- 4.1.2 请求URL
- 4.1.3 请求首部
- 4.1.4 请求主体
- 4.2 Go与HTML表单
- 4.2.1 Form字段
- 4.2.2 PostForm字段
- 4.2.3 MultipartForm字段
- 4.2.4 文件
- 4.2.5 处理含JSON主体的POST请求
- 4.3 ResponseWriter
- 4.3.1 Write方法
- 4.3.2 WriteHeader方法
- 4.3.2 Header方法
- 4.3.4 完整JSON响应
- 4.4 cookie
- 4.4.1Go与cookie
- 4.4.2 发送cookie至浏览器
- 4.4.3 从浏览器获取cookie
- 4.4.4 cookie实现闪现消息
第4章 处理请求
4.1 请求和响应
HTTP报文在客户端和服务器间传递消息,分为HTTP请求和HTTP响应,结构如下:
(1)请求行或响应行;
(2)零或多个首部;
(3)一个空行;
(4)可选报文主体。
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1
Host: www.w3.org
User-Agent: Mozilla/5.0
(empty line)
4.1.1 Request结构
type Request struct {Method stringURL *url.URLProto string // "HTTP/1.0"ProtoMajor int // 1ProtoMinor int // 0Header HeaderBody io.ReadCloserGetBody func() (io.ReadCloser, error)ContentLength int64TransferEncoding []stringClose boolHost stringForm url.ValuesPostForm url.ValuesMultipartForm *multipart.FormTrailer HeaderRemoteAddr stringRequestURI stringTLS *tls.ConnectionStateCancel <-chan struct{}Response *Responsectx context.Context
}
4.1.2 请求URL
type URL struct {Scheme stringOpaque string // encoded opaque dataUser *Userinfo // username and password informationHost string // host or host:portPath string // path (relative paths may omit leading slash)RawPath string // encoded path hint (see EscapedPath method)ForceQuery bool // append a query ('?') even if RawQuery is emptyRawQuery string // encoded query values, without '?'Fragment string // fragment for references, without '#'RawFragment string // encoded fragment hint (see EscapedFragment method)
}
//URL一般格式:
scheme://[userinfo@]host/path[?query][#fragment]
//解析为:
scheme:opaque[?query][#fragment]
http://www.example.com/post?id=123&thread_id=456
//RawQuery为
id=123&thread_id=456
浏览器向服务器发送请求时会剔除URL中的片段部分,服务器接收到URL中无Fragment字段。
4.1.3 请求首部
type Header map[string][]string
添加、删除、获取和设置。
package mainimport ("fmt""net/http"
)func headers(w http.ResponseWriter, r *http.Request) {h := r.Header//h := r.Header["Accept-Encoding"]//h := r.Header.Get("Accept-Encoding")fmt.Fprintln(w, h)
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/headers", headers)server.ListenAndServe()
}
4.1.4 请求主体
Body io.ReadCloser
GET请求无报文主体。
package mainimport ("fmt""net/http"
)func body(w http.ResponseWriter, r *http.Request) {len := r.ContentLengthbody := make([]byte, len)r.Body.Read(body)fmt.Fprintln(w, string(body))
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/body", body)server.ListenAndServe()
}
curl -id "first_name=sausheong&last_name=chang" 127.0.0.1:8080/body
4.2 Go与HTML表单
POST请求基本通过HTML表单发送:
<form action="/process" method="post"><input type="text" name="first_name"/><input type="text" name="last_name"/><input type="submit"/>
</form>
表单输入数据以键值形式记录在请求主体中。
决定POST请求发送键值对时格式的HTML表单内容类型(content type),由表单的enctype属性(默认"application/x-www-form-urlencoded")指定。
<form action="/process" method="post" enctype="application/x-www-form-urlencoded"><input type="text" name="first_name"/><input type="text" name="last_name"/><input type="submit"/>
</form>
浏览器至少支持application/x-www-form-urlencoded和multipart/form-data,HTML5还支持text/plain。
- application/x-www-form-urlencoded
&分隔的键值对保存在请求主体中。 - multipart/form-data
表单数据转换为MIME报文,键值对带有各自内容类型和内容配置(disposition)。
传送大量数据(如上传文件),可Base64编码,以文本方式传送二进制数据。
HTML表单可发送GET请求,键值对在请求URL中,无主体。
4.2.1 Form字段
URL、主体数据提取到Form、PostForm和MultipartForm字段中。
Request获取表单数据的步骤:
(1)ParseFomr或者ParseMultipartForm方法,对请求进行语法分析;
(2)访问Form、PostForm或者MultipartForm字段。
client.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?hello=world&thread=123" method="post" enctype="application/x-www-form-urlencoded"><input type="text" name="hello" value="sau sheong"/><input type="text" name="post" value="456"/><input type="submit"/></form></body>
</html>
package mainimport ("fmt""net/http"
)func process(w http.ResponseWriter, r *http.Request) {r.ParseForm()fmt.Fprintln(w, r.Form)
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/process", process)server.ListenAndServe()
}
//URL
http://127.0.0.1:8080/process?hello=world&thread=123
//post主体
hello=sau sheong&post=456//输出
//r.ParseForm()和r.Form包含url和表单键值对
//且表单值总是排在URL值的前面
map[thread:[123] hello:[sau sheong world] post:[456]]
4.2.2 PostForm字段
//r.ParseForm()和r.PostForm(只支持application/x-www-form-urlencoded编码)
//只包含表单键值对
//r.ParseForm()和r.Form(multipart/form-data)
//只返回URL查询值
4.2.3 MultipartForm字段
获取multipart/form-data编码的表单数据,需要ParseMultipartForm方法(需要时会自行调用ParseFrom方法)和MultipartForm字段。
//从multipart编码表单里取出字节数
r.ParseMultipartForm(1024)//&{map[hello:[sau sheong] post:[456]] map[]}
//只包含表单键值对
//第二个空映射map[]用来记录用户上传的文件
fmt.Fprintln(w, r.MultipartForm)//FormValue只取第一个值
//需要时自动调用ParseFrom或ParseMultipartForm
fmt.Fprintln(w, r.FormValue("hello"))//PostFormValue只返回表单值
//需要时自动调用ParseFrom或ParseMultipartForm
fmt.Fprintln(w, r.PostFormValue("hello"))
FormValue和PostFormValue解析multipart/form-data表单,无结果。
整理:
(1)
表单(application/x-www-form-urlencoded,默认)
+
ParseFrom方法
+
Form字段
👇
URL键值对+表单键值对;
(2)
表单(application/x-www-form-urlencoded,默认)
+
ParseFrom方法
+
PostForm字段
👇
表单键值对;
(3)
表单(multipart/form-data)
+
ParseMultipartFrom方法
+
MultipartFrom字段
👇
表单键值对;
(4)
表单(application/x-www-form-urlencoded,默认)
+
FromValue字段
👇
URL键值对+表单键值对;
(5)
表单(application/x-www-form-urlencoded,默认)
+
PostFromValue字段
👇
表单键值对;
4.2.4 文件
multipart/form-data编码常用于文件上传,需要file类型的input标签。
<html><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Go Web Programming</title></head><body><form action="http://localhost:8080/process?hello=world&thread=123" method="post" enctype="multipart/form-data"><input type="text" name="hello" value="sau sheong"/><input type="text" name="post" value="456"/><input type="file" name="uploaded"><input type="submit"></form></body>
</html>
package mainimport ("fmt""io/ioutil""net/http"
)func process(w http.ResponseWriter, r *http.Request) {r.ParseMultipartForm(1024)fileHeader := r.MultipartForm.File["uploaded"][0]file, err := fileHeader.Open()if err == nil {data, err := ioutil.ReadAll(file)if err == nil {fmt.Fprintln(w, string(data))}}
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/process", process)server.ListenAndServe()
}
package mainimport ("fmt""io/ioutil""net/http"
)func process(w http.ResponseWriter, r *http.Request) {file, _, err := r.FormFile("uploaded")if err == nil {data, err := ioutil.ReadAll(file)if err == nil {fmt.Fprintln(w, string(data))}}
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/process", process)server.ListenAndServe()
}
4.2.5 处理含JSON主体的POST请求
不同客户端使用不同方式编码POST请求。
- jQuery使用POST+默认表单编码+首部Content-Type: application/x-www-form-urlencoded
- Angular使用POST+表单编码application/json
ParseForm方法不接受application/json编码。
4.3 ResponseWriter
处理器通过ResponseWriter接口创建HTTP响应。
ResponseWriter接口内部会使用http.response结构(非导出,nonexported)。
4.3.1 Write方法
字节数组作为参数,写入响应主体。
首部未设置内容类型时,通过写入前512字节决定。
package mainimport ("fmt""encoding/json""net/http"
)type Post struct {User stringThreads []string
}func writeExample(w http.ResponseWriter, r *http.Request) {str := `
Go Web Programming
Hello World
`w.Write([]byte(str))
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/write", writeExample)server.ListenAndServe()
}
curl -i 127.0.0.1:8080/writer
4.3.2 WriteHeader方法
响应状态码,未设置时默认返回200 OK。
package mainimport ("fmt""encoding/json""net/http"
)type Post struct {User stringThreads []string
}func writeHeaderExample(w http.ResponseWriter, r *http.Request) {w.WriteHeader(501)fmt.Fprintln(w, "No such service, try next door")
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/writeheader", writeHeaderExample)server.ListenAndServe()
}
curl -i 127.0.0.1:8080/writerheader
4.3.2 Header方法
写入首部映射。
先调用Header方法写入首部,再调用WriteHeader(执行后,不允许修改首部)写入状态码。
package mainimport ("fmt""encoding/json""net/http"
)type Post struct {User stringThreads []string
}func headerExample(w http.ResponseWriter, r *http.Request) {w.Header().Set("Location", "http://google.com")w.WriteHeader(302)
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/redirect", headerExample)server.ListenAndServe()
}
curl -i 127.0.0.1:8080/redirect
4.3.4 完整JSON响应
package mainimport ("fmt""encoding/json""net/http"
)type Post struct {User stringThreads []string
}func writeExample(w http.ResponseWriter, r *http.Request) {str := `
Go Web Programming
Hello World
`w.Write([]byte(str))
}func writeHeaderExample(w http.ResponseWriter, r *http.Request) {w.WriteHeader(501)fmt.Fprintln(w, "No such service, try next door")
}func headerExample(w http.ResponseWriter, r *http.Request) {w.Header().Set("Location", "http://google.com")w.WriteHeader(302)
}func jsonExample(w http.ResponseWriter, r *http.Request) {w.Header().Set("Content-Type", "application/json")post := &Post{User: "Sau Sheong",Threads: []string{"first", "second", "third"},}json, _ := json.Marshal(post)w.Write(json)
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/write", writeExample)http.HandleFunc("/writeheader", writeHeaderExample)http.HandleFunc("/redirect", headerExample)http.HandleFunc("/json", jsonExample)server.ListenAndServe()
}
curl -i 127.0.0.1:8080/json
4.4 cookie
存储在客户端的、体积较小的信息,最初通过服务器HTTP响应报文发送(set-cookie),之后客户端每次请求发送cookie。
cookie划分为会话cookie和持久cookie。
4.4.1Go与cookie
type Cookie struct {Name stringValue stringPath stringDomain stringExpires time.TimeRawExpires string MaxAge intSecure boolHttpOnly boolSameSite SameSiteRaw stringUnparsed []string
}
会话cookie或临时cookie(未设置Expires字段),浏览器关闭时自动移除。
持久cookie(设置了Expires字段),时间过期或手动删除。
Expires绝对时间,几乎所有浏览器都支持。
MaxAge相对时间,HTTP 1.1推荐使用。
4.4.2 发送cookie至浏览器
package mainimport ("fmt""net/http"
)func setCookie(w http.ResponseWriter, r *http.Request) {c1 := http.Cookie{Name: "first_cookie",Value: "Go Web Programming",HttpOnly: true,}c2 := http.Cookie{Name: "second_cookie",Value: "Manning Publications Co",HttpOnly: true,}w.Header().Set("Set-Cookie", c1.String())w.Header().Add("Set-Cookie", c2.String())
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/set_cookie", setCookie)server.ListenAndServe()
}
package mainimport ("fmt""net/http"
)func setCookie(w http.ResponseWriter, r *http.Request) {c1 := http.Cookie{Name: "first_cookie",Value: "Go Web Programming",HttpOnly: true,}c2 := http.Cookie{Name: "second_cookie",Value: "Manning Publications Co",HttpOnly: true,}http.SetCookie(w, &c1)http.SetCookie(w, &c2)
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/set_cookie", setCookie)server.ListenAndServe()
}
4.4.3 从浏览器获取cookie
package mainimport ("fmt""net/http"
)func setCookie1(w http.ResponseWriter, r *http.Request) {c1 := http.Cookie{Name: "first_cookie",Value: "Go Programming",}c2 := http.Cookie{Name: "second_cookie",Value: "Go Web Programming",HttpOnly: true,}w.Header().Set("Set-Cookie", c1.String())w.Header().Add("Set-Cookie", c2.String())fmt.Fprintf(w, "%s\n%s\n", c1.String(), c2.String())
}func setCookie2(w http.ResponseWriter, r *http.Request) {c1 := http.Cookie{Name: "first_cookie",Value: "Go Programming",}c2 := http.Cookie{Name: "second_cookie",Value: "Go Web Programming",HttpOnly: true,}http.SetCookie(w, &c1)http.SetCookie(w, &c2)
}func getCookie1(w http.ResponseWriter, r *http.Request) {cookie := r.Header["Cookie"]fmt.Fprintf(w, "%s\n", cookie)
}func getCookie2(w http.ResponseWriter, r *http.Request) {cookie, err := r.Cookie("first_cookie")if err != nil {fmt.Fprintln(w, "Cannot get Cookie")}cookies := r.Cookies()fmt.Fprintf(w, "%s\n%s\n", cookie, cookies)
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/set_cookie", setCookie2)http.HandleFunc("/get_cookie", getCookie2)server.ListenAndServe()
}
//获取的Set-Cookie保存在a.cookie文件中
curl -i -c a.cookie http://127.0.0.1:8080/set_cookie//发送a.cookie文件中的cookies
curl -i -b a.cookie http://127.0.0.1:8080/get_cookie
4.4.4 cookie实现闪现消息
某个条件满足时,页面上显示临时消息(闪现消息,flash message),刷新页面后消失。
package mainimport ("encoding/base64""fmt""net/http""time"
)func set_message(w http.ResponseWriter, r *http.Request) {msg := []byte("Hello World")cookie := http.Cookie{Name: "flash",//响应首部对空格,百分号,中文等特殊字符的URL编码要求Value: base64.URLEncoding.EncodeToString(msg),}http.SetCookie(w, &cookie)
}func show_message(w http.ResponseWriter, r *http.Request) {cookie, err := r.Cookie("flash")if err != nil {if err == http.ErrNoCookie {fmt.Fprintln(w, "no messages to show")}} else {expire_cookie := http.Cookie{Name: "flash",MaxAge: -1, //负值Expires: time.Unix(1, 0), //过去时间}//MaxAge设置负值,Expires设置过去时间//SetCookie将同名cookie("flash")发送到客户端//等价于完全移除这个cookiehttp.SetCookie(w, &expire_cookie)value, _ := base64.URLEncoding.DecodeString(cookie.Value)fmt.Fprintln(w, string(value))}
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/set_message", set_message)http.HandleFunc("/show_message", show_message)server.ListenAndServe()
}
curl -i -c b.cookie http://127.0.0.1:8080/set_message
curl -i -b b.cookie -c b.cookie http://127.0.0.1:8080/show_messagecurl -i -b b.cookie -c b.cookie http://127.0.0.1:8080/show_message
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!