Go Session2:Session創(chuàng)建

session是在服務(wù)器端實(shí)現(xiàn)的一種用戶和服務(wù)器之間認(rèn)證的解決方案肴掷。

session創(chuàng)建過程

session的基本原理是由服務(wù)器為每個(gè)會(huì)話維護(hù)一份信息數(shù)據(jù),客戶端和服務(wù)端依靠一個(gè)全局唯一的標(biāo)識(shí)來(lái)訪問這份數(shù)據(jù)宴杀,以達(dá)到交互的目的。當(dāng)用戶訪問Web應(yīng)用時(shí)筑舅,服務(wù)端程序會(huì)隨需要?jiǎng)?chuàng)建session媒吗,這個(gè)過程可以概括為三個(gè)步驟:

1)生成全局唯一標(biāo)識(shí)符(sessionid)辈毯;

2)開辟數(shù)據(jù)存儲(chǔ)空間坝疼。可以將會(huì)話數(shù)據(jù)寫到文件里或存儲(chǔ)在數(shù)據(jù)庫(kù)中漓摩,這樣雖然會(huì)增加I/O開銷裙士,但是它可以實(shí)現(xiàn)某種程度的session持久化,也更有利于session的共享管毙;

3)將session的全局唯一標(biāo)示符發(fā)送給客戶端(最關(guān)鍵)腿椎;

如何發(fā)送session的唯一標(biāo)識(shí)(sessionID)

1)Cookie服務(wù)端通過設(shè)置Set-cookie頭就可以將session的標(biāo)識(shí)符傳送到客戶端,而客戶端此后的每一次請(qǐng)求都會(huì)帶上這個(gè)標(biāo)識(shí)符夭咬;

2)URL重寫啃炸,就是在返回給用戶的頁(yè)面里的所有的URL后面追加session標(biāo)識(shí)符,這樣用戶在收到響應(yīng)之后卓舵,無(wú)論點(diǎn)擊響應(yīng)頁(yè)面里的哪個(gè)鏈接或提交表單南用,都會(huì)自動(dòng)帶上session標(biāo)識(shí)符,從而就實(shí)現(xiàn)了會(huì)話的保持掏湾。

雖然第二種做法比較麻煩裹虫,但是如果客戶端禁用了cookie的話,此種方案將會(huì)是首選融击。

實(shí)現(xiàn)Session管理

1筑公、session管理設(shè)計(jì)

session管理涉及到如下幾個(gè)因素:

1)全局session管理器;

2)保證sessionID的全局唯一性尊浪;

3)為每個(gè)客戶關(guān)聯(lián)一個(gè)session匣屡;

4)session的存儲(chǔ)(內(nèi)存、文件拇涤、數(shù)據(jù)庫(kù)等)捣作;

5)session過期處理;

2鹅士、Session管理器

定義一個(gè)全局的session管理器:

type SessionManager struct {

????cookieName string????// private cookiename

????lock sync.Mutex ????????// protects session

????provider Provider

????maxLifeTime int64

}

func NewManager(provideName, cookieName string, maxLifeTime int64) (*SessionManager, error) {

????provider, ok := provides[provideName]

????if !ok {

????????return nil, fmt.Errorf("session: unknown provide %q(forgotten import?)", provideName)

????}

????return &SessionManager{provider: provider, cookieName: cookieName, maxLifeTime: maxLifeTime}, nil

}

在main.go中創(chuàng)建一個(gè)全局的Session管理器:

var globalSessions *session.SessionManager

func init() {

? ? globalSessions, _ = NewManager("memory", "gosid", 3600)

}

session是保存在服務(wù)器端的數(shù)據(jù)券躁,它可以以任何的方式存儲(chǔ),比如存儲(chǔ)在內(nèi)存、數(shù)據(jù)庫(kù)或者文件中嘱朽。

我們抽象出一個(gè)Provider接口旭贬,用以表征session管理器底層存儲(chǔ)結(jié)構(gòu)。

type Provider interface {

????SessionInit(sid string) (Session, error)

????SessionRead(sid string) (Session, error)

????SessionDestroy(sid string) error

????SessionGC(maxLifeTime int64)

}

其中:

SessionInit():實(shí)現(xiàn)Session的初始化搪泳,操作成功則返回此新的Session變量;

SessionRead():返回sid所代表的Session變量扼脐,如果不存在岸军,那么將以sid為參數(shù)調(diào)用SessionInit函數(shù)創(chuàng)建并返回一個(gè)新的Session變量;

SessionDestroy():用來(lái)銷毀sid對(duì)應(yīng)的Session變量瓦侮;

SessionGC():根據(jù)maxLifeTime來(lái)刪除過期的數(shù)據(jù)艰赞;

Session接口實(shí)現(xiàn)功能:

type Session 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

}

注冊(cè)Session實(shí)現(xiàn):

var provides = make(map[string]Provider)

//Register makes a session provide available by the provided name.

//If Register is called twice with the same name or if driver is nil,

//it panics.

func Register(name string, provider Provider) {

? ? if provider == nil {

? ? ? ? panic("Session: Register provider is nil")

? ? }

? ? if _, dup := provides[name]; dup {

? ? ? ? panic("session: Register called twice for provider " + name)

? ? }

? ? provides[name] = provider

}

全局唯一的SessionID

import (

????crand "crypto/rand"

????"encoding/base64"

????"fmt"

????"io"

)

func main() {

????sessionid := sessionId()

????fmt.Println("sessionid:", sessionid)

}

