7.6 Session

Go 语言实现操作session不像cookie那样,net/http包里有现成函数可以很方便的使用,一些web服务用到session的话,没办法地自己敲代码实现。

Go具体实现session:

  • 服务端可以通过内存、redis、数据库等存储session数据(本例只有内存)。
  • 通过cookie将唯一SessionID发送到客户端

session.go

  1. package session
  2. import (
  3. "crypto/rand"
  4. "encoding/base64"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "net/url"
  9. "sync"
  10. "time"
  11. )
  12. //session存储方式接口
  13. type Provider interface {
  14. //初始化一个session,sid根据需要生成后传入
  15. SessionInit(sid string) (Session, error)
  16. //根据sid,获取session
  17. SessionRead(sid string) (Session, error)
  18. //销毁session
  19. SessionDestroy(sid string) error
  20. //回收
  21. SessionGC(maxLifeTime int64)
  22. }
  23. //Session操作接口
  24. type Session interface {
  25. Set(key, value interface{}) error
  26. Get(key interface{}) interface{}
  27. Delete(ket interface{}) error
  28. SessionID() string
  29. }
  30. type Manager struct {
  31. cookieName string
  32. lock sync.Mutex //互斥锁
  33. provider Provider //存储session方式
  34. maxLifeTime int64 //有效期
  35. }
  36. //示例化一个session管理器
  37. func NewSessionManager(provideName, cookieName string, maxLifeTime int64) (*Manager, error) {
  38. provide, ok := provides[provideName]
  39. if !ok {
  40. return nil, fmt.Errorf("session: unknown provide %q ", provideName)
  41. }
  42. return &Manager{cookieName: cookieName, provider: provide, maxLifeTime: maxLifeTime}, nil
  43. }
  44. //注册 由实现Provider接口的结构体调用
  45. func Register(name string, provide Provider) {
  46. if provide == nil {
  47. panic("session: Register provide is nil")
  48. }
  49. if _, ok := provides[name]; ok {
  50. panic("session: Register called twice for provide " + name)
  51. }
  52. provides[name] = provide
  53. }
  54. var provides = make(map[string]Provider)
  55. //生成sessionId
  56. func (manager *Manager) sessionId() string {
  57. b := make([]byte, 32)
  58. if _, err := io.ReadFull(rand.Reader, b); err != nil {
  59. return ""
  60. }
  61. //加密
  62. return base64.URLEncoding.EncodeToString(b)
  63. }
  64. //判断当前请求的cookie中是否存在有效的session,存在返回,否则创建
  65. func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
  66. manager.lock.Lock() //加锁
  67. defer manager.lock.Unlock()
  68. cookie, err := r.Cookie(manager.cookieName)
  69. if err != nil || cookie.Value == "" {
  70. //创建一个
  71. sid := manager.sessionId()
  72. session, _ = manager.provider.SessionInit(sid)
  73. cookie := http.Cookie{
  74. Name: manager.cookieName,
  75. Value: url.QueryEscape(sid), //转义特殊符号@#¥%+*-等
  76. Path: "/",
  77. HttpOnly: true,
  78. MaxAge: int(manager.maxLifeTime),
  79. Expires: time.Now().Add(time.Duration(manager.maxLifeTime)),
  80. //MaxAge和Expires都可以设置cookie持久化时的过期时长,Expires是老式的过期方法,
  81. // 如果可以,应该使用MaxAge设置过期时间,但有些老版本的浏览器不支持MaxAge。
  82. // 如果要支持所有浏览器,要么使用Expires,要么同时使用MaxAge和Expires。
  83. }
  84. http.SetCookie(w, &cookie)
  85. } else {
  86. sid, _ := url.QueryUnescape(cookie.Value) //反转义特殊符号
  87. session, _ = manager.provider.SessionRead(sid)
  88. }
  89. return session
  90. }
  91. //销毁session 同时删除cookie
  92. func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
  93. cookie, err := r.Cookie(manager.cookieName)
  94. if err != nil || cookie.Value == "" {
  95. return
  96. } else {
  97. manager.lock.Lock()
  98. defer manager.lock.Unlock()
  99. sid, _ := url.QueryUnescape(cookie.Value)
  100. manager.provider.SessionDestroy(sid)
  101. expiration := time.Now()
  102. cookie := http.Cookie{
  103. Name: manager.cookieName,
  104. Path: "/",
  105. HttpOnly: true,
  106. Expires: expiration,
  107. MaxAge: -1}
  108. http.SetCookie(w, &cookie)
  109. }
  110. }
  111. func (manager *Manager) GC() {
  112. manager.lock.Lock()
  113. defer manager.lock.Unlock()
  114. manager.provider.SessionGC(manager.maxLifeTime)
  115. time.AfterFunc(time.Duration(manager.maxLifeTime), func() { manager.GC() })
  116. }

