编写GO的WEB开发框架 (八): Session支持及自定义Session

session支持算是WEB框架的标配了,本篇先对session的原理进行简单说明,然后讲述框架是如何实现session支持,最后讲述自定义session的支持及实现方式。

session的基本原理

http协议是无状态的,所以,客户端都会使用cookie来标识请求来自同一个客户端。 而因为cookie是保存在客户端的,每次请求都要带上,所以不能放太多的数据,而且安全性容易受到威协。

相对地,session将用户会话数据保存在服务端,避免了敏感数据的传输,减少了传输的带宽要求,也提升了安全性。

会话数据保存在服务端,那服务器怎么与客户端一一对应呢?那就是sessionId,一般地,sessionId还是会通过cookie传输(也有人用自定义的header或者url方式), 服务端收到sessionId后,再去相应保存session的地方将会话数据读出来使用。在客户端还没有sessionId时,是由服务端生成一个唯一的id,并将用户会话数据以该id为主键保存,最后将sessionId响应给客户端。

session的操作方法及实现

先来看看操作session用到哪些方法:

  • 调用this.SessionStart() 开启session,也可以在配置中增加session.auto_start=on 来让框架自动开启

  • 使用this.Session["key"]的方式读取session值 (框架在SessionStart时会将所有的session值读入this.Session)

  • 使用this.SessionSet("key",value) 设置一个session

  • 使用this.SessionUnSet("key") 注销session的一个key

  • 使用this.SessionDestroy() 注销session全部内容

  • 在请求结束时,框架会自动调用this.sessionSave() 将更新数据保存回会话数据中,以便下次使用

接下来就是这些方法的实现:

func (this *App) SessionStart() {
	if this.sessionOn { //已启动过,返回
		return
	}
	sid := getSessionId() //从cookie中读取sessionID,如何没有, 则生成一个唯一的id,比如md5当前时间的纳秒数
	this.sessHandler.Open(sid, this.Conf) //调用handler的open方法初始化 ,this.Conf是配置访问对象,用来读取必要的配置
	this.Session = this.sessHandler.Read() //调用Read方法,将session读取全局的this.Session,以方便使用
	//将sid放到cookie返回
	cookie := &http.Cookie{Name: "sessionId", Value: sid, HttpOnly: true}
	// 如有需要,设置cookie的生命周期
	this.SetCookie(cookie)
	this.sessionOn = true
	//gc
	go func() {
		//按一定的机率调用handler的Gc方法,请理过期数据
	}()
}

//设置一个session
func (this *App) SessionSet(key string, val interface{}) {
	if !this.sessionOn {
		return
	}
	this.sessHandler.Set(key, val)
	this.Session[key] = val
}

//销毁一个或多个session,如果不传参数,则销毁全部
func (this *App) SessionUnset(keys ...interface{}) {
	if !this.sessionOn {
		return
	}
	if keys == nil { //key为空,删除全部
		this.sessHandler.Destroy()
		for k, _ := range this.Session {
			delete(this.Session, k)
		}
	} else {
		for _, k := range keys {
			if key, ok := k.(string); ok {
				this.sessHandler.Set(key, nil)
				delete(this.Session, key)
			}
		}
	}
}

//保存SESSION(请求结束时自动调用)
func (this *App) sessionSave() {
	if !this.sessionOn {
		return
	}
	this.sessHandler.Save()
}

SessionHandler接口

由上面的session操作可以看出,session操作只是实现了sid的获取或生成,然后就是在需要的时候调用SessionHandler的相应方法来完成实际的处理。

为了支持自定义的SessionHandler,把SessionHandler定义成一个接口,所有实现了该接口的对象都可以作为session处理器

框架中,使用 SetSessionHandler(sh *SessionHandler){} 方法来指定所使用的handler (不使用该方法设置handler时,使用配置文件中指定的内置handler)

  • SessionHandler接口定义
type SessionHandler interface {
	Open(sessId string, config *Conf) //开启session时调用
	Set(key string, val interface{})  //写入session,给Session赋值时调用,val设为nil时,表示删除
	Read() map[string]interface{}     //读取session,返回反序列后的格式数据,用来设置到Session的map
	Destroy()                         //销毁一个session(SessionDestory时调用)
	Save()                            //将session的值序列化后持久化保存(请求完成时)
	Gc(maxLife int64)                 //过期数据清理,系统按特定机率触发
}

SessionHandler的实现

框架内置了两种常用的SessionHandler,分别是基于文件和基于memcache的,下面简单介绍一下基于文件的实现思路,基于memcache或者其它方式类似,有兴趣的可以自行试验。

type fileSession struct {
	file   string
	path   string
	change bool
	data   map[string]interface{}
}

func (this *fileSession) Open(sessId string, config *Conf) {
	this.path, _ = os.TempDir()+"/sess"
	this.file = fmt.Sprintf("%s/%s/%s/%s", this.path, sessId[:2], sessId[2:4], sessId[4:]) //session文件, hash两层路径
}
func (this *fileSession) Set(key string, val interface{}) {
	this.change = true
	if val == nil {
		delete(this.data, key)
	} else {
		this.data[key] = val
	}
}
func (this *fileSession) Read() map[string]interface{} {
	this.data = make(map[string]interface{})
	_, err := os.Stat(this.file)
	if err == nil { //存在,读取
		os.Chtimes(this.file, time.Now(), time.Now()) //设置一下最后更新时间
		fi, _ := os.Open(this.file)
		defer fi.Close()
		content, _ := ioutil.ReadAll(fi)
		if err := json.Unmarshal(content, &this.data); err != nil { 
			//check err
		}
	}
	return this.data
}
func (this *fileSession) Destroy() {
	os.Remove(this.file)
	for k, _ := range this.data {
		delete(this.data, k)
	}
}
func (this *fileSession) Save() {
	if this.change {
		data, _ := json.Marshal(this.data) //将数据序列化后保存
		//若目录不存在先创建
		path := filepath.Dir(this.file)
		_, err := os.Stat(path)
		if err != nil && os.IsNotExist(err) {
			os.MkdirAll(path, os.ModePerm)
		}
		//写入文件
		fd, _ := os.OpenFile(this.file, os.O_TRUNC|os.O_CREATE, os.ModePerm)
		fd.Write(data)
	}
}
func (this *fileSession) Gc(maxLife int64) {
	//遍历this.path,获取每一文件的更新时间,如果距离现在超过maxLife,则删除
}

转载于:https://my.oschina.net/tim8670/blog/633308

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值