Go 学习笔记(三十五)Cookie
本文原创地址:博客园骏马金龙Go Web:Cookie
Cookie 用来解决 http 协议无状态的问题。
首先,在服务端生成 Cookie,然后在 http 响应 header 中设置 Set-Cookie 字段,客户端会读取到 Set-Cookie 字段后,会将 cookie 信息存储起来,下次继续访问服务端时,会在 http 请求中设置 Cookie 字段并发送给服务端,服务端可以解析这个 Cookie 字段,从而知道这个客户端之前已经和自己有过会话 (上下文),然后再执行相应的逻辑代码。
Cookie 分为两种类型:session cookie 和 persistent cookie。
- Session Cookie 也称为临时 Cookie,客户端只会将 cookie 数据存储在 http client 进程的内容中,不会保存到磁盘文件中 (或其它存储设备),浏览器关闭(或者说 http client 进程退出) 的时候,cookie 就删除了 。
- persistent cookie 是持久化 cookie,浏览器退出也不删除,而是根据服务端发送 cookie 时设置的过期时长判断 cookie 是否过期,只要 cookie 还有效,客户端就会携带 cookie 访问服务端 。
Cookie struct
$ go doc http.cookie
type Cookie struct {
Name string
Value string
Path string // optional
Domain string // optional
Expires time.Time // optional
RawExpires string // for reading cookies only
// MaxAge=0 means no 'Max-Age' attribute specified.
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
// MaxAge>0 means Max-Age attribute present and given in seconds
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string // Raw text of unparsed attribute-value pairs
}
func (c *Cookie) String() string
一个 Cookie 代表一个 http cookie。 服务端可以设置多个 Set-Cookie 字段发送给客户端。 。
Name 和 Value 分别设置这个 cookie 的 key/value。一定要有至少一个能唯一区分客户端的 ID 类的 value。
Expires 指定 cookie 到什么时候过期,是一个时间值。当指定为过去的时间值时,表示这个 cookie 已经过期。
MaxAge 也用来设置 cookie 什么时候过期,MaxAge 为负数或等于 0 表示立即过期,MaxAge 大于 0 表示过多少秒之后过期。
MaxAge 和 Expires 都可以设置 cookie 持久化时的过期时长,Expires 是老式的过期方法,如果可以,应该使用 MaxAge 设置过期时间,但有些老版本的浏览器不支持 MaxAge。如果要支持所有浏览器,要么使用 Expires,要么同时使用 MaxAge 和 Expires。
Path 和 Domain 设置访问哪些路径或域名范围的主机时应该携带这个 cookie。如果不设置,则访问所有路径、该 Domain 下的主机都携带 cookie。
cookie.Path("/WEB16");
代表访问WEB16应用中的任何资源都携带cookie
cookie.Path("/WEB16/cookietest");
代表访问WEB16中的cookietest时才携带cookie信息
cookie.Domain(".foo.com");
这对foo.com域下的所有主机都生效(如www.foo.com),但不包括子域www.abc.foo.com
Secure 和 HttpOnly 字段为 cookie 提供一些保护机制。
Cookie 有一个 String() 方法,用来将 Cookie 实例转换成字符串。转化成字符串之后就可以直接设置在 Header 中。
例如,下面是登录 youtube 的时候,对方发送给我的 cookie:
设置 Cookie 并发送给客户端
package main
import (
"fmt"
"net/http"
)
func setCookie(w http.ResponseWriter, r *http.Request) {
// 定义两个cookie
c1 := http.Cookie{
Name: "first_cookie",
Value: "Go Programming",
}
c2 := http.Cookie{
Name: "second_cookie",
Value: "Go Web Programming",
HttpOnly: true,
}
// 设置Set-Cookie字段
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 main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/set_cookie", setCookie)
server.ListenAndServe()
}
访问http://127.0.0.1:8080/set_cookie
时,查看 Header 将显式 Set-Cookie 字段。
$ curl -i http://127.0.0.1:8080/set_cookie
HTTP/1.1 200 OK
Set-Cookie: first_cookie="Go Programming"
Set-Cookie: second_cookie="Go Web Programming"; HttpOnly
Date: Tue, 27 Nov 2018 10:12:44 GMT
Content-Length: 75
Content-Type: text/plain; charset=utf-8
first_cookie="Go Programming"
second_cookie="Go Web Programming"; HttpOnly
http 包提供了一个 SetCookie() 函数,可以直接用来设置 Set-Cookie 字段。
func SetCookie(w ResponseWriter, cookie *Cookie)
注意, 第二个字段是指针类型的 Cookie 。
修改前面的示例,使用 SetCookie() 函数发送 Set-Cookie 字段:
func setCookie(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)
}
取得客户端携带的 cookie
由于客户端发起请求时,如果携带 cookie,是直接放在 Request 的 Cookie Header 中的。所以,可以通过 Request 取得客户端携带的 cookie 信息。当然,也可以通过 Request 的方法 Cookie()或 Cookies() 取得 cookie 信息。
func (r *Request) Cookie(name string) (*Cookie, error)
func (r *Request) Cookies() []*Cookie
- Cookie(Name) 只取某个 cookie
- Cookies() 取所有的 cookie
下面是通过 Request Header 的方式取 Cookie 的示例:
package main
import (
"fmt"
"net/http"
)
func setCookie(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 getCookie(w http.ResponseWriter, r *http.Request) {
cookie := r.Header.Get("Cookie")
fmt.Fprintf(w, "%s\n", cookie)
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/set_cookie", setCookie)
http.HandleFunc("/get_cookie", getCookie)
server.ListenAndServe()
}
在访问http://127.0.0.1:8080/set_cookie
之后不要关闭浏览器,再次访问http://127.0.0.1:8080/get_cookie
,将输出:
first_cookie="Go Programming"; second_cookie="Go Web Programming"
或者,使用 curl 记录 cookie,并下次访问时读取 cookie:
$ curl -c a.cookie http://127.0.0.1:8080/set_cookie
$ curl -b a.cookie http://127.0.0.1:8080/get_cookie
first_cookie="Go Programming"; second_cookie="Go Web Programming"
下面是改用 Request 的 Cookie()和 Cookies() 方法取 cookie:
func getCookie(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("first_cookie")
if err != nil {
fmt.Fprintf(w, "Cat't get Cookie")
}
cookies := r.Cookies()
fmt.Fprintf(w, "%s\n%s\n", cookie, cookies)
}
访问结果:
$ curl -c a.cookie http://127.0.0.1:8080/set_cookie
$ curl -b a.cookie http://127.0.0.1:8080/get_cookie
first_cookie="Go Programming"
[first_cookie="Go Programming" second_cookie="Go Web Programming"]
设置 cookie 过期示例:发送临时消息
有时候可能想要让客户端的某些操作只显示一次相关消息,例如 post 一篇帖子失败后,应该显示失败信息,但下次再访问不应该再显示这些失败信息。
通过设置 cookie 过期的技巧,可以实现一些一次性操作。设置 cookie 过期的方式是设置 MaxAge 为负数或 0,为了兼容所有浏览器,可以设置 Expires 为过去的一段时间。
下面的示例中,将一段数据使用 URL 格式编码后作为 flash cookie 的值。当客户端访问 set_message 的时候,就会在 http Client 进程中保存这段 cookie。再访问 show_message 的时候,handler 解析客户端携带的 cookie,并设置一个 Set-Cookie 字段,这个字段的作用是使之前保存的 cookie 过期。然后输出解码后客户端携带的 cookie 的值。再次刷新 show_message,将得到不同的输出结果。
package main
import (
"encoding/base64"
"fmt"
"net/http"
"time"
)
func set_message(w http.ResponseWriter, r *http.Request) {
msg := []byte("Hello World")
cookie := http.Cookie{
Name: "flash",
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),
}
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 测试。注意,首先访问 set_message 的时候,保存 cookie 到 b.cookie 文件。再访问 show_message 的时候,也要带上-c b.cookie
将已保存的 cookie 设置为过期,之后再访问 show_message 就会出现预期的结果:
$ curl -c b.cookie http://127.0.0.1:8080/set_message
$ curl -b b.cookie -c b.cookie http://127.0.0.1:8080/show_message
Hello World
$ curl -b b.cookie -c b.cookie http://127.0.0.1:8080/show_message
no messages to show
上一篇 Go 学习笔记(三十四)处理请求
Go 学习笔记(目录)
下一篇 Go 学习笔记(三十六)数据存储 (1)——内存存储