记得以前面试会有被问到 session 和 cookie 的区别,今天咱们梳理下 cookie 和 session 到底有什么区别。
常见解释:
- session 在服务器端,cookie 在客户端(浏览器)
- session 默认被存在在服务器的一个文件里(不是内存)
- session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
- session 可以放在 文件、数据库、或内存中都可以。
我们通过分析 beego 的 session 实现来具体验证一下以上几点
测试验证:
beego session 使用
beego 内置了 session 模块,目前 session 模块支持的后端引擎包括 memory、cookie、file、mysql、redis、couchbase、memcache、postgres,用户也可以根据相应的 interface 实现自己的引擎。
beego 中使用 session 相当方便,只要在 main 入口函数中设置如下:
beego.BConfig.WebConfig.Session.SessionOn = true
或者通过配置文件配置如下:
sessionon = true
通过这种方式就可以开启 session,如何使用 session,请看下面的例子:
func (this *MainController) Get() {
v := this.GetSession("asta")
if v == nil {
this.SetSession("asta", int(1))
this.Data["num"] = 0
} else {
this.SetSession("asta", v.(int)+1)
this.Data["num"] = v.(int)
}
this.TplName = "index.tpl"
}
源码分析
controller 实现的 GetSession、SetSession 方法可以读取、设置 session 信息,那么它做了什么呢。
// StartSession starts session and load old session data info this controller.
func (c *Controller) StartSession() session.Store {
if c.CruSession == nil {
c.CruSession = c.Ctx.Input.CruSession
}
return c.CruSession
}
// SetSession puts value into session.
func (c *Controller) SetSession(name interface{}, value interface{}) {
if c.CruSession == nil {
c.StartSession()
}
c.CruSession.Set(name, value)
}
// GetSession gets value from session.
func (c *Controller) GetSession(name interface{}) interface{} {
if c.CruSession == nil {
c.StartSession()
}
return c.CruSession.Get(name)
}
我们可以看到 session 的数据是通过 CruSession(session 仓库)进行存取
beego 的 router 路由处理请求时 SessionStart 设置了 CruSession
// session init
if BConfig.WebConfig.Session.SessionOn {
var err error
context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r)
if err != nil {
logs.Error(err)
exception("503", context)
goto Admin
}
defer func() {
if context.Input.CruSession != nil {
context.Input.CruSession.SessionRelease(rw)
}
}()
}
SessionStart 类似于 php 的 session_start 此方法是 session 依赖 cookie 实现的关键,大家看下代码中我的注释,了解下代码运行逻辑就明白了。
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Store, err error) {
//获取当前用户的sid sid会通过用户的cookie,请求参数 以及header 中读取
sid, errs := manager.getSid(r)
if errs != nil {
return nil, errs
}
// sid不为空并且存在此sid对应的session,返回session存储数据结构
if sid != "" && manager.provider.SessionExist(sid) {
return manager.provider.SessionRead(sid)
}
// 因为sid为空生成一个 sid
sid, errs = manager.sessionID()
if errs != nil {
return nil, errs
}
//根据sid生成一个session存储数据结构
session, err = manager.provider.SessionRead(sid)
if err != nil {
return nil, err
}
//组装sid的cookie信息并在返回header中设置此cookie
cookie := &http.Cookie{
Name: manager.config.CookieName,
Value: url.QueryEscape(sid),
Path: "/",
HttpOnly: !manager.config.DisableHTTPOnly,
Secure: manager.isSecure(r),
Domain: manager.config.Domain,
}
if manager.config.CookieLifeTime > 0 {
cookie.MaxAge = manager.config.CookieLifeTime
cookie.Expires = time.Now().Add(time.Duration(manager.config.CookieLifeTime) * time.Second)
}
if manager.config.EnableSetCookie {
http.SetCookie(w, cookie)
}
r.AddCookie(cookie)
if manager.config.EnableSidInHTTPHeader {
r.Header.Set(manager.config.SessionNameInHTTPHeader, sid)
w.Header().Set(manager.config.SessionNameInHTTPHeader, sid)
}
return
}
func (manager *Manager) getSid(r *http.Request) (string, error) {
//cookie中读取sid
cookie, errs := r.Cookie(manager.config.CookieName)
if errs != nil || cookie.Value == "" {
var sid string
//请求参数中读取cookie
if manager.config.EnableSidInURLQuery {
errs := r.ParseForm()
if errs != nil {
return "", errs
}
sid = r.FormValue(manager.config.CookieName)
}
// 如果cookie 请求参数中都没有sid 那就从请求的header中读取
if manager.config.EnableSidInHTTPHeader && sid == "" {
sids, isFound := r.Header[manager.config.SessionNameInHTTPHeader]
if isFound && len(sids) != 0 {
return sids[0], nil
}
}
return sid, nil
}
// HTTP Request contains cookie for sessionid info.
return url.QueryUnescape(cookie.Value)
}
我们了解以上代码的话,可以总结流程为:
-
读取用户的 session_id
session_id 可以通过 cookie/请求参数/header 参数 读取
-
通过 session_id 获取用户的 session 存储
type SessionStore struct {
sid string
lock sync.RWMutex
values map[interface{}]interface{}
maxlifetime int64
}
sid(session_id)、lock(读写锁)、values(session 存储的数据)、maxlifetime(session 的有效期) session 的存储由以上几个基本元素构成,我们也可以实现一个自己的存储。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于