zoukankan      html  css  js  c++  java
  • 在Go中使用Session

    整个客户端和服务端的交互过程可以概括为以下三个步骤:

    • 客户端第一次发送请求时,服务端创建 Session,并生成唯一标识符 SessionId
    • 服务端将 SessionId 发送给客户端
      (一般来说有两种常用的方式:Cookie 和 URL 重写)
    • 客户端再次向服务端发送请求时一并将 SessionId 发送给服务端。

    Go 实现 session

    在 Go 的标准库中并没有提供对 Sessoin 的实现,所以下面我们通过分析《Go Web编程》一书中的示例来学习一下如何自行实现一个 Session 的功能。
    (ps:虽然标准库中没有实现 session,但是有很多 Web 框架都提供了 session 的实现)

    实现 Session 主要需要考虑以下几点:

    • Session 的创建
    • 全局 Session 管理器
    • SessionID 的全局唯一性
    • Session 的存储(可以存储到内存、文件、数据库等)
    • Session 过期处理

    下面跟着相应的 go 代码示例分析一下整个设计思路:

    定义 Session:

    Session 使用的是一种类似散列表的结构(也可能就是散列表)来保存的信息。如果您有任何 Web 开发经验,您应该知道 Session 只有四个操作:设置值,获取值,删除值和获取当前的 SessionID。
    因此 Session 接口应该有四种方法来执行这种操作:

    type Session interface {
    	Set(key, value interface{}) error //设置Session
    	Get(key interface{}) interface{}  //获取Session
    	Delete(key interface{}) error     //删除Session
    	SessionID() string                //当前SessionID
    }
    

    可以通过多种方式保存 Session,包括内存,文件和数据库等,所以这里定义了一个 Session 操作接口,不同存储方式的 Session 操作有所不同,实现也不同。

    Session 管理器:

    我们知道 Session 是保存在服务端的数据,因此我们可以抽象出一个 Provider 接口来表示 Session 管理器的底层结构。Provider 将通过 SessionID 来访问和管理 Session。

    type Provider interface {
    	SessionInit(sid string) (Session, error)
    	SessionRead(sid string) (Session, error)
    	SessionDestroy(sid string) error
    	SessionGC(maxLifeTime int64)
    }
    • SessionInit 实现 Session 的初始化,如果成功则返回新的 Session
    • SessionRead 返回由相应 sid 表示的 Session,如果不存在,那么将以 sid 为参数调用 SessionInit 方法创建并返回一个新的 Session 变量。
    • SessionDestroy 给定一个 sid,删除相应的 Session。
    • SessionGC 根据 maxLifeTime 删除过期的 Session 变量

    定义好了 Provider 接口之后,我们再写一个注册方法,使得我们可以根据 provider 管理器的名称就能找到其对应的 provider 管理器

    var providers = make(map[string]Provider)
    
    //注册一个能通过名称来获取的 session provider 管理器
    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
    }
    

      接着再把 Provider 封装一下,定义一个全局的 Session 的管理器

    type Manager struct {
    	cookieName string //cookie的名称
    	lock sync.Mutex //锁,保证并发时数据的安全一致
    	provider Provider //管理session
    	maxLifeTime int64 //超时时间
    }
    
    func NewManager(providerName, cookieName string, maxLifetime int64) (*Manager, error){
    	provider, ok := providers[providerName]
    	if !ok {
    		return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", providerName)
    	}
    
    	//返回一个 Manager 对象
    	return &Manager{
    		cookieName: cookieName,
    		maxLifeTime: maxLifetime,
    		provider: provider,
    	}, nil
    }
    

      然后在 main 包中创建一个全局的 Session 管理器

    var globalSession *Manager
    
    func init() {
    	globalSession, _ = NewManager("memory", "sessionid", 3600)
    }
    

      唯一的 Session ID

    Session ID是用来识别访问 Web 应用的每一个用户的,因此需要保证它是全局唯一的,示例代码如下:

    func (manager *Manager) sessionId() string {
    	b := make([]byte, 32)
    	if _, err := io.ReadFull(rand.Reader, b); err != nil {
    		return ""
    	}
    	return base64.URLEncoding.EncodeToString(b)
    }
    

      创建 Session

    我们需要为每个来访的用户分配或者获取与它相关连的 Session,以便后面根据 Session 信息来验证操作。SessionStart 这个函数就是用来检测是否已经有某个 Session 与当期来访用户发生了关联,如果没有则创建它。

    //根据当前请求的cookie中判断是否存在有效的session, 不存在则创建
    func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
    	//为该方法加锁
    	manager.lock.Lock()
    	defer manager.lock.Unlock()
    	//获取 request 请求中的 cookie 值
    	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)}
    
    		http.SetCookie(w, &cookie) //将新的cookie设置到响应中
    	} else {
    		sid, _ := url.QueryUnescape(cookie.Value)
    		session, _ = manager.provider.SessionRead(sid)
    	}
    	return
    }
    

      现在我们已经可以通过 SessionStart 方法返回一个满足 Session 接口的变量了。下面通过一个例子来展示一下 Session 的读写操作:

    //根据用户名判断是否存在该用户的session,不存在则创建
    func login(w http.ResponseWriter, r *http.Request){
    	sess := globalSession.SessionStart(w, r)
    	r.ParseForm()
    	name := sess.Get("username")
    	if name != nil {
    		sess.Set("username", r.Form["username"]) //将表单提交的username值设置到session中
    	}
    }
    

      

    注销 Session

    在 Web 应用中通常有用户退出登录操作,那么当用户退出应用的时候,我们就可以对该用户的 session 数据进行注销。

    // SessionDestroy 注销 Session
    func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
    cookie, err := r.Cookie(manager.cookieName)
    	if err != nil || cookie.Value == "" {
    		return
    	}
    
    	manager.lock.Lock()
    	defer manager.lock.Unlock()
    
    	manager.provider.SessionDestroy(cookie.Value)
    	expiredTime := time.Now()
    	newCookie := http.Cookie{
    		Name: manager.cookieName,
    		Path: "/", HttpOnly: true,
    		Expires: expiredTime,
    		MaxAge: -1,  //会话级cookie
    	}
    	http.SetCookie(w, &newCookie)
    }
    

      现在我们有对 Session 进行 读(Get)、写(Set)、删除(Destroy)操作的方法了,下面结合这三个操作来展示一个示例:

    //记录该session被访问的次数
    func count(w http.ResponseWriter, r *http.Request) {
    	sess := globalSession.SessionStart(w, r) //获取session实例
    	createTime := sess.Get("createTime") //获得该session的创建时间
    	if createTime == nil {
    		sess.Set("createTime", time.Now().Unix())
    	} else if (createTime.(int64) + 360) < (time.Now().Unix()) { //已过期
    		//注销旧的session信息,并新建一个session  globalSession.SessionDestroy(w, r)
    		sess = globalSession.SessionStart(w, r)
    	}
    	count := sess.Get("countnum")
    	if count == nil {
    		sess.Set("countnum", 1)
    	} else {
    		sess.Set("countnum", count.(int) + 1)
    	}
    }
    

      删除 Session

    接着再来看看如何让 Session 管理器删除 Session。

    //在启动函数中开启GC
    func init() {
    	go globalSession.SessionGC()
    }
    
    func (manager *Manager) SessionGC() {
    	manager.lock.Lock()
    	defer manager.lock.Unlock()
    	manager.provider.SessionGC(manager.maxLifeTime)
    	//使用time包中的计时器功能,它会在session超时时自动调用GC方法
    	time.AfterFunc(time.Duration(manager.maxLifeTime), func() {
    		manager.SessionGC()
    	})
    }
    

      

  • 相关阅读:
    HDU 1261 字串数(排列组合)
    Codeforces 488C Fight the Monster
    HDU 1237 简单计算器
    POJ 2240 Arbitrage
    POJ 3660 Cow Contest
    POJ 1052 MPI Maelstrom
    POJ 3259 Wormholes
    POJ 3268 Silver Cow Party
    Codesforces 485D Maximum Value
    POJ 2253 Frogger(最短路)
  • 原文地址:https://www.cnblogs.com/FSH1014/p/12585452.html
Copyright © 2011-2022 走看看