原生Go语言没有实现session管理机制,所以如果使用原生Go语言进行web编程,我们需要自己进行session管理机制的设计与实现,本文将就此进行详细介绍,并实现一个简单的session管理机制。
session信息可以使用内存、文件或数据库等方式进行存储,考虑到对不同存储方式的兼容,我们设计的session管理机制应该能很方便的在不同存储方式之间进行切换。所以,session管理机制可以分为两部分内容:session管理器和session存储器,session管理器主要负责多种存储方式的共同操作部分,例如,cookie的读取与设置、session ID的生成,以及一些共同需要的参数设置等等。session管理器结构可设置如下:

  1. //session管理器
  2. type SessionManager struct {
  3. cookieName string //cookie名称
  4. cookieExpire int //cookie有效期时间(单位:分钟,0表示会话cookie)
  5. sessionExpire int64 //session有效期时间(单位:分钟)
  6. gcDuration int //垃圾回收机制运行间隔时间(单位:分钟)
  7. provider SessionProvider //session存储器
  8. }

其创建方法为:

  1. //创建session管理器
  2. func NewManager(cookieName string, cookieExpire int, sessionExpire int64, gcDuration int, provider SessionProvider) *SessionManager {
  3. return &SessionManager{
  4. cookieName: cookieName,
  5. cookieExpire: cookieExpire,
  6. sessionExpire: sessionExpire,
  7. gcDuration: gcDuration,
  8. provider: provider,
  9. }
  10. }

而session存储器则对应具体的存储方式,只需负责根据session ID对session数据进行读写操作,这部分根据存储方式不同而不同,但方法签名是一致的,可以定义其接口类型为:

  1. //session存储器
  2. type SessionProvider interface {
  3. create(sessionId string, data map[string]interface{}) error //创建session
  4. get(sessionId, key string) (string, error) //读取session键值
  5. getAll(sessionId string) (map[string]string, error) //读取session所有键值对
  6. set(sessionId, key string, value interface{}) error //设置session键值
  7. destroy(sessionId string) error //销毁session
  8. gc(expire int64) error //垃圾回收:删除过期session
  9. }

接下来定义session管理器生成session ID的方法,可根据请求头信息生成,这里只是举一个例子:

  1. //生成session ID
  2. func (sm *SessionManager) createSessionId(req *http.Request) string {
  3. addr := req.RemoteAddr
  4. userAgent := req.Header.Get("User-Agent")
  5. rand.Seed(time.Now().UnixNano())
  6. n := rand.Intn(10000)
  7. str := addr + "_" + userAgent + "_" + strconv.Itoa(n)
  8. h := md5.New()
  9. h.Write([]byte(str))
  10. cipherStr := h.Sum(nil)
  11. return hex.EncodeToString(cipherStr)
  12. }

session管理器的各个方法都需要读cookie,获取session ID:

  1. //读cookie,获取session ID
  2. func (sm *SessionManager) getSessionId(req *http.Request) (string, error) {
  3. c, err := req.Cookie(sm.cookieName)
  4. if err != nil {
  5. return "", errors.New("Reading cookie failed: " + err.Error())
  6. }
  7. if len(c.Value) == 0 { //尚未设置cookie
  8. return "", errors.New("Cookie does not exists: " + sm.cookieName)
  9. }
  10. return c.Value, nil
  11. }

