Session和Cookie简介
HTTP协议是一种无状态协议,即服务器端每次接收到客户端的请求都是一个全新的请求,服务器端并不知道客户端的历史请求记录。session和cookie的主要目的就是为了弥补HTTP的无状态特性。
session是什么
客户端请求服务器端,服务器端会为这次请求开辟一块内存空间,这个对象便是session对象,存储结构为ConcurrentHashMap。
session弥补了HTTP的无状态特性,服务器端可以利用session存储客户端在同一个会话期间的一些操作记录。
session如何判断是否为同一个会话
服务器端在第一次接收到请求时,会开辟一块session空间(创建了session对象),同时生成一个sessionld,并通过响应头的“Set-Cookie:JSESSIONID=XXXXXXX”命令,向客户端发送要求设置cookie的响应。
客户端在收到响应后,在本机客户端设置了一个“JSESSIONID=XXXXXXX”的cookie信息,该cookie的过期时间为浏览器会话结束。

接下来,在客户端每次向同一个服务器端发送请求时,请求头中都会有该cooke信息(包含sessionld)。服务器端通过读取请求头中的cookie信息,获取名称为JSESSIONID的值,得到此次请求的sessionld。
session的缺点
session机制有一个缺点:如果A服务器存储了session(即做了负载均衡),假如一段时间内A的访问量激增,则访问会被转发到B服务器,但是B服务器并没有存储A服务器的session,从而导致session失效。
cookie是什么
HTTP协议中的cookie是服务器端发送到客户端Web浏览器的一小块数据,包括Web cookie和浏览器cookie。服务器端发送到客户端浏览器的cookie,浏览器会进行存储,并与下一个请求一起发送到服务器端。通常,它用于判断两个请求是否来自同一个客户端浏览器,例如用户保持登
录状态。
cookie主要用于以下3个方面:
- 会话管理:在登录、购物车、游戏得分或者服务器里常需要用会话管理来记住其内容。
- 实现个性化:个性化是指用户偏好、主题或者其他设置。
- 追踪:记录和分析用户行为。
cooke曾经用作一般的客户端存储,那时这是合法的,因为它们是在客户端上存储数据的唯方法。但如今建议使用现代存储API。cookie随每个请求一起被发送,因此它们可能会降低性能(尤其是对于移动数据连接而言)。
session和cookie的区别。
首先,无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用cookie,但session仍然是能够工作的,因为客户端无法禁用服务器端的session。
其次,在存储的数据量方面,session和cookie也是不一样的。session能够存储任意类型的对象,cookie只能存储String类型的对象。
创建cookie
当接收到客户端发出的HTTP请求时,服务器端可以发送带有响应的Set-Cookie标头。cookie通常由浏览器存储,浏览器将cookie与HTTP标头组合在一起向服务器端发送请求。
- Set-Cookie标头和cookie。
Set-Cookie HTTP响应标头的作用是将cookie从服务器端发送到用户代理。 - 会话cookie
会话cookie有一个特征一客户端关闭时cookie会被删除,因为它没有指定Expires或Max-Age指令。但是,Web浏览器可能会使用会话还原,这会使得大多数会话cookie保持“永久”状态,就像从未关闭过浏览器。 - 永久性cookie。
永久性cookie不会在客户端关闭时过期,而是在到达特定日期(Expires)或特定时间长度(Max-Age)后过期。例如“Set-Cookie:id=b8gNc;Expires=Sun,21Dec202007:28:00
GMT;”表示设置一个id为b8gNc、过期时间为2020年12月21日07:28:00、格林尼治时间的cookie。 - 安全的cookie。
安全的cookie需要HTTPS协议通过加密的方式发送到服务器。即使是安全的,也不应该将敏感信息存储在Cooke中,因为它们本质上是不安全的,并且此标志不能提供真正的保护。
cookie的作用域
Domain和Path标识定义了cookie的作用域,即cookie应该被发送给哪些URL。Domain标识指定了哪些主机可以接受cookie。如果不指定Domain,则默认为当前主机(不包含子域名);如果指定了Domain,则一般包含子域名。例如,如果设置Domain=baidu.com,则cookie也包含在子域名中(如news.baidu.coml)。
例如,设置Path=test,则以下地址都会匹配:
- /test
- /test/news/
- /test/news/id
Go与cookie
在Go标准库的net/http包中定义了名为Cookie的结构体。Cookie结构体代表一个出现在HTTP响应头中的Set-Cookie的值,或者HTTP请求头中的cookie的值。
Cookie结构体的定义如下:
type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
//MaxAge=0标识未设置Max-Age属性
//MaxAge<0标识立刻删除该cookie,等价于Max-Age:0
//MaxAge>0标识存在Max-Age属性,单位是s
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string //未解析的"属性-值"对的原始文本
}
设置cookie
在Go语言的net/http包中提供了SetCookie()函数来设置cookie。
SetCookie()函数的定义如下:
func SetCookie(w ResponseWriter,cookie *Cookie)
SetCookie()函数的使用示例如下。
func main() {
http.HandleFunc("/", testHandle)
http.ListenAndServe(":8080", nil)
}
func testHandle(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("test_cookie")
fmt.Printf("cookie:%#v, err:%v\n", c, err)
cookie := &http.Cookie{
Name: "test_cookie",
Value: "DKLJFDSKIOfddFDSFIDSP",
MaxAge: 3600,
Domain: "localhost",
Path: "/",
}
http.SetCookie(w, cookie)
//应在具体数据返回之前设置cookie,否则cookie设置不成功
w.Write([]byte("hello"))
}
获取cookie
Go语言net/http包中的Request对象一共拥有3个处理cookie的方法:2个获取cookie的方法和1个添加cookie的方法。获取cookie,使用Cookies()或Cookie()方法。
- Cookies()方法。Cookies()方法的定义如下:
func (r *Request)Cookies() []*Cookie
Cookies()方法用于解析并返回该请求的所有cookie。
- Cookie()方法。Cookie()方法的定义如下:
func (r *Request)Cookie(name string)(*Cookie, error)
Cookie()方法用于返回请求中名为name的cookie,如果未找到该cookie,则返回“nil,ErrNoCookie”。
- AddCookie()方法。AddCookie()方法用于向请求中添加一个cookie。
AddCookie()方法的定义如下:
func (r *Request)AddCookie(c *Cookie)
Cookie()方法和AddCookie()方法的使用示例如下。
func main() {
CopeHandle("GET", "http://www.baidu.com", "")
}
func CopeHandle(method, urlVal, data string) {
client := &http.Client{}
var req *http.Request
if data == "" {
urlArr := strings.Split(urlVal, "?")
if len(urlArr) == 2 {
urlVal = urlArr[0] + "?" + getParseParam(urlArr[1])
}
req, _ = http.NewRequest(method, urlVal, nil)
} else {
req, _ = http.NewRequest(method, urlVal, strings.NewReader(data))
}
cookie := &http.Cookie{
Name: "X-Xsrftoken",
Value: "adfsf233lkj29432mdvmsdfdfdsfd",
HttpOnly: true,
}
req.AddCookie(cookie)
req.Header.Add("X-Xsrftoken", "werierpoweip2342tuiou543543")
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(b))
}
//将GEt请求的参数进行转义
func getParseParam(param string) string {
return url.PathEscape(param)
}
Go使用session
Go的标准库并没有提供实现session的方法,但很多Web框架都提供了。下面通过Go语言的具体示例来简单实现一个session的功能。
1. 定义一个名为Session的接口
Session结构体只有4种操作:设置值、获取值、删除值和获取当前的sessionld,因此Session接口应该有4种方法来执行这种操作:
//session接口
type Session interface {
Set(key, value interface{}) error //设置session
Get(key interface{}) interface{} //获取session
Delete(key interface{}) error //删除session
SessionID() string //返回sessionId
}
2. 创建session管理器
由于session是被保存在服务器端数据中的,因此可以抽象出一个Provider接口来表示session管理器的底层结构。Provider接口将通过sessionld来访问和管理session:
//session管理器底层结构
type Provider interface {
//session初始化
SessionInit(sessionId string) (Session, error)
//返回sessionId对应的session对象
SessionRead(sessionId string) (Session, error)
//删除给定的sessionId对应的session
SessionDestroy(sessionId string) error
//根据maxLifeTime删除过期的session
GarbageCollector(maxLifeTime int64)
}
在定义好Provider接口后,我们再写一个注册方法,以便可以根据provider管理器的名称来找到其对应的provider管理器:
var providers = make(map[string]Provider)
//注册一个能通过名称来获取的session provider管理器
func RegisterProvider(name string, provider Provider) {
if provider == nil {
panic("session: Register provider is nil")
}
if _, p := providers[name]; p {
panic("session: Register provider is existed")
}
providers[name] = provider
}
接着把provider管理器封装一下,定义一个全局的session管理器:
//全局session管理器
type SessionManager struct {
cookieName string //cookie的名称
lock sync.Mutex //锁,保证并发时数据的一致性
provider Provider //管理session
maxLifeTime int64 //超时时间
}
func NewSessionManager(providerName, cookieName string, maxLifetime int64) (*SessionManager, error) {
provider, ok := providers[providerName]
if !ok {
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", providerName)
}
//返回一个SessionManager对象
return &SessionManager{
cookieName: cookieName,
maxLifeTime: maxLifetime,
provider: provider,
}, nil
}
然后在main包中创建一个全局的session管理器:
var globalSession *SessionManager
func init() {
globalSession, _ = NewSessionManager("memory", "sessionId", 3600)
}
3. 创建获取sessionId的方法GetSessionId()
sessionld是用来识别访问Web应用的每一个用户的,因此需要保证它是全局唯一的。示例代码如下:
func (manager *SessionManager) GetSessionId() string {
b := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(b)
}
4. 创建SessionBegin()方法来创建session
需要为每个来访的用户分配或者获取与它相关连的session,以便后面能根据session信息来进行验证操作。SessionBegin()函数就是用来检测是否已经有某个session与当前来访用户发生了关联,如果没有则创建它。
//根据当前请求的cookie来判断是否存在有效的session,如果不存在则创建它
func (manager *SessionManager) SessionBegin(w http.ResponseWriter, r *http.Request) (session Session) {
manager.lock.Lock()
defer manager.lock.Unlock()
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {
sessionId := manager.GetSessionId()
session, _ = manager.provider.SessionInit(sessionId)
cookie := http.Cookie{
Name: manager.cookieName,
Value: url.QueryEscape(sessionId),
Path: "/",
HttpOnly: true,
MaxAge: int(manager.maxLifeTime),
}
http.SetCookie(w, &cookie)
} else {
sessionId, _ := url.QueryUnescape(cookie.Value)
session, _ = manager.provider.SessionRead(sessionId)
}
return session
}
现在已经可以通过SessionBegin()方法返回一个满足Session接口的变量了。
下面通过一个例子来展示一下session的读写操作:
//根据用户名判断是否存在该用户的session,如果不存在则创建它
func login(w http.ResponseWriter, r *http.Request) {
session := globalSession.SessionBegin(w, r);
r.ParseForm();
name := session.Get("username")
if name != nil {
//将表单提交的username值设置到session中
session.Set("username", r.Form["username"])
}
}
5.创建SessionDestroy()方法来注销session
在Wb应用中,通常有用户退出登录的操作。当用户退出应用时,我们就可以对该用户的session数据进行注销。下面创建一个名为SessionDestroy0的方法来注销session:
//创建SessionDestroy()方法来注销session
func (manager *SessionManager) SessiionDestroy(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {
return
}
manager.lock.Lock()
defer manager.lock.Unlock()
manager.provider.SessionDestroy(cookie.Value)
expiredTime := time.Now()
newCookie := http.Cookie{
Name: manager.cookieName,
Path: "/",
HttpOnly: true,
Expires: expiredTime,
MaxAge: -1,
}
http.SetCookie(w, &newCookie)
}
6.创建GarbageCollector()方法来删除session
接下来看看如何让session管理器删除session,示例代码如下:
func (manager *SessionManager) GarbageCollector() {
manager.lock.Lock()
defer manager.lock.Unlock()
manager.provider.GarbageCollector(manager.maxLifeTime)
time.AfterFunc(time.Duration(manager.maxLifeTime), func() {
manager.GarbageCollector()
})
}
360

被折叠的 条评论
为什么被折叠?



