Session背後的基本原則是服務器維護每一個客戶端的信息,客戶端依賴惟一的SessionID來訪問此信息。
當用戶訪問Web應用程序時,服務器將根據須要使用如下三個步驟建立新Session:html
這裏的關鍵步驟是將惟一Session ID發送到客戶端。在標準HTTP響應的上下文中,您可使用響應行,標題或正文來完成此操做;所以,咱們有兩種方法將Session ID發送給客戶端:經過cookie或URL重寫。sql
接下來,經過完整實例來演示下如何實現上面的設計數據庫
定義全局Session管理器:瀏覽器
// Manager Session管理 type Manager struct { cookieName string lock sync.Mutex provider Provider maxLifeTime int64 } // GetManager 獲取Session Manager func GetManager(providerName string, cookieName string, maxLifeTime int64) (*Manager, error) { provider, ok := providers[providerName] if !ok { return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", providerName) } return &Manager{ cookieName: cookieName, maxLifeTime: maxLifeTime, provider: provider, }, nil }
在main()函數中建立一個全局Session管理器:服務器
var appSession *Manager // 初始化session manager func init() { appSession, _ = GetManager("memory", "sessionid", 3600) go appSession.SessionGC() }
咱們知道咱們能夠經過多種方式保存Session,包括內存,文件系統或直接進入數據庫。咱們須要定義一個Provider接口,以表示Session管理器的底層結構:cookie
// Provider 接口 type Provider interface { SessionInit(sid string) (Session, error) SessionRead(sid string) (Session, error) SessionDestroy(sid string) error SessionGC(maxLifeTime int64) }
// Session 接口 type Session interface { Set(key, value interface{}) error // 設置Session Get(key interface{}) interface{} // 獲取Session Del(key interface{}) error // 刪除Session SID() string // 當前Session ID }
這個設計源於database/sql/driver,它首先定義接口,而後在咱們想要使用它時註冊特定的結構。如下代碼是Session寄存器功能的內部實現。session
var providers = make(map[string]Provider) // RegisterProvider 註冊Session 寄存器 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 }
保持Session ID惟一app
Session ID用於標識Web應用程序的用戶,所以它們必須是惟一的。如下代碼顯示瞭如何實現此目標:ide
// GenerateSID 產生惟一的Session ID func (m *Manager) GenerateSID() string { b := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, b); err != nil { return "" } return base64.URLEncoding.EncodeToString(b) }
咱們須要分配或獲取現有Session以驗證用戶操做。SessionStart函數用於檢查與當前用戶相關的任何Session的存在,並在未找到任何Session時建立新Session。函數
// SessionStart 啓動Session功能 func (m *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) { m.lock.Lock() defer m.lock.Unlock() cookie, err := r.Cookie(m.cookieName) if err != nil || cookie.Value == "" { sid := m.GenerateSID() session, _ := m.provider.SessionInit(sid) newCookie := http.Cookie{ Name: m.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(m.maxLifeTime), } http.SetCookie(w, &newCookie) } else { sid, _ := url.QueryUnescape(cookie.Value) session, _ := m.provider.SessionRead(sid) } return }
如下是使用Session進行登陸操做的示例。
func login(w http.ResponseWriter, r *http.Request) { sess := appSession.SessionStart(w, r) r.ParseForm() if r.Method == "GET" { t, _ := template.ParseFiles("login.html") w.Header().Set("Content-Type", "text/html") t.Execute(w, sess.Get("username")) } else { sess.Set("username", r.Form["username"]) http.Redirect(w, r, "/", 302) } }
SessionStart函數返回實現Session接口的變量。咱們如何使用它?您在上面的示例中看到了session.Get("uid")以進行基本操做。如今讓咱們來看一個更詳細的例子。
func count(w http.ResponseWriter, r *http.Request) { sess := appSession.SessionStart(w, r) createtime := sess.Get("createtime") if createtime == nil { sess.Set("createtime", time.Now().Unix()) } else if (createtime.(int64) + 360) < (time.Now().Unix()) { appSession.SessionDestroy(w, r) sess = appSession.SessionStart(w, r) } ct := sess.Get("countnum") if ct == nil { sess.Set("countnum", 1) } else { sess.Set("countnum", (ct.(int) + 1)) } t, _ := template.ParseFiles("count.html") w.Header().Set("Content-Type", "text/html") t.Execute(w, sess.Get("countnum")) }
如您所見,對Session進行操做只需在Set,Get和Delete操做中使用鍵/值模式。因爲Session具備到期時間的概念,所以咱們定義GC以更新Session的最新修改時間。這樣,GC將不會刪除已過時但仍在使用的Session。
咱們知道Web應用程序具備註銷操做。當用戶註銷時,咱們須要刪除相應的Session。咱們已經在上面的示例中使用了重置操做 - 如今讓咱們看一下函數體。
// SessionDestory 註銷Session func (m *Manager) SessionDestory(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(m.cookieName) if err != nil || cookie.Value == "" { return } m.lock.Lock() defer m.lock.Unlock() m.provider.SessionDestroy(cookie.Value) expiredTime := time.Now() newCookie := http.Cookie{ Name: m.cookieName, Path: "/", HttpOnly: true, Expires: expiredTime, MaxAge: -1, } http.SetCookie(w, &newCookie) }
讓咱們看看如何讓Session管理器刪除Session。咱們須要在main()函數中啓動GC:
func init() { go appSession.SessionGC() } // SessionGC Session 垃圾回收 func (m *Manager) SessionGC() { m.lock.Lock() defer m.lock.Unlock() m.provider.SessionGC(m.maxLifeTime) time.AfterFunc(time.Duration(m.maxLifeTime), func() { m.SessionGC() }) }
咱們看到GC充分利用了時間包中的計時器功能。它會在Session超時時自動調用GC,確保在maxLifeTime期間全部Session均可用。相似的解決方案可用於計算在線用戶。