网络编程中cookie和session是必不可少的,今天就简单说一下go语言对session的实现。
图片来源于网络
cookie,简而言之就是在本地计算机保存一些用户操作的历史信息(当然包括登录信息),并在用户再次访问该站点时浏览器通过HTTP协议将本地cookie内容发送给服务器,从而完成验证,或继续上一步操作。
session在Web开发环境下,它的含义是指一类用来在客户端与服务器端之间保持状态的解决方案。有时候Session也用来指这种解决方案的存储结构。
简而言之,cookie是本地的文件,存储一下和服务器相关的内容,session的服务端存储的数据,用来和cookie相互对应,简化用户登录等,因为http都是无状态的,在用户登录后,再访问其他页面时不应该再次登录,所以可以使用cookie和session来解决,我们不可能每一次访问界面都要将用户的账号密码等信息都发送一次,这样不仅是做无用功,而且也很危险。
下面是我用go语言实现的一个简单的session:
package main
import (
"crypto/rand"
"encoding/base64"
"errors"
"io"
"net/http"
"net/url"
"strconv"
"sync"
"time"
)
// SessionMgr session manager
type SessionMgr struct {
cookieName string
mLock sync.RWMutex
maxLifeTime int64
sessions map[string]*Session
}
// Session
type Session struct {
sessionID string
lastTime time.Time
values map[interface{}]interface{}
}
// NewSessionMgr create session manager
func NewSessionMgr(cookieName string, maxLifeTime int64) *SessionMgr {
mgr := &SessionMgr{cookieName: cookieName, maxLifeTime: maxLifeTime, sessions: make(map[string]*Session)}
go mgr.SessionGC()
return mgr
}
// NewSession create session
func (mgr *SessionMgr) NewSession(w http.ResponseWriter, r *http.Request) string {
mgr.mLock.Lock()
defer mgr.mLock.Unlock()
newSessionID := url.QueryEscape(mgr.NewSessionID())
session := &Session{sessionID: newSessionID, lastTime: time.Now(),
values: make(map[interface{}]interface{})}
mgr.sessions[newSessionID] = session
cookie := http.Cookie{Name: mgr.cookieName, Value: newSessionID,
Path: "/", HttpOnly: true, MaxAge: int(mgr.maxLifeTime)}
http.SetCookie(w, &cookie)
return newSessionID
}
// EndSession
func (mgr *SessionMgr) EndSession(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(mgr.cookieName)
if err != nil || cookie.Value == "" {
return
}
mgr.mLock.Lock()
defer mgr.mLock.Unlock()
delete(mgr.sessions, cookie.Value)
newCookie := http.Cookie{Name: mgr.cookieName,
Path: "/", HttpOnly: true,
Expires: time.Now(), MaxAge: -1}
http.SetCookie(w, &newCookie)
}
// EndSessionByID end the session by session ID
func (mgr *SessionMgr) EndSessionByID(sessionID string) {
mgr.mLock.Lock()
defer mgr.mLock.Unlock()
delete(mgr.sessions, sessionID)
}
// SetSessionValue set value fo session
func (mgr *SessionMgr) SetSessionValue(sessionID string, key interface{}, value interface{}) error {
mgr.mLock.Lock()
defer mgr.mLock.Unlock()
if session, ok := mgr.sessions[sessionID]; ok {
session.values[key] = value
return nil
}
return errors.New("invalid session ID")
}
// GetSessionValue get value fo session
func (mgr *SessionMgr) GetSessionValue(sessionID string, key interface{}) (interface{}, error) {
mgr.mLock.RLock()
defer mgr.mLock.RUnlock()
if session, ok := mgr.sessions[sessionID]; ok {
if val, ok := session.values[key]; ok {
return val, nil
}
}
return nil, errors.New("invalid session ID")
}
//CheckCookieValid check cookie is valid or not
func (mgr *SessionMgr) CheckCookieValid(w http.ResponseWriter, r *http.Request) (string, error) {
cookie, err := r.Cookie(mgr.cookieName)
if cookie == nil ||
err != nil {
return "", err
}
mgr.mLock.Lock()
defer mgr.mLock.Unlock()
sessionID := cookie.Value
if session, ok := mgr.sessions[sessionID]; ok {
session.lastTime = time.Now()
return sessionID, nil
}
return "", errors.New("invalid session ID")
}
// SessionGC maintain session
func (mgr *SessionMgr) SessionGC() {
mgr.mLock.Lock()
defer mgr.mLock.Unlock()
for id, session := range mgr.sessions {
if session.lastTime.Unix()+mgr.maxLifeTime < time.Now().Unix() {
delete(mgr.sessions, id)
}
}
time.AfterFunc(time.Duration(mgr.maxLifeTime)*time.Second, func() {
mgr.SessionGC()
})
}
// NewSessionID generate unique ID
func (mgr *SessionMgr) NewSessionID() string {
b := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
nano := time.Now().UnixNano()
return strconv.FormatInt(nano, 10)
}
return base64.URLEncoding.EncodeToString(b)
}
这里我们有一个session管理者和一个session结构体,我们可以创建session,而且生成全局唯一的sessionID,并且设置session的存活时间,手游买号和上一次登录的时间,同样,我们也可以摧毁一个session。这样,当有请求过来时,我们可以根据请求中的cookie的值,找到对应的sessionID,从而找到对应的session。就可以判断这个登录是否有效了。
目前市场上有很多go web的框架,里面都实现了session,当然框架中的实现要比我写的这个完善的多,但是我这个主要是帮助大家加深对web中session的理解,这样即使以后大家看框架的源码时,看到session这部分也不会陌生,而且理解速度也会更快。
首先有一个session manager,用来管理session,在创建sessionmgr同时,创建一个协程,用来管理session是否有效,既SessionGC()。如果有每隔一段时间判断所有session是否有过期的,有则删除掉。然后我们有NewSession和EndSession方法,用来创建session和结束一个session,同时可以将session的对于信息传给cookie。我们还可以对session设置值,根据不同的业务需求,我们可以对每一个session存储一些特定的值,这里我们使用一个map来存储。还有一个重要的接口就是CheckCookieValid()。有了请求之后,我们可以判断发送过来的cookie的数据是否正确,这里我们认为cookie中的value就是sessionID,如果根据sessionID找到对应的session,就说明有效,同时更新session的last time信息。如果没有则返回错误。
这个是非常简单的,如果大家需要自己动手写session,可以参考这个,但是有很多细节都需要根据实际情况来处理,希望大家都能够随机应变,活学活用。