最后就是session管理器创建session、读取session、写入session、销毁session、session GC等方法的定义,这些方法比较简单,只是调用session存储器对应的方法即可:

  1. //创建session
  2. func (sm *SessionManager) Create(writer *http.ResponseWriter, req *http.Request, data map[string]interface{}) error {
  3. sessionId, _ := sm.getSessionId(req)
  4. if len(sessionId) > 0 {
  5. data, _ := sm.provider.getAll(sessionId)
  6. if data != nil { //已有session,无需创建
  7. return nil
  8. }
  9. }
  10. sessionId = sm.createSessionId(req)
  11. if len(sessionId) == 0 {
  12. return errors.New("Length of sessionId is 0")
  13. }
  14. err := sm.provider.create(sessionId, data)
  15. if err != nil {
  16. return err
  17. }
  18. if sm.cookieExpire == 0 { //会话cookie
  19. http.SetCookie(*writer, &http.Cookie{
  20. Name: sm.cookieName,
  21. Value: sessionId,
  22. Path: "/", //一定要设置为根目录,才能在所有页面生效
  23. HttpOnly: true,
  24. })
  25. } else { //持久cookie
  26. expire, _ := time.ParseDuration(strconv.Itoa(sm.cookieExpire) + "m")
  27. http.SetCookie(*writer, &http.Cookie{
  28. Name: sm.cookieName,
  29. Value: sessionId,
  30. Path: "/", //一定要设置为根目录,才能在所有页面生效
  31. Expires: time.Now().Add(expire),
  32. HttpOnly: true,
  33. })
  34. }
  35. return nil
  36. }
  37.  
  38. //获取session键值
  39. func (sm *SessionManager) Get(writer *http.ResponseWriter, req *http.Request, key string) (string, error) {
  40. sessionId, _ := sm.getSessionId(req)
  41. if len(sessionId) == 0 {
  42. return "", errors.New("Length of sessionId is 0")
  43. }
  44. return sm.provider.get(sessionId, key)
  45. }
  46.  
  47. //读取session所有键值对
  48. func (sm *SessionManager) GetAll(writer *http.ResponseWriter, req *http.Request) (map[string]string, error) {
  49. sessionId, _ := sm.getSessionId(req)
  50. if len(sessionId) == 0 {
  51. return nil, errors.New("Length of sessionId is 0")
  52. }
  53. return sm.provider.getAll(sessionId)
  54. }
  55.  
  56. //设置session键值
  57. func (sm *SessionManager) Set(writer *http.ResponseWriter, req *http.Request, key string, value interface{}) error {
  58. sessionId, _ := sm.getSessionId(req)
  59. if len(sessionId) == 0 {
  60. return errors.New("Length of sessionId is 0")
  61. }
  62. return sm.provider.set(sessionId, key, value)
  63. }
  64.  
  65. //销毁session
  66. func (sm *SessionManager) Destroy(req *http.Request) error {
  67. sessionId, _ := sm.getSessionId(req)
  68. if len(sessionId) == 0 {
  69. return errors.New("Length of sessionId is 0")
  70. }
  71. return sm.provider.destroy(sessionId)
  72. }
  73.  
  74. //垃圾回收:删除过期session
  75. func (sm *SessionManager) Gc() error {
  76. err := sm.provider.gc(sm.sessionExpire)
  77. duration, _ := time.ParseDuration(strconv.Itoa(sm.gcDuration) + "m")
  78. time.AfterFunc(duration, func() { sm.Gc() }) //设置下次运行时间
  79. return err
  80. }

至此,我们已经实现session管理器!

接下来,不管使用什么方式存储session信息,只要实现SessionProvider接口,关心session数据的读写操作即可。
这里,我们实现一个文件session存储器,除了session文件保存路径,为了并发安全,每个session文件还需要对应一个读写锁,所以其结构可设计为:

  1. //文件session存储器
  2. type FileProvider struct {
  3. savePath string //session文件保存路径
  4. muxMap map[string]*sync.RWMutex //session文件锁
  5. }

对应的创建方法:

  1. //创建文件session存储器对象
  2. func NewFileProvider(savePath string) *FileProvider {
  3. return &FileProvider{
  4. savePath: savePath,
  5. muxMap: make(map[string]*sync.RWMutex),
  6. }
  7. }

所有方法都需要根据session ID得到文件路径,可定义共用方法:

  1. //返回session文件名称
  2. func (fp FileProvider) filename(sessionId string) string {
  3. return fp.savePath + "/" + sessionId
  4. }

