在 Go 语言中使用 Session(一)
在上一篇博客 理解Cookie和Session 中,我们了解了 Cookie 和 Session 的一些基础知识,也知道了 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()
})
}
以上类似的解决方法可用于计算在线用户上。
至此,我们实现了一个用来在 Web 应用中全局管理 Session 的 SessionManager,定义了用来提供 Session 存储实现 Provider 接口。
接口的具体实现
关于针对 Session 接口和 Provider 接口的具体实现,这里就不展开了,有兴趣的读者可参考《Go Web编程》第 6.3 小节的内容
参考:
《Go Web 编程》第6章
在 Go 语言中使用 Session(一)的更多相关文章
- 如何在报表权限中使用session
1. 问题描述 权限中使用session,一般是用来存放用户名和密码,下面以报表开发工具FineReport为例,分两种情况介绍用户名和密码的保存: 2. 同一应用下session 由于session ...
- 巨人大哥谈Web应用中的Session(session详解)
巨人大哥谈Web应用中的Session(session详解) 虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术. ...
- nginx+php负载均衡集群环境中的session共享方案梳理
在网站使用nginx+php做负载均衡情况下,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,就会出现很多问题,比如说最常见的登录状态. 下面罗列几种nginx负载均衡 ...
- 针对负载均衡集群中的session解决方案的总结
在日常运维工作中,当给Web站点使用负载均衡之后,必须面临的一个重要问题就是Session的处理办法,无论是PHP.Python.Ruby还是Java语言环境,只要使用服务器保存Session,在做负 ...
- {Django基础八之cookie和session}一 会话跟踪 二 cookie 三 django中操作cookie 四 session 五 django中操作session
Django基础八之cookie和session 本节目录 一 会话跟踪 二 cookie 三 django中操作cookie 四 session 五 django中操作session 六 xxx 七 ...
- Django中的session和cookie及分页设置
cookie Cookie的由来 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不 ...
- JAVA语言中的修饰符
JAVA语言中的修饰符 -----------------------------------------------01--------------------------------------- ...
- Java语言中的面向对象特性总结
Java语言中的面向对象特性 (总结得不错) [课前思考] 1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类? 2. 面向对象编程的特性有哪三个?它们各自又有哪些特性? 3. 你知 ...
- python语言中的编码问题
在编程的过程当中,常常会遇到莫名其妙的乱码问题.很多人选择出了问题直接在网上找答案,把别人的例子照搬过来,这是快速解决问题的一个好办法.然而,作为一个严谨求实的开发者,如果不从源头上彻底理解乱码产生的 ...
随机推荐
- eventFlow 系列 <一> 入门
var exampleId = ExampleId.New; var commandBus = resolver.Resolve<ICommandBus>(); ,) var execut ...
- faceswap深度学习AI实现视频换脸详解
给大家介绍最近超级火的黑科技应用deepfake,这是一个实现图片和视频换脸的app.前段时间神奇女侠加尔盖朵的脸被换到了爱情动作片上,233333.我们这里将会从github项目faceswap开始 ...
- SP338ROADS题解--最短路变式
题目链接 https://www.luogu.org/problemnew/show/SP338 分析 联想到不久前做过的一道题\(Full\) \(Tank\),感觉可以用优先队列做,于是写了\(d ...
- ant-design的Table组件自定义空状态
Table,是antd中一个我们经常使用的组件,在官方文档中给出了非常详细的实例以及API, 但在我们在使用过程中可能需要根据项目的要求来定制空状态时的展示. 什么是空状态呢? 在antd的官方文档中 ...
- CSS 定位 四种定位
absolute 生成绝对定位的元素,相对于static定位以外的第一个父元素进行定位.元素的位置通过“left”,“top”,“right”以及“bottom”属性进行定位. fixed 生成固定 ...
- python3使用pytesseract进行验证码识别
pytesseract介绍 1.Python-tesseract是一个基于google's Tesseract-OCR的独立封装包: 2.Python-tesseract功能是识别图片文件中文字,并作 ...
- Delphi 10.3.3最新消息
有朋友说,已经开始内测,预计10月末发版,按最新的路线图,此版本支持iOS 13及Android 64位. 2019-11-18,今天,下载及注册机都来了,快下载安装,试用吧. 需要的话加入QQ群20 ...
- RabbitMQ消息分发轮询
一,前言 如果我们一个生产者,对应多个消费者,rabbitmq 会发生什么呢 二,消息分发轮询 前提条件:1个生产者 ----> 多个消费者,且no_ack=True (启动三次生产者) ① ...
- Mysql(八):ORM框架SQLAlchemy
一 介绍 SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取 ...
- 多线程模块的同步机制event对象
多线程模块的同步机制event对象 线程的核心特征就是他们能够以非确定的方式(即何时开始执行,何时被打断,何时恢复完全由操作系统来调度管理,这是用户和程序员无法确定的)独立执行的,如果程序中有其他线程 ...