beego session源码分析

Provider和Store接口(session.go)

Store接口定义一系列session存储销毁的规范

// Store contains all data for one session process with specific id.
type Store interface {
    Set(key, value interface{}) error     //set session value
    Get(key interface{}) interface{}      //get session value
    Delete(key interface{}) error         //delete session value
    SessionID() string                    //back current sessionID
    SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data
    Flush() error                         //delete all data
}

Provider是session存储的提供者,定义session的存储方式

// Provider contains global session methods and saved SessionStores.
// it can operate a SessionStore by its id.
type Provider interface {
    SessionInit(gclifetime int64, config string) error
    SessionRead(sid string) (Store, error)
    SessionExist(sid string) bool
    SessionRegenerate(oldsid, sid string) (Store, error)
    SessionDestroy(sid string) error
    SessionAll() int //get all active session
    SessionGC()
}

注册自定义的Provider

实现session和provider中的方法,并且注册..
redis sessionStore的持有value map,保存现有session,session操作都会暂存至values map键值对。同时HttpServer每次请求后都会调用SessionRelease(rw)将数据保存至redis

type SessionStore struct {
    p           *redis.Pool
    sid         string
    lock        sync.RWMutex
    values      map[interface{}]interface{}
    maxlifetime int64
}
func init() {
    ## 调用session.go中的Register方法
    session.Register("redis", redispder)
}
func Register(name string, provide Provider) {
    if provide == nil {
        panic("session: Register provide is nil")
    }
    if _, dup := provides[name]; dup {
        panic("session: Register called twice for provider " + name)
    }
    provides[name] = provide
}

session.go中定义了 var provides = make(map[string]Provider) 持有注册的Provider

session流程

  1. beego.run()

关注initBeforeHTTPRun函数

func Run(params ...string) {
    
    initBeforeHTTPRun()
    
    if len(params) > 0 && params[0] != "" {
        strs := strings.Split(params[0], ":")
        if len(strs) > 0 && strs[0] != "" {
            BConfig.Listen.HTTPAddr = strs[0]
        }
        if len(strs) > 1 && strs[1] != "" {
            BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
        }
    }
    
    BeeApp.Run()
}

2、注册系列hooks(hooks.go)

func initBeforeHTTPRun() {
    //init hooks
    ...
    AddAPPStartHook(registerSession)
    ...
    
    //挨个执行每个register函数
    for _, hk := range hooks {
        if err := hk(); err != nil {
            panic(err)
        }
    }
}
  1. registerSession(hook.go)

获取session配置项,调用NewManager,得到gobelSessions。gobelSessions即为reids session manager

func registerSession() error {
    if BConfig.WebConfig.Session.SessionOn {
        var err error
        sessionConfig := AppConfig.String("sessionConfig")
        conf := new(session.ManagerConfig)
        if sessionConfig == "" {
            conf.CookieName = BConfig.WebConfig.Session.SessionName
            conf.EnableSetCookie = BConfig.WebConfig.Session.SessionAutoSetCookie
            conf.Gclifetime = BConfig.WebConfig.Session.SessionGCMaxLifetime
            conf.Secure = BConfig.Listen.EnableHTTPS
            conf.CookieLifeTime = BConfig.WebConfig.Session.SessionCookieLifeTime
            conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig)
            conf.DisableHTTPOnly = BConfig.WebConfig.Session.SessionDisableHTTPOnly
            conf.Domain = BConfig.WebConfig.Session.SessionDomain
            conf.EnableSidInHTTPHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader
            conf.SessionNameInHTTPHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader
            conf.EnableSidInURLQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery
        } else {
            if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
                return err
            }
        }
        if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, conf); err != nil {
            return err
        }
        go GlobalSessions.GC()
    }
    return nil
}

5、从持有注册provider的map中根据provider name得到Manger

func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) {
    provider, ok := provides[provideName]
    if !ok {
        return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
    }
    
    if cf.Maxlifetime == 0 {
        cf.Maxlifetime = cf.Gclifetime
    }
    
    if cf.EnableSidInHTTPHeader {
        if cf.SessionNameInHTTPHeader == "" {
            panic(errors.New("SessionNameInHTTPHeader is empty"))
        }
    
        strMimeHeader := textproto.CanonicalMIMEHeaderKey(cf.SessionNameInHTTPHeader)
        if cf.SessionNameInHTTPHeader != strMimeHeader {
            strErrMsg := "SessionNameInHTTPHeader (" + cf.SessionNameInHTTPHeader + ") has the wrong format, it should be like this : " + strMimeHeader
            panic(errors.New(strErrMsg))
        }
    }
    
    err := provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig)
    if err != nil {
        return nil, err
    }
    
    if cf.SessionIDLength == 0 {
        cf.SessionIDLength = 16
    }
    
    return &Manager{
        provider,
        cf,
    }, nil
}

6、httpServer handler中初始化session

  • 检测是否开启session
  • 初始化上下文context.input.CruSession
  • 调用session.go中的SessionStart(rw, r)。验证context.input.CruSession是否有session,如果没有则生成sessionId,返回cookie
  • 最后执行redis session 的SessionRelease(rw)
// Implement http.Handler interface.
func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
    
    // 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)
            }
        }()
    }

初始化,从cookie中获取sid,如果存在则直接读取,如果不存在则生成sessionId,并放入cookie,加入response

func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Store, err error) {
    //从cookie获取sessionId
    sid, errs := manager.getSid(r)
    if errs != nil {
        return nil, errs
    }
    
    //如果存在直接读取,调用redis session的SessionRead方法,读取完成后放入sessionStore
    if sid != "" && manager.provider.SessionExist(sid) {
        return manager.provider.SessionRead(sid)
    }
    
    // 如果不存在创建sessionId
    sid, errs = manager.sessionID()
    if errs != nil {
        return nil, errs
    }
    
    session, err = manager.provider.SessionRead(sid)
    if err != nil {
        return nil, err
    }
    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
}
session.go

func (manager *Manager) getSid(r *http.Request) (string, error) {
    //根据配置的cookieName 从cookie中获取sid
    cookie, errs := r.Cookie(manager.config.CookieName)
    if errs != nil || cookie.Value == "" {
        var sid string
        if manager.config.EnableSidInURLQuery {
            errs := r.ParseForm()
            if errs != nil {
                return "", errs
            }
    
            sid = r.FormValue(manager.config.CookieName)
        }
    
        // if not found in Cookie / param, then read it from request headers
        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)
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 背景在HTTP协议的定义中,采用了一种机制来记录客户端和服务器端交互的信息,这种机制被称为cookie,cooki...
    时芥蓝阅读 2,353评论 1 17
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • 以前吧,有什么不开心的还会找父母,现在,孤独,没有朋友,妈妈也不关心我,只有自己对自己好。。。家里也好久没来电话,...
    安稳的猪阅读 252评论 0 0
  • 江歌案至今,风波无数,有人痛斥刘鑫不堪为人,也有人回护刘鑫直指网络暴力。 人是社会性动物,有社会就有舆论,谁都没有...
    女王蜂data阅读 200评论 0 1