写入session文件时,数据只能是字符串,而存入session的却不一定是字符串,所以需要一个将其他数据类型转换为字符串的共用方法:

  1. //将数据类型转换为字符串
  2. func (fp FileProvider) toString(value interface{}) (string, error) {
  3. var str string
  4. vType := reflect.TypeOf(value)
  5. switch vType.Name() {
  6. case "int":
  7. i, _ := value.(int)
  8. str = strconv.Itoa(i)
  9. case "string":
  10. str, _ = value.(string)
  11. case "int64":
  12. i, _ := value.(int64)
  13. str = strconv.FormatInt(i, 10)
  14. default:
  15. return "", errors.New("Unsupported type: " + vType.Name())
  16. }
  17. return str, nil
  18. }

文件session存储器的create、get、getAll、set等四个方法,本质上都是对session文件进行读写操作,可以将读和写抽取出来成为两个共用方法:

  1. //创建/重写session文件
  2. func (fp FileProvider) write(sessionId string, data map[string]string, newFile bool) error {
  3. _, exist := fp.muxMap[sessionId]
  4. if !exist { //内存中没有锁,先建锁
  5. fp.muxMap[sessionId] = new(sync.RWMutex)
  6. }
  7. fp.muxMap[sessionId].Lock()
  8. defer func() {
  9. fp.muxMap[sessionId].Unlock()
  10. }()
  11. fname := fp.filename(sessionId)
  12. _, err := os.Stat(fname)
  13. var f *os.File
  14. if newFile {
  15. if err == nil { //若session文件存在,则先删除
  16. os.Remove(fname)
  17. }
  18. f, err = os.Create(fname)
  19. if err != nil {
  20. return errors.New("Creating session file failed: " + err.Error())
  21. }
  22. } else {
  23. if err != nil { //session文件不存在
  24. return errors.New("Session file does not exists: " + fname)
  25. }
  26. f, err = os.OpenFile(fname, os.O_RDWR|os.O_TRUNC, 0644)
  27. if err != nil {
  28. return errors.New("Opening session file failed: " + err.Error())
  29. }
  30. }
  31. defer func() {
  32. os.Chtimes(fname, time.Now(), time.Now()) //更新文件最后访问时间
  33. f.Close()
  34. }()
  35. for key, value := range data {
  36. _, err = fmt.Fprintln(f, key+":"+value)
  37. if err != nil {
  38. return errors.New("Setting session key value failed: " + err.Error())
  39. }
  40. }
  41. return nil
  42. }
  43.  
  44. //读取session文件
  45. func (fp FileProvider) read(sessionId string) (map[string]string, error) {
  46. fname := fp.filename(sessionId)
  47. _, err := os.Stat(fname)
  48. if err != nil { //session文件不存在
  49. return nil, errors.New("Session file does not exists: " + fname)
  50. }
  51. _, exist := fp.muxMap[sessionId]
  52. if !exist { //内存中没有锁,先建锁
  53. fp.muxMap[sessionId] = new(sync.RWMutex)
  54. }
  55. fp.muxMap[sessionId].Lock()
  56. defer func() {
  57. fp.muxMap[sessionId].Unlock()
  58. }()
  59. f, err := os.Open(fname)
  60. if err != nil {
  61. return nil, errors.New("Opening session file failed: " + err.Error())
  62. }
  63. defer func() {
  64. os.Chtimes(fname, time.Now(), time.Now()) //更新文件最后访问时间
  65. f.Close()
  66. }()
  67. data := make(map[string]string)
  68. scaner := bufio.NewScanner(f)
  69. for scaner.Scan() {
  70. kv := strings.Split(scaner.Text(), ":")
  71. if len(kv) != 2 {
  72. continue
  73. }
  74. data[kv[0]] = kv[1]
  75. }
  76. if len(data) == 0 {
  77. return nil, errors.New("No data in session file")
  78. }
  79. return data, nil
  80. }