memory.go

  1. package memory
  2. import (
  3. "container/list"
  4. "example/example/public/session"
  5. "sync"
  6. "time"
  7. )
  8. var pder = &FromMemory{list: list.New()}
  9. func init() {
  10. pder.sessions = make(map[string]*list.Element, 0)
  11. //注册 memory 调用的时候一定有一致
  12. session.Register("memory", pder)
  13. }
  14. //session实现
  15. type SessionStore struct {
  16. sid string //session id 唯一标示
  17. LastAccessedTime time.Time //最后访问时间
  18. value map[interface{}]interface{} //session 里面存储的值
  19. }
  20. //设置
  21. func (st *SessionStore) Set(key, value interface{}) error {
  22. st.value[key] = value
  23. pder.SessionUpdate(st.sid)
  24. return nil
  25. }
  26. //获取session
  27. func (st *SessionStore) Get(key interface{}) interface{} {
  28. pder.SessionUpdate(st.sid)
  29. if v, ok := st.value[key]; ok {
  30. return v
  31. } else {
  32. return nil
  33. }
  34. return nil
  35. }
  36. //删除
  37. func (st *SessionStore) Delete(key interface{}) error {
  38. delete(st.value, key)
  39. pder.SessionUpdate(st.sid)
  40. return nil
  41. }
  42. func (st *SessionStore) SessionID() string {
  43. return st.sid
  44. }
  45. //session来自内存 实现
  46. type FromMemory struct {
  47. lock sync.Mutex //用来锁
  48. sessions map[string]*list.Element //用来存储在内存
  49. list *list.List //用来做 gc
  50. }
  51. func (frommemory *FromMemory) SessionInit(sid string) (session.Session, error) {
  52. frommemory.lock.Lock()
  53. defer frommemory.lock.Unlock()
  54. v := make(map[interface{}]interface{}, 0)
  55. newsess := &SessionStore{sid: sid, LastAccessedTime: time.Now(), value: v}
  56. element := frommemory.list.PushBack(newsess)
  57. frommemory.sessions[sid] = element
  58. return newsess, nil
  59. }
  60. func (frommemory *FromMemory) SessionRead(sid string) (session.Session, error) {
  61. if element, ok := frommemory.sessions[sid]; ok {
  62. return element.Value.(*SessionStore), nil
  63. } else {
  64. sess, err := frommemory.SessionInit(sid)
  65. return sess, err
  66. }
  67. return nil, nil
  68. }
  69. func (frommemory *FromMemory) SessionDestroy(sid string) error {
  70. if element, ok := frommemory.sessions[sid]; ok {
  71. delete(frommemory.sessions, sid)
  72. frommemory.list.Remove(element)
  73. return nil
  74. }
  75. return nil
  76. }
  77. func (frommemory *FromMemory) SessionGC(maxLifeTime int64) {
  78. frommemory.lock.Lock()
  79. defer frommemory.lock.Unlock()
  80. for {
  81. element := frommemory.list.Back()
  82. if element == nil {
  83. break
  84. }
  85. if (element.Value.(*SessionStore).LastAccessedTime.Unix() + maxLifeTime) <
  86. time.Now().Unix() {
  87. frommemory.list.Remove(element)
  88. delete(frommemory.sessions, element.Value.(*SessionStore).sid)
  89. } else {
  90. break
  91. }
  92. }
  93. }
  94. func (frommemory *FromMemory) SessionUpdate(sid string) error {
  95. frommemory.lock.Lock()
  96. defer frommemory.lock.Unlock()
  97. if element, ok := frommemory.sessions[sid]; ok {
  98. element.Value.(*SessionStore).LastAccessedTime = time.Now()
  99. frommemory.list.MoveToFront(element)
  100. return nil
  101. }
  102. return nil
  103. }

memory.go里面是sesson.go 的Provider 和Session 接口的具体实现,通过这种方式可以灵活的扩展存取session数据的方式。

调用示例:

main.go

  1. package main
  2. import (
  3. _ "example/example/public/memory" //这里修改成你存放menory.go相应的目录
  4. "example/example/public/session" //这里修改成你存放session.go相应的目录
  5. "fmt"
  6. "log"
  7. "net/http"
  8. )
  9. var globalSessions *session.Manager
  10. func init() {
  11. var err error
  12. globalSessions, err = session.NewSessionManager("memory", "goSessionid", 3600)
  13. if err != nil {
  14. fmt.Println(err)
  15. return
  16. }
  17. go globalSessions.GC()
  18. fmt.Println("fd")
  19. }
  20. func sayHelloHandler(w http.ResponseWriter, r *http.Request) {
  21. cookie, err := r.Cookie("name")
  22. if err == nil {
  23. fmt.Println(cookie.Value)
  24. fmt.Println(cookie.Domain)
  25. fmt.Println(cookie.Expires)
  26. }
  27. //fmt.Fprintf(w, "Hello world!\n") //这个写入到w的是输出到客户端的
  28. }
  29. func login(w http.ResponseWriter, r *http.Request) {
  30. sess := globalSessions.SessionStart(w, r)
  31. val := sess.Get("username")
  32. if val != nil {
  33. fmt.Println(val)
  34. } else {
  35. sess.Set("username", "jerry")
  36. fmt.Println("set session")
  37. }
  38. }
  39. func loginOut(w http.ResponseWriter, r *http.Request) {
  40. //销毁
  41. globalSessions.SessionDestroy(w, r)
  42. fmt.Println("session destroy")
  43. }
  44. func main() {
  45. http.HandleFunc("/", sayHelloHandler) // 设置访问路由
  46. http.HandleFunc("/login", login)
  47. http.HandleFunc("/loginout", loginOut) //销毁
  48. log.Fatal(http.ListenAndServe(":8080", nil))
  49. }

memory 包的应用方式用下划线,只需执行 memory的init方法即可。

运行 main.go

访问 http://localhost:8080/login 设置session

服务端输出:

set session

jerry

访问http://localhost:8080/loginout 销毁session

服务端输出:

session destroy

links