
cookie
1.GO的cookie
cookie 的出现是为了解决 HTTP 协议的无状态性的。客户端通过 HTTP 协议与服务器通信,多次请求之间无法记录状态。服务器可以在响应中设置 cookie,客户端保存这些 cookie。然后每次请求时都带上这些 cookie,服务器就可以通过这些 cookie 记录状态,辨别用户身份等。
整个计算机行业的收入都建立在 cookie 机制之上,广告领域更是如此
上面的说法虽然有些夸张,但是可见 cookie 的重要性
我们知道广告是互联网最常见的盈利方式。其中有一个很厉害的广告模式,叫做联盟广告。最常见的就是,刚在百度上搜索某个关键字,然后打开淘宝或京东后发现相关的商品已经被推荐到首页或边栏。这是由于这些网站组成了广告联盟,只要加入它们,就可以共享用户浏览器的cookie数据
Go 中 cookie 使用 http.Cookie 结构表示,在 net/http 包中定义
// src/net/http/cookie.go type Cookie struct { Name string Value string Path string Domain string Expires time.Time RawExpires string MaxAge int Secure bool HttpOnly bool SameSite SameSite Raw string Unparsed []string }
- Name/Value : cookie 的键值对,都是字符串类型
- 没有设置 Expires 字段的 cookie 被称为会话 cookie 或临时 cookie,这种 cookie 在浏览器关闭时就会自动删除。设置了Expires 字段的 cookie 称为持久 cookie,这种 cookie 会一直存在,直到指定的时间来临或手动删除;
- HttpOnly 字段设置为 true 时,该 cookie 只能通过 HTTP 访问,不能使用其它方式操作,如JavaScript。提高安全性
注意 :
Expires 和 MaxAge 都可以用于设置 cookie 的过期时间。 Expires 字段设置的是 cookie 在什么时间点过期,而 MaxAge 字段表示 cookie 自创建之后能够存活多少秒。虽然 HTTP 1.1 中废弃了Expires ,推荐使用 MaxAge 代替。但是几乎所有的浏览器都仍然支持Expires ;而且微软的IE6/IE7/IE8 都不支持 MaxAge 。所以为了更好的可移植性,可以只使用 Expires 或同时使用这两个字段。cookie 需要通过响应的首部发送给客户端。浏览器收到 Set-Cookie 首部时,会将其中的值解析成cookie 格式保存在浏览器中。下面我们来具体看看如何设置 cookie:
package main import ( "log" "net/http" ) func main() { http.HandleFunc("/", setCookie) if err := http.ListenAndServe(":8082", nil); err != nil { log.Fatal(err) } } func setCookie(w http.ResponseWriter, r *http.Request) { c1 := &http.Cookie{ Name: "name", Value: "lianshi", HttpOnly: true, } c2 := &http.Cookie{ Name: "age", Value: "18", HttpOnly: true, } w.Header().Set("Set-Cookie", c1.String()) w.Header().Add("Set-Cookie", c2.String()) }运行程序,打开浏览器输入 localhost:8080/set_cookie ,在浏览器开发者工具,切换到Application(应用)标签,查看 cookie。在左侧 Cookies 下点击测试的 URL,右侧即可显示我们刚刚设置的 cookie
上面构造 cookie 的代码中,有几点需要注意:
- 首部名称为 Set-Cookie
- 首部的值需要是字符串,所以调用了 Cookie 类型的 String 方法将其转为字符串再设置
- 设置第一个 cookie 调用 Header 类型的 Set 方法,添加第二个 cookie 时调用 Add 方法。 Set 会将同名的键覆盖掉。如果第二个也调用 Set 方法,那么第一个 cookie 将会被覆盖
为了使用的快捷, net/http 包还提供了SetCookie 方法。方法如下:
func setCookie2(w http.ResponseWriter, r *http.Request) { c1 := &http.Cookie { Name: "name", Value: "lianshi", HttpOnly: true, } c2 := &http.Cookie { Name: "age", Value: "18", HttpOnly: true, } http.SetCookie(w, c1) http.SetCookie(w, c2) } mux.HandleFunc("/set_cookie2", setCookie2)如果收到的响应中有 cookie 信息,浏览器会将这些 cookie 保存下来。只有没有过期,在向同一个主机发送请求时都会带上这些 cookie。在服务端,我们可以从请求的 Header 字段读取 Cookie 属性来获得cookie:
func getCookie(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Host:", r.Host) fmt.Fprintln(w, "Cookies:", r.Header["Cookie"]) } mux.HandleFunc("/get_cookie", getCookie)第一次启动服务器,请求 localhost:8080/get_cookie 时,没有 cookie 信息
先请求一次 localhost:8080/set_cookie ,然后再次请求 localhost:8080/get_cookie ,浏览器就将 cookie 传过来了
r.Header["Cookie"] 返回一个切片,这个切片又包含了一个字符串,而这个字符串又包含了客户端发送的任意多个 cookie。如果想要取得单个键值对格式的 cookie,就需要解析这个字符串。 为此, net/http 包在 http.Request 上提供了一些方法使我们更容易地获取 cookie
func GetCookie(w http.ResponseWriter, r *http.Request) { name, err := r.Cookie("name") if err != nil { fmt.Fprintln(w, "cannot get cookie of name") } cookies := r.Cookies() fmt.Fprintln(w, name) fmt.Fprintln(w, cookies) } mux.HandleFunc("/get_cookies", getCookies2)
- Cookie 方法返回以传入参数为键的 cookie,如果该 cookie 不存在,则返回一个错误
- Cookies 方法返回客户端传过来的所有 cookie
有⼀点需要注意, cookie 是与主机名绑定的,不考虑端口。我们上面查看 cookie 的图中有一列Domain 表示的就是主机名。可以这样来验证一下,创建两个服务器,一个绑定在 8080 端口,一个绑定在 8081 端口,先请求 localhost:8080/set_cookie 设置 cookie,然后请求 localhost:8081/get_cookie
package main import ( "fmt" "log" "net/http" "sync" ) func main() { mux1 := http.NewServeMux() mux1.HandleFunc("/Set-Cookie", SetCookie) mux1.HandleFunc("/Get-Cookie", GetCookie) server1 := &http.Server{ Addr: ":8083", Handler: mux1, } mux2 := http.NewServeMux() mux2.HandleFunc("/Set-Cookie", SetCookie) mux2.HandleFunc("/Get-Cookie", GetCookie) server2 := &http.Server{ Addr: ":8081", Handler: mux2, } wg := sync.WaitGroup{} wg.Add(2) go func() { defer wg.Done() if err := server1.ListenAndServe(); err != nil { log.Fatal(err) } }() go func() { defer wg.Done() if err := server2.ListenAndServe(); err != nil { log.Fatal(err) } }() wg.Wait() } func GetCookie(w http.ResponseWriter, r *http.Request) { name, err := r.Cookie("name") if err != nil { fmt.Fprintln(w, "cannot get cookie of name") } cookies := r.Cookies() fmt.Fprintln(w, name) fmt.Fprintln(w, cookies) } func SetCookie(w http.ResponseWriter, r *http.Request) { c1 := &http.Cookie{ Name: "name", Value: "lianshi", HttpOnly: true, } c2 := &http.Cookie{ Name: "age", Value: "18", HttpOnly: true, } w.Header().Set("Set-Cookie", c1.String()) w.Header().Add("Set-Cookie", c2.String()) }发送给端口 8081 的请求同样可以获取 cookie
上面代码中,不能直接在主 goroutine 中依次 ListenAndServe 两个服务器。因为 ListenAndServe只有在出错或关闭时才会返回。在此之前,第二个服务器永远得不到机会运行。所以,我创建两个goroutine 各自运行一个服务器,并且使用 sync.WaitGroup 来同步。否则,主 goroutine 运行结束之后,整个程序就退出了
2.梳理一下net/http代码的执行流程
首先调用Http.HandleFunc
按顺序做了几件事:
- 调用DefaultServeMux的HandleFunc
- 调用DefaultServeMux的Handle
- 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
其次调用http.ListenAndServe(":8080", nil)
按顺序做了几件事情:
- 实例化Server
- 调用Server的ListenAndServe()
- 调用net.Listen("tcp", addr)监听端口
- 启动一个for循环,在循环体中Accept请求
- 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进入服务go c.serve()
- 读取每个请求的内容w, err := c.readRequest()
- 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler), handler就设置为DefaultServeMux
- 调用handler的ServeHttp
- 在这个例子中,下面就进入到DefaultServeMux.ServeHttp
- 根据request选择handler,并且进入到这个handler的ServeHTTP
3.Go Web 的基本形式如下
package main import ( "fmt" "log" "net/http" ) func helloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World") } type greetingHandler struct { Name string } func (h greetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s", h.Name) } func main() { mux := http.NewServeMux() // 注册处理器函数 mux.HandleFunc("/hello", helloHandler) // 注册处理器 mux.Handle("/greeting/golang", greetingHandler{Name: "Golang"}) server := &http.Server{ Addr: ":8080", Handler: mux, } if err := server.ListenAndServe(); err != nil { log.Fatal(err) } }
4.cookie代码
login.html
<html> <head> <title>Go Web</title> </head> <body> <form action="/form?lang=cpp&name=ls" method="post" enctype="application/x-www-form-urlencoded"> <label>Form:</label> <input type="text" name="lang" /> <input type="text" name="age" /> <button type="submit">提交</button> </form> </body> </html>main
package main import ( "fmt" "io/ioutil" "log" "net/http" ) func main(){ //当用户访问/login的时候,执行login函数 http.HandleFunc("/login",loginHandler) http.HandleFunc("/home",homeHandler) if err := http.ListenAndServe(":8080",nil); err !=nil { log.Fatal(err) } } func loginHandler(w http.ResponseWriter,r * http.Request){ if r.Method == http.MethodPost{ username := r.FormValue("username") password := r.FormValue("password") //对数据进行校验 fmt.Println(username,password) if username == "xiaop" && password == "123456"{ //登陆成功 //创建cookie数据 c1 :=&http.Cookie{ Name: "login", Value: "true", HttpOnly: true, //默认关闭浏览器的cookie就失败 MaxAge: 10, } //把cookie添加到响应头中 //w.Header().Set("Set-Cookie",c1.String()) http.SetCookie(w,c1) w.Write([]byte("登陆成功")) }else { w.Write([]byte("登陆失败")) } } else { b,_ :=ioutil.ReadFile("./login.html") w.Write(b) } } func homeHandler(w http.ResponseWriter,r *http.Request){ c,err := r.Cookie("login") if err !=nil{ //从当前请求中取不到这个cookie数值 fmt.Println(err) //2.如果不是登陆用户就跳转到登陆页面 //返回一个跳转到登陆页面的响应 w.Header().Set("Location","http://127.0.0.1:8080/login") w.WriteHeader(http.StatusFound) //必须作为修改响应头的最后一步 return } fmt.Println(c.Value) fmt.Println("个人中心页面") }