最后,实现SessionProvider接口的6个方法:

  1. //创建session
  2. func (fp FileProvider) create(sessionId string, data map[string]interface{}) error {
  3. strData := make(map[string]string)
  4. for key, value := range data {
  5. strValue, err := fp.toString(value)
  6. if err != nil {
  7. return err
  8. }
  9. strData[key] = strValue
  10. }
  11. return fp.write(sessionId, strData, true)
  12. }
  13.  
  14. //读取session键值
  15. func (fp FileProvider) get(sessionId, key string) (string, error) {
  16. data, err := fp.read(sessionId)
  17. if err != nil {
  18. return "", err
  19. }
  20. value, ok := data[key]
  21. if !ok {
  22. return "", errors.New("Session key does not exists: " + key)
  23. }
  24. return value, nil
  25. }
  26.  
  27. //读取session所有键值对
  28. func (fp FileProvider) getAll(sessionId string) (map[string]string, error) {
  29. return fp.read(sessionId)
  30. }
  31.  
  32. //设置session键值
  33. func (fp FileProvider) set(sessionId, key string, value interface{}) error {
  34. data, err := fp.read(sessionId)
  35. if data == nil {
  36. return err
  37. }
  38. str, err := fp.toString(value)
  39. if err != nil {
  40. return err
  41. }
  42. data[key] = str
  43. return fp.write(sessionId, data, false)
  44. }
  45.  
  46. //销毁session:删除session文件
  47. func (fp FileProvider) destroy(sessionId string) error {
  48. fname := fp.filename(sessionId)
  49. _, err := os.Stat(fname)
  50. if err != nil { //session文件不存在
  51. return errors.New("Session file does not exists: " + fname)
  52. }
  53. _, exist := fp.muxMap[sessionId]
  54. if !exist { //内存中没有锁,先建锁
  55. fp.muxMap[sessionId] = new(sync.RWMutex)
  56. }
  57. fp.muxMap[sessionId].Lock()
  58. err = os.Remove(fname)
  59. fp.muxMap[sessionId].Unlock()
  60. if err != nil {
  61. return errors.New("Removing session file failed: " + err.Error())
  62. }
  63. delete(fp.muxMap, sessionId)
  64. return nil
  65. }
  66.  
  67. //垃圾回收:删除过期session文件
  68. func (fp FileProvider) gc(expire int64) error {
  69. now := time.Now().Unix()
  70. for sessionId, mux := range fp.muxMap {
  71. fname := fp.filename(sessionId)
  72. if len(fname) == 0 {
  73. continue
  74. }
  75. mux.Lock()
  76. info, err := os.Stat(fname)
  77. if err != nil {
  78. mux.Unlock()
  79. continue
  80. }
  81. modTime := info.ModTime().Unix() //文件最后访问时间
  82. if modTime+expire*60 < now { //已超出过期时间
  83. err = os.Remove(fname)
  84. mux.Unlock()
  85. if err != nil {
  86. delete(fp.muxMap, sessionId)
  87. }
  88. } else {
  89. mux.Unlock()
  90. }
  91. }
  92. return nil
  93. }

这样就完成了文件session存储器的实现。

当然我们也可以使用内存或数据库等其他方式进行session数据的存储,只需实现SessionProvider接口,并将其实例化对象赋值给session管理器创建方法的provider参数,即可实现不同存储方式的快速切换。

go web编程——session管理机制设计与实现的更多相关文章

  1. Tomcat的Session管理机制

    >>Session和Cookie请求的过程 Http连接本身是无状态的,即前一次发起的连接跟后一次没有任何关系,是属于两次独立的连接请求,但是互联网访问基本上都是需要有状态的,即服务器需要 ...

  2. C++服务器设计(四):超时管理机制设计

    前四章介绍了系统层的设计,从这一章开始进入服务层的设计. 连接断开 在常见的服务器场景中,客户端断开连接的方式为被动关闭.即作为客户端请求完服务器的服务后,选择主动关闭同服务器的连接.在服务器的角度看 ...

  3. 微软与开源干货对比篇_PHP和 ASP.NET在 Session实现和管理机制上差异

    微软与开源干货对比篇_PHP和 ASP.NET在 Session实现和管理机制上差异 前言:由于开发人员要靠工具吃饭,可能和开发工具.语言.环境呆的时间比和老婆孩子亲人在一起的时间还多,所以每个人或多 ...

  4. 细说shiro之六:session管理

    官网:https://shiro.apache.org/ 我们先来看一下shiro中关于Session和Session Manager的类图. 如上图所示,shiro自己定义了一个新的Session接 ...

  5. Spring aop与HibernateTemplate——session管理(每事务一次 Session)

    一.HibernateTemplate与Spring aop简介 参见http://bbs.csdn.net/topics/340207475中网友blueram的发言.(感谢blueram) 二.在 ...

  6. Tomcat中session的管理机制

    1.       请求过程中的session操作: 简述:在请求过程中首先要解析请求中的sessionId信息,然后将sessionId存储到request的参数列表中.然后再从 request获取s ...

  7. web状态管理机制

    引入:b/s(浏览器/服务器模式)区别于winform的是winform中只加载一次页面构造函数,而b/s中只要点击按钮或者其他涉及后台的操作都会调用后台代码.一般情况下为了防止服务器过载,b/s不会 ...

  8. atitit. access token是什么??微信平台公众号开发access_token and Web session保持状态机制

    atitit. access token是什么??微信平台公众号开发access_token and Web session保持状态机制 1. token机制and  session保持状态机制 1 ...

  9. 008-shiro与spring web项目整合【二】认证、授权、session管理

    一.认证 1.添加凭证匹配器 添加凭证匹配器实现md5加密校验. 修改applicationContext-shiro.xml: <!-- realm --> <bean id=&q ...

随机推荐

  1. JAVA四种引用方式

    JAVA四种引用方式: java.lang.ref: 强引用(直接变量赋值) 软引用(SoftReference): 只有在要发生OOM错误之前才会回收掉老的软引用对象,应用场景主要防止内存溢出.(缓 ...

  2. 编译原理--NFA/DFA

    现成的, 讲义: https://www.cnblogs.com/AndyEvans/p/10240790.html https://www.cnblogs.com/AndyEvans/p/10241 ...

  3. matplotlib.pyplot 包

    import matplotlib.pyplot as plt 图片的打开和保存: from PIL import Image img=Image.open('....') img.save('... ...

  4. shell第一个脚本

    mkdir 创建目录touch 创建空文件 chmod +x ./test.sh  #使脚本具有执行权限

  5. java匿名内部类 (转载)

    匿名内部类也就是没有名字的内部类 正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写 但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口 实例1:不使用匿名内部类来实现抽象 ...

  6. Jira插件安装

    以下操作需要反编译 1.反编译的jar包 1)E:\JIRA安装路径\atlassian-jira\WEB-INF\atlassian-bundled-plugins\atlassian-univer ...

  7. 包装CGFloat和用NSNumber初始化的区别?

    @(CGFloat)和[NSNumber numberWith:CGFloat]的区别?

  8. Android 中的 Matrix

    Matrix 是 Android SDK 提供的一个矩阵类,它代表一个 3 X 3 的矩阵 Matrix主要可以对图像做4种基本变换 Translate 平移变换 Rotate 旋转变换 Scale ...

  9. XML大作业

    XML大作业 共两题,均于实验上机完成 第一题:在xml文档中使用DTD 第二题:掌握使用xsl显示xml文件的基本方法 第一题: 一.实验目的 (1)通过本实验,使学生能够了解并掌握XML DTD的 ...

  10. vue实现动态显示与隐藏底部导航的方法分析

    本文实例讲述了vue实现动态显示与隐藏底部导航的方法.分享给大家供大家参考,具体如下: 在日常项目中,总有几个页面是要用到底部导航的,总有那么些个页面,是不需要底部导航的,这里列举一下页面底部导航的显 ...