func sessionId() string {

????r := make([]byte, 32)

????if _, err := io.ReadFull(crand.Reader, r); err != nil {

????????return ""

????}

????return base64.URLEncoding.EncodeToString(r)

}

session創(chuàng)建

檢測(cè)是否已經(jīng)有某個(gè)Session與當(dāng)前來(lái)訪用戶發(fā)生了關(guān)聯(lián),如果沒有則創(chuàng)建肚吏。

func (manager *SessionManager) SessionStart(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 == "" {

????????sid := manager.sessionId()

????????session, _ = manager.provider.SessionInit(sid)

????????cookie := http.Cookie{

????????????Name:? ? manager.cookieName,

????????????Value:? ? url.QueryEscape(sid),

????????????Path:? ? "/",

????????????HttpOnly: true,

????????????MaxAge:? int(manager.maxLifeTime),

????????}

????????http.SetCookie(w, &cookie)

????} else {

????????sid, _ := url.QueryUnescape(cookie.Value)

????????session, _ = manager.provider.SessionRead(sid)

????}

????return

}

Session的調(diào)用:

func login(w http.ResponseWriter, r *http.Request) {

? ? sess := globalSessions.SessionStart(w, r)

? ? r.ParseForm()

? ? if r.Method == "GET" {

? ? ? ? sid = sess.Get("uid")

? ? } else {

? ? ? ? sess.Set("uid", r.Form["username"])

? ? }

}

Session操作:設(shè)置方妖、讀取和刪除

func count(w http.ResponseWriter, r *http.Request) {

????sess := globalSessions.SessionStart(w, r)

????createtime := sess.Get("createtime")

????if createtime == nil {

????????sess.Set("createtime", time.Now().Unix())

????} else if (createtime.(int64) + 360) < (time.Now().Unix()) {

????????globalSessions.SessionDestroy(w, r)

????????sess = globalSessions.SessionStart(w, r)

????}

????ct := sess.Get("countnum")

????if ct == nil {

????????sess.Set("countnum", 1)

????} else {

????????sess.Set("countnum", (ct.(int) + 1))

????}

????fmt.Println("countnum:" + countnum)

}

session重置

在Web中用戶退出登錄時(shí),需要對(duì)該用戶的session數(shù)據(jù)進(jìn)行銷毀操作罚攀。

func (manager *SessionManager) SessionDestroy(w http.ResponseWriter, r *http.Request) {

????cookie, err := r.Cookie(manager.cookieName)

????if err != nil || cookie.Value == "" {

????????return

????} else {

????????manager.lock.Lock()

????????defer manager.lock.Unlock()

????????manager.provider.SessionDestroy(cookie.Value)

????????expiration := time.Now()

????????cookie := http.Cookie{

????????????Name:? ? manager.cookieName,

????????????Path:? ? "/",

????????????HttpOnly: true,

????????????Expires:? expiration,

????????????MaxAge:? -1,

????????}

????????http.SetCookie(W, &cookie)

????}

}

session銷毀

在main.go啟動(dòng)的時(shí)候党觅,啟動(dòng):

func init() {

? ? go globalSessions.GC()

}

func (manager *SessionManager) GC() {

? ? manager.lock.Lock()

? ? defer manager.lock.Unlock()

? ? manager.provider.SessionGC(manager.maxLifeTime)

? ? time.AfterFunc(time.Duration(manager.maxLifeTime), func() {

? ? ? ? manager.GC()

? ? })

}

以上代碼中,GC利用了time包中的定時(shí)器功能斋泄,當(dāng)超時(shí)maxLifeTime之后調(diào)用GC函數(shù)杯瞻,這樣就可以保證maxLifeTime時(shí)間內(nèi)的session都是可用的,類似的方案也可以用于統(tǒng)計(jì)在線用戶數(shù)之類的炫掐。

學(xué)習(xí)代碼:https://github.com/astaxie/beego/tree/master/session

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末魁莉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子募胃,更是在濱河造成了極大的恐慌旗唁,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痹束,死亡現(xiàn)場(chǎng)離奇詭異检疫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)参袱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門电谣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人抹蚀,你說我怎么就攤上這事剿牺。” “怎么了环壤?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵晒来,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我郑现,道長(zhǎng)湃崩,這世上最難降的妖魔是什么荧降? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮攒读,結(jié)果婚禮上朵诫,老公的妹妹穿的比我還像新娘。我一直安慰自己薄扁,他們只是感情好剪返,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邓梅,像睡著了一般脱盲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上日缨,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天钱反,我揣著相機(jī)與錄音,去河邊找鬼匣距。 笑死面哥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的墨礁。 我是一名探鬼主播幢竹,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼恩静!你這毒婦竟也來(lái)了焕毫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤驶乾,失蹤者是張志新(化名)和其女友劉穎邑飒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體级乐,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疙咸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了风科。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撒轮。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贼穆,靈堂內(nèi)的尸體忽然破棺而出题山,到底是詐尸還是另有隱情,我是刑警寧澤故痊,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布顶瞳,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏慨菱。R本人自食惡果不足惜焰络,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望符喝。 院中可真熱鬧闪彼,春花似錦、人聲如沸协饲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)囱稽。三九已至,卻和暖如春二跋,著一層夾襖步出監(jiān)牢的瞬間战惊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工扎即, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吞获,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓谚鄙,卻偏偏與公主長(zhǎng)得像各拷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闷营,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容