type Manager struct { cookieName string // private cookiename lock sync.Mutex // protects session provider Provider maxLifeTime int64 } func NewManager(provideName, cookieName string, maxLifeTime int64) (*Manager, error) { provider, ok := provides[provideName] if !ok { return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName) } return &Manager{provider: provider, cookieName: cookieName, maxLifeTime: maxLifeTime}, nil }Go 實現整個的流程應該也是這樣的,在 main 包中建立一個全域性的 session 管理器。
var globalSessions *session.Manager
//然後在init函數中初始化
func init() {
globalSessions, _ = NewManager("memory", "gosessionid", 3600)
}
type Provider interface {
SessionInit(sid string) (Session, error)
SessionRead(sid string) (Session, error)
SessionDestroy(sid string) error
SessionGC(maxLifeTime int64)
}
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
}
var provides = make(map[string]Provider) //register 通過提供的名稱提供對談。 //如果用相同的名稱呼叫兩次 register,或者如果 driver 為 nil,它會恐慌。 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 }
func (manager *Manager) sessionId() string { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "" } return base64.URLEncoding.EncodeToString(b) }
func (manager *Manager) 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 }我們用前面 login 操作來演示 session 的運用:
func login(w http.ResponseWriter, r *http.Request) { sess := globalSessions.SessionStart(w, r) r.ParseForm() if r.Method == "GET" { t, _ := template.ParseFiles("login.gtpl") 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) } }
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)) } t, _ := template.ParseFiles("count.gtpl") w.Header().Set("Content-Type", "text/html") t.Execute(w, sess.Get("countnum")) }通過上面的例子可以看到,session 的操作和操作 key/value 資料庫類似:Set、Get、Delete 等操作。
//Destroy sessionid func (manager *Manager) 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) } }
func init() { go globalSessions.GC() } func (manager *Manager) GC() { manager.lock.Lock() defer manager.lock.Unlock() manager.provider.SessionGC(manager.maxLifeTime) time.AfterFunc(time.Duration(manager.maxLifeTime), func() { manager.GC() }) }我們可以看到 GC 充分利用了 time 包中的定時器功能,當超時 maxLifeTime 之後呼叫 GC 函數,這樣就可以保證 maxLifeTime 時間內的 session 都是可用的,類似的方案也可以用於統計線上使用者數之類的。
package memory import ( "container/list" "github.com/astaxie/session" "sync" "time" ) var pder = &Provider{list: list.New()} type SessionStore struct { sid string //session id唯一標示 timeAccessed time.Time //最後存取時間 value map[interface{}]interface{} //session裡面儲存的值 } func (st *SessionStore) Set(key, value interface{}) error { st.value[key] = value pder.SessionUpdate(st.sid) return nil } func (st *SessionStore) Get(key interface{}) interface{} { pder.SessionUpdate(st.sid) if v, ok := st.value[key]; ok { return v } else { return nil } } func (st *SessionStore) Delete(key interface{}) error { delete(st.value, key) pder.SessionUpdate(st.sid) return nil } func (st *SessionStore) SessionID() string { return st.sid } type Provider struct { lock sync.Mutex //用來鎖 sessions map[string]*list.Element //用來儲存在記憶體 list *list.List //用來做gc } func (pder *Provider) SessionInit(sid string) (session.Session, error) { pder.lock.Lock() defer pder.lock.Unlock() v := make(map[interface{}]interface{}, 0) newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v} element := pder.list.PushBack(newsess) pder.sessions[sid] = element return newsess, nil } func (pder *Provider) SessionRead(sid string) (session.Session, error) { if element, ok := pder.sessions[sid]; ok { return element.Value.(*SessionStore), nil } else { sess, err := pder.SessionInit(sid) return sess, err } return nil, nil } func (pder *Provider) SessionDestroy(sid string) error { if element, ok := pder.sessions[sid]; ok { delete(pder.sessions, sid) pder.list.Remove(element) return nil } return nil } func (pder *Provider) SessionGC(maxlifetime int64) { pder.lock.Lock() defer pder.lock.Unlock() for { element := pder.list.Back() if element == nil { break } if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() { pder.list.Remove(element) delete(pder.sessions, element.Value.(*SessionStore).sid) } else { break } } } func (pder *Provider) SessionUpdate(sid string) error { pder.lock.Lock() defer pder.lock.Unlock() if element, ok := pder.sessions[sid]; ok { element.Value.(*SessionStore).timeAccessed = time.Now() pder.list.MoveToFront(element) return nil } return nil } func init() { pder.sessions = make(map[string]*list.Element, 0) session.Register("memory", pder) }上面這個程式碼實現了一個記憶體儲存的 session 機制。通過 init 函數註冊到 session 管理器中。這樣就可以方便的呼叫了。我們如何來呼叫該引擎呢?請看下面的程式碼。
import (
"github.com/astaxie/session"
_ "github.com/astaxie/session/providers/memory"
)
var globalSessions *session.Manager //然後在init函數中初始化 func init() { globalSessions, _ = session.NewManager("memory", "gosessionid", 3600) go globalSessions.GC() }
func count(w http.ResponseWriter, r *http.Request) { sess := globalSessions.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.gtpl") w.Header().Set("Content-Type", "text/html") t.Execute(w, sess.Get("countnum")) }count.gtpl 的程式碼如下所示:
Hi. Now count:{{.}}
然後我們在瀏覽器裡面重新整理可以看到如下內容: