《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 string
URL *url.URL
Protostring // "HTTP/1.0"
ProtoMajor int// 1
ProtoMinor int// 0
Header Header
Body io.ReadCloser
GetBody func() (io.ReadCloser, error)
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chan struct{}
Response *Response
ctx context.Context
}
4.1.2 请求URL
type URL struct {
Schemestring
Opaquestring// encoded opaque data
User*Userinfo // username and password information
Hoststring// host or host:port
Pathstring// path (relative paths may omit leading slash)
RawPathstring// encoded path hint (see EscapedPath method)
ForceQuerybool// append a query ('?') even if RawQuery is empty
RawQuerystring// encoded query values, without '?'
Fragmentstring// 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.ContentLength
body := 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表单 【Go|《Go Web 编程》之第4章 处理请求】POST请求基本通过HTML表单发送:
表单输入数据以键值形式记录在请求主体中。
决定POST请求发送键值对时格式的HTML表单内容类型(content type),由表单的enctype属性(默认"application/x-www-form-urlencoded")指定。
浏览器至少支持application/x-www-form-urlencoded和multipart/form-data,HTML5还支持text/plain。
- application/x-www-form-urlencoded
&分隔的键值对保存在请求主体中。 - multipart/form-data
表单数据转换为MIME报文,键值对带有各自内容类型和内容配置(disposition)。
传送大量数据(如上传文件),可Base64编码,以文本方式传送二进制数据。
4.2.1 Form字段
URL、主体数据提取到Form、PostForm和MultipartForm字段中。
Request获取表单数据的步骤:
(1)ParseFomr或者ParseMultipartForm方法,对请求进行语法分析;
(2)访问Form、PostForm或者MultipartForm字段。
client.html
Go Web Programming - 锐客网
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标签。
Go Web Programming - 锐客网
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
4.3 ResponseWriter 处理器通过ResponseWriter接口创建HTTP响应。
ResponseWriter接口内部会使用http.response结构(非导出,nonexported)。
4.3.1 Write方法
字节数组作为参数,写入响应主体。
首部未设置内容类型时,通过写入前512字节决定。
package mainimport (
"fmt"
"encoding/json"
"net/http"
)type Post struct {
Userstring
Threads []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 {
Userstring
Threads []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 {
Userstring
Threads []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 {
Userstring
Threads []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 {
Namestring
Value string Pathstring
Domainstring
Expirestime.Time
RawExpires string MaxAgeint
Securebool
HttpOnly bool
SameSite SameSite
Rawstring
Unparsed []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")发送到客户端
//等价于完全移除这个cookie
http.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
推荐阅读
- Go精进|Go语言学习笔记——Golang 1.18新特性泛型
- 历史上的今天|【历史上的今天】9 月 13 日(“海盗湾”创始人出生;第一台装载硬盘的超级计算机;《超级马里奥兄弟》发布)
- 极客时间Go实战训练营全新升级第5期2022最新完结无密
- 极客时间-Go实战训练营全新升级第5期无密
- 面试|CentOS下安装及配置MySQL
- mysql|MySQL中的中文报错--保姆级解决方法
- 遇见Golang|【Go开源宝藏】十分强大的日志库 logrus
- asp.net|netcore基于asp.net的校园二手闲置商品交易系统
- 安全防御|防火墙实验1