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均可用。相似的解决方案可用于计算在线用户。

相关文章
相关标签/搜索