Go基礎學習記錄之如何在Golang中使用Session

Session背後的基本原則是服務器維護每一個客戶端的信息,客戶端依賴惟一的SessionID來訪問此信息。
當用戶訪問Web應用程序時,服務器將根據須要使用如下三個步驟建立新Session:html

  1. 建立惟一的Session ID
  2. 打開數據存儲空間:一般咱們將Session保存在內存中,但若是系統意外中斷,您將丟失全部Session數據。若是Web應用程序處理敏感數據(例如電子商務),這多是一個很是嚴重的問題。爲了解決此問題,您能夠將Session數據保存在數據庫或文件系統中。這使得數據持久性更加可靠,而且易於與其餘應用程序共享,但須要權衡的是,讀取和寫入這些Session須要更多的服務器端IO。
  3. 將惟一SessionID發送到客戶端。

這裏的關鍵步驟是將惟一Session ID發送到客戶端。在標準HTTP響應的上下文中,您可使用響應行,標題或正文來完成此操做;所以,咱們有兩種方法將Session ID發送給客戶端:經過cookie或URL重寫。sql

  1. Cookie:服務器能夠輕鬆地在響應標頭內使用Set-cookie將Session ID發送到客戶端,而後客戶端能夠將此cookie用於未來的請求;咱們常常將包含Session信息的cookie的到期時間設置爲0,這意味着cookie將保存在內存中,而且只有在用戶關閉瀏覽器後纔會被刪除。
  2. URL重寫:將Session ID做爲參數附加到全部頁面的URL中。這種方式看起來很混亂,但若是客戶在瀏覽器中禁用了cookie,那麼這是最好的選擇。

使用Go來管理Session

Session管理設計

  1. 全局Session管理。
  2. 保持Session ID惟一。
  3. 爲每一個用戶準備一個Session。
  4. Session存儲在內存,文件或數據庫中。
  5. 處理過時的Session。

接下來,經過完整實例來演示下如何實現上面的設計數據庫

全局Session管理

定義全局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)
}
  1. SessionInit實現Session的初始化,若是成功則返回新Session。
  2. SessionRead返回由相應sid表示的Session。建立一個新Session,若是它尚不存在則返回它。
  3. SessionDestroy給定一個sid,刪除相應的Session。
  4. SessionGC根據maxLifeTime刪除過時的Session變量。那麼咱們的Session接口應該有什麼方法呢?若是您有任何Web開發經驗,您應該知道Session只有四個操做:設置值,獲取值,刪除值和獲取當前Session ID。所以,咱們的Session接口應該有四種方法來執行這些操做。
// 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

咱們須要分配或獲取現有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)
    }
}

Session的相關操做

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。

註銷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管理器刪除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均可用。相似的解決方案可用於計算在線用戶。

相關文章
相關標籤/搜索