7.6 Session
Go 语言实现操作session不像cookie那样,net/http包里有现成函数可以很方便的使用,一些web服务用到session的话,没办法地自己敲代码实现。
Go具体实现session:
- 服务端可以通过内存、redis、数据库等存储session数据(本例只有内存)。
- 通过cookie将唯一SessionID发送到客户端
session.go
package session
import (
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"
"sync"
"time"
)
//session存储方式接口
type Provider interface {
//初始化一个session,sid根据需要生成后传入
SessionInit(sid string) (Session, error)
//根据sid,获取session
SessionRead(sid string) (Session, error)
//销毁session
SessionDestroy(sid string) error
//回收
SessionGC(maxLifeTime int64)
}
//Session操作接口
type Session interface {
Set(key, value interface{}) error
Get(key interface{}) interface{}
Delete(ket interface{}) error
SessionID() string
}
type Manager struct {
cookieName string
lock sync.Mutex //互斥锁
provider Provider //存储session方式
maxLifeTime int64 //有效期
}
//示例化一个session管理器
func NewSessionManager(provideName, cookieName string, maxLifeTime int64) (*Manager, error) {
provide, ok := provides[provideName]
if !ok {
return nil, fmt.Errorf("session: unknown provide %q ", provideName)
}
return &Manager{cookieName: cookieName, provider: provide, maxLifeTime: maxLifeTime}, nil
}
//注册 由实现Provider接口的结构体调用
func Register(name string, provide Provider) {
if provide == nil {
panic("session: Register provide is nil")
}
if _, ok := provides[name]; ok {
panic("session: Register called twice for provide " + name)
}
provides[name] = provide
}
var provides = make(map[string]Provider)
//生成sessionId
func (manager *Manager) sessionId() string {
b := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return ""
}
//加密
return base64.URLEncoding.EncodeToString(b)
}
//判断当前请求的cookie中是否存在有效的session,存在返回,否则创建
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),
Expires: time.Now().Add(time.Duration(manager.maxLifeTime)),
//MaxAge和Expires都可以设置cookie持久化时的过期时长,Expires是老式的过期方法,
// 如果可以,应该使用MaxAge设置过期时间,但有些老版本的浏览器不支持MaxAge。
// 如果要支持所有浏览器,要么使用Expires,要么同时使用MaxAge和Expires。
}
http.SetCookie(w, &cookie)
} else {
sid, _ := url.QueryUnescape(cookie.Value) //反转义特殊符号
session, _ = manager.provider.SessionRead(sid)
}
return session
}
//销毁session 同时删除cookie
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()
sid, _ := url.QueryUnescape(cookie.Value)
manager.provider.SessionDestroy(sid)
expiration := time.Now()
cookie := http.Cookie{
Name: manager.cookieName,
Path: "/",
HttpOnly: true,
Expires: expiration,
MaxAge: -1}
http.SetCookie(w, &cookie)
}
}
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() })
}
memory.go
package memory
import (
"container/list"
"example/example/public/session"
"sync"
"time"
)
var pder = &FromMemory{list: list.New()}
func init() {
pder.sessions = make(map[string]*list.Element, 0)
//注册 memory 调用的时候一定有一致
session.Register("memory", pder)
}
//session实现
type SessionStore struct {
sid string //session id 唯一标示
LastAccessedTime 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
}
//获取session
func (st *SessionStore) Get(key interface{}) interface{} {
pder.SessionUpdate(st.sid)
if v, ok := st.value[key]; ok {
return v
} else {
return nil
}
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
}
//session来自内存 实现
type FromMemory struct {
lock sync.Mutex //用来锁
sessions map[string]*list.Element //用来存储在内存
list *list.List //用来做 gc
}
func (frommemory *FromMemory) SessionInit(sid string) (session.Session, error) {
frommemory.lock.Lock()
defer frommemory.lock.Unlock()
v := make(map[interface{}]interface{}, 0)
newsess := &SessionStore{sid: sid, LastAccessedTime: time.Now(), value: v}
element := frommemory.list.PushBack(newsess)
frommemory.sessions[sid] = element
return newsess, nil
}
func (frommemory *FromMemory) SessionRead(sid string) (session.Session, error) {
if element, ok := frommemory.sessions[sid]; ok {
return element.Value.(*SessionStore), nil
} else {
sess, err := frommemory.SessionInit(sid)
return sess, err
}
return nil, nil
}
func (frommemory *FromMemory) SessionDestroy(sid string) error {
if element, ok := frommemory.sessions[sid]; ok {
delete(frommemory.sessions, sid)
frommemory.list.Remove(element)
return nil
}
return nil
}
func (frommemory *FromMemory) SessionGC(maxLifeTime int64) {
frommemory.lock.Lock()
defer frommemory.lock.Unlock()
for {
element := frommemory.list.Back()
if element == nil {
break
}
if (element.Value.(*SessionStore).LastAccessedTime.Unix() + maxLifeTime) <
time.Now().Unix() {
frommemory.list.Remove(element)
delete(frommemory.sessions, element.Value.(*SessionStore).sid)
} else {
break
}
}
}
func (frommemory *FromMemory) SessionUpdate(sid string) error {
frommemory.lock.Lock()
defer frommemory.lock.Unlock()
if element, ok := frommemory.sessions[sid]; ok {
element.Value.(*SessionStore).LastAccessedTime = time.Now()
frommemory.list.MoveToFront(element)
return nil
}
return nil
}
memory.go里面是sesson.go 的Provider 和Session 接口的具体实现,通过这种方式可以灵活的扩展存取session数据的方式。
调用示例:
main.go
package main
import (
_ "example/example/public/memory" //这里修改成你存放menory.go相应的目录
"example/example/public/session" //这里修改成你存放session.go相应的目录
"fmt"
"log"
"net/http"
)
var globalSessions *session.Manager
func init() {
var err error
globalSessions, err = session.NewSessionManager("memory", "goSessionid", 3600)
if err != nil {
fmt.Println(err)
return
}
go globalSessions.GC()
fmt.Println("fd")
}
func sayHelloHandler(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("name")
if err == nil {
fmt.Println(cookie.Value)
fmt.Println(cookie.Domain)
fmt.Println(cookie.Expires)
}
//fmt.Fprintf(w, "Hello world!\n") //这个写入到w的是输出到客户端的
}
func login(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
val := sess.Get("username")
if val != nil {
fmt.Println(val)
} else {
sess.Set("username", "jerry")
fmt.Println("set session")
}
}
func loginOut(w http.ResponseWriter, r *http.Request) {
//销毁
globalSessions.SessionDestroy(w, r)
fmt.Println("session destroy")
}
func main() {
http.HandleFunc("/", sayHelloHandler) // 设置访问路由
http.HandleFunc("/login", login)
http.HandleFunc("/loginout", loginOut) //销毁
log.Fatal(http.ListenAndServe(":8080", nil))
}
memory 包的应用方式用下划线,只需执行 memory的init方法即可。
运行 main.go
访问 http://localhost:8080/login 设置session
服务端输出:
set session
jerry
访问http://localhost:8080/loginout 销毁session
服务端输出:
session destroy