go实践二十一 使用cookie和session

go使用cookie和session 

目录结构如下: 


项目根目录是 goweb

goweb的目录结构
├── session                                # session配置目录
│     ├── memorysession.go       # 内存session实现文件
│     ├── session.go                    # session底层
├── testsession.go                     # 业务控制
————————————————
 

下面,我们根据目录结构,从上往下建立文件夹和文件

建立文件夹和文件 goweb/session/memorysession.go ,memorysession.go 内容如下:

package session

import (
	"container/list"
	"sync"
	"time"
)

type ProviderStruct struct {
	lock sync.Mutex  //用来锁
	sessions map[string]*list.Element //用来存储在内存
	list *list.List //用来做gc
}

var ps = &ProviderStruct{list:list.New()}

type SessionStore struct {
	sid string //session id 唯一标识
	timeAccessed time.Time //最后访问时间
	value map[interface{}]interface{} //session 里面存储的值
}

func (st *SessionStore) Set(key,value interface{}) error{
	st.value[key] = value
	ps.SessionUpdate(st.sid)
	return nil
}

func (st *SessionStore) Get(key interface{}) interface{}{
	ps.SessionUpdate(st.sid)
	if v,ok := st.value[key]; ok{
		return v
	}else{
		return nil
	}
	return nil
}

func (st *SessionStore) Delete(key interface{}) error{
	delete(st.value, key)
	ps.SessionUpdate(st.sid)
	return nil
}

func (st *SessionStore) SessionID() string{
	return st.sid
}

func (ps *ProviderStruct) SessionInit(sid string) (Session,error){
	ps.lock.Lock()
	defer ps.lock.Unlock()
	v := make(map[interface{}]interface{},0)
	newsess := &SessionStore{sid:sid,timeAccessed:time.Now(),value:v}
	element := ps.list.PushBack(newsess)
	ps.sessions[sid] = element
	return newsess,nil
}

func (ps *ProviderStruct) SessionRead(sid string) (Session,error){
	if element,ok := ps.sessions[sid];ok{
		return element.Value.(*SessionStore),nil
	}else{
		sess,err := ps.SessionInit(sid)
		return sess,err
	}
	return nil,nil
}

func (ps *ProviderStruct) SessionDestroy(sid string) error{
	if element,ok := ps.sessions[sid];ok{
		delete(ps.sessions,sid)
		ps.list.Remove(element)
		return nil
	}
	return nil
}

func (ps *ProviderStruct) SessionGC(maxlifetime int64){
	ps.lock.Lock()
	defer ps.lock.Unlock()
	for{
		element := ps.list.Back()
		if element == nil{
			break
		}
		if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix(){
			ps.list.Remove(element)
			delete(ps.sessions,element.Value.(*SessionStore).sid)
		}else{
			break
		}
	}
}

func (ps *ProviderStruct) SessionUpdate(sid string) error{
	ps.lock.Lock()
	defer ps.lock.Unlock()
	if element,ok := ps.sessions[sid];ok{
		element.Value.(*SessionStore).timeAccessed = time.Now()
		ps.list.MoveToFront(element)
		return nil
	}
	return nil
}

func init(){
	ps.sessions = make(map[string]*list.Element,0)
	Register("memory",ps)
}

建立文件夹和文件 goweb/session/session.go ,session.go 内容如下:

package session

import (
	"sync"
	"time"
	"encoding/base64"
	"math/rand"
	"net/http"
	"net/url"
)
/*
session 管理设计
·全局session 管理器
·保证sessionid 的全局唯一性
·为每个客户关联一个session
·session 的存储(可以存储到内存、文件、数据库等)
·session 过期处理
*/

//session管理器
type Manager struct {
	cookieName string //cookie 名称
	lock sync.Mutex //cookie 锁
	provider Provider //cookie 提供者
	maxLifeTime int64 //cookie 最大生存时间
}

//抽象出Provider 接口,用来表示session 管理器底层存储结构
type Provider interface {
	//SessionInit 函数实现Session 的初始化,返回新的Session 变量
	SessionInit(sid string)(Session,error)
	//SessionRead 函数返回sid 代表的Session 变量,如果不存在,那么将以sid 为参数调用SessionInit 函数创建并返回一个新的Session 变量
	SessionRead(sid string)(Session,error)
	//SessionDestroy 函数用来销毁sid 对应的Session 变量
	SessionDestroy(sid string) error
	//SessionGC 根据maxLifeTime 来删除过期的数据
	SessionGC(maxLifeTime int64)
}

//Session 的处理基本就 设置值、读取值、删除值和获取sessionid
type Session interface {
	Set(key,value interface{}) error
	Get(key interface{}) interface{}
	Delete(key interface{}) error
	SessionID() string
}

var provides = make(map[string]Provider)

//创建manager
func NewManager(provideName,cookieName string,maxLifeTime int64) (*Manager,error){
	provider,ok := provides[provideName]
	if !ok {
		panic("session: unknown provide "+provideName+"(forgotten import?)")
	}
	return &Manager{provider:provider,cookieName:cookieName,maxLifeTime:maxLifeTime},nil
}

//提供名称,注册一个session 提供者
//如果注册两次都是重复的,或者驱动是nil , 返回错误
func Register(name string,provider Provider){
	if provider == nil{
		panic("session:Register provider is nil")
	}
	if _,dup := provides[name];dup{
		panic("session:Register called twice for provider "+name)
	}
	provides[name] = provider

}

//全局唯一的 SessionID
func (manager *Manager) sessionId() string{
	b := make([]byte,32)
	if _,err := rand.Read(b);err != nil{
		return ""
	}
	return base64.URLEncoding.EncodeToString(b)
}

func (manager *Manager) SessionStart(w http.ResponseWriter,r *http.Request)(session Session){
	manager.lock.Lock()
	defer manager.lock.Unlock()
	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,
			//设置httpOnly属性(说明:Cookie的HttpOnly属性,指示浏览器不要在除HTTP(和 HTTPS)请求之外暴露Cookie。
			// 一个有HttpOnly属性的Cookie,不能通过非HTTP方式来访问,例如通过调用JavaScript(例如,引用 document.cookie),
			// 因此,不可能通过跨域脚本(一种非常普通的攻击技术)来偷走这种Cookie。尤其是Facebook 和 Google 正在广泛地使用HttpOnly属性。)
			MaxAge: int(manager.maxLifeTime),
		}
		http.SetCookie(w,&cookie)
	}else{
		sid,_ := url.QueryUnescape(cookie.Value)
		session,_ = manager.provider.SessionRead(sid)
	}
	return
}

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

func (manager *Manager) GC(){
	manager.lock.Lock()
	defer manager.lock.Unlock()
	manager.provider.SessionGC(manager.maxLifeTime)
	time.AfterFunc(time.Duration(manager.maxLifeTime),func(){
		manager.GC()
	})
}

建立文件夹和文件 goweb/testsession.go ,testsession.go 内容如下:

package main

import (
	"crypto/md5"
	"io"

	"fmt"
	"log"
	"time"
	"net/http"
	"html/template"

	"goweb/session"
)

/*
session 管理设计
·全局session 管理器
·保证sessionid 的全局唯一性
·为每个客户关联一个session
·session 的存储(可以存储到内存、文件、数据库等)
·session 过期处理
*/

var globalSessions *session.Manager

//初始化函数,会在main之前执行
func init(){
	//初始化session ,使用memory 存储session ,
	globalSessions,_ = session.NewManager("memory","gosessionid",8)
	fmt.Printf("globalSessions: %+v \n",globalSessions)

	//异步使用Gc 销毁session
	go globalSessions.GC()
}

//默认路由
func sayhelloName(w http.ResponseWriter,r *http.Request)  {
	//下面这个写入到w 的是输出到客户端的
	fmt.Fprintf(w,"hello testsession.go")
}

//测试session
func testSession(w http.ResponseWriter, r *http.Request) {
	fmt.Printf("globalSessions: %+v \n",globalSessions)
	sess := globalSessions.SessionStart(w, r)
	r.ParseForm()
	if r.Method == "GET" {
		t, _ := template.New("foo").Parse(`{{define "T"}}hello,{{.}}!{{end}}
username: {{.UserName}}
token: {{.Token}}
`)
		err := t.ExecuteTemplate(w,"T",template.HTML("<script>alert('you have been testSession')</script>"))
		if err != nil{
			log.Println(err)
		}

		w.Header().Set("Content-Type", "text/html")

		//创建唯一的token ,并写入session 里
		token := uniqueToken()
		sess.Set("token",token)

		// 创建一个数据对象
		type Result struct {
			UserName interface{}
			Token interface{}
		}
		res := Result{
			UserName: sess.Get("username"),
			Token: token,
		}
		//数据对象输出到模板中
		t.Execute(w,res)

	} else {
		sess_token := sess.Get("token")
		//token := r.Form["token"] //通常token 是放到表单中提交过来的
		token := uniqueToken()
		if sess_token!=token{
			//提示登录
			fmt.Fprintf(w,"token无效")
			return
		}
		sess.Set("username", "testsession")
		http.Redirect(w, r, "/", 302)
	}
}

func countSession(w http.ResponseWriter,r *http.Request){
	sess := globalSessions.SessionStart(w, r)
	createtime := sess.Get("createtime")
	if createtime == nil{
		sess.Set("createtime",time.Now().Unix())
	}else if(createtime.(int64) + 3600) < (time.Now().Unix()){
		//间隔生成新的SID
		//每3600秒就刷新一次session ,用户需重新登录
		globalSessions.SessionDestroy(w,r)
		sess = globalSessions.SessionStart(w,r)
	}
	ct := sess.Get("countnum")
	if ct == nil{
		sess.Set("countnum",1)
	}else {
		sess.Set("countnum",(ct.(int) + 1))
	}
	t, _ := template.New("foo").Parse(`{{define "T"}}hello,{{.}}!{{end}}
refresh count:{{.}}
`)
	err := t.ExecuteTemplate(w,"T",template.HTML("<script>alert('you have been countSession')</script>"))
	if err != nil{
		log.Println(err)
	}

	w.Header().Set("Content-Type","text/html")
	t.Execute(w,sess.Get("countnum"))

}

//创建唯一token
func uniqueToken() string{
	h := md5.New()
	salt:="sessionss%^7&8888"
	io.WriteString(h,salt+time.Now().String())
	token:=fmt.Sprintf("%x",h.Sum(nil))
	return token
}

func main() {
	http.HandleFunc("/",sayhelloName) //主页
	http.HandleFunc("/test",testSession) //测试session
	http.HandleFunc("/count",countSession) //测试session 统计

	err := http.ListenAndServe(":6665",nil) //设置监听的端口
	if err != nil{
		log.Fatal("ListenAndServe:",err)
	}
}

启动服务器:

[root@izj6c4jirdug8kh3uo6rdez goweb]# go mod init goweb
[root@izj6c4jirdug8kh3uo6rdez goweb]# go run testsession.go
globalSessions: &{cookieName:gosessionid lock:{state:0 sema:0} provider:0xaf94a0 maxLifeTime:8} 

访问首页:http://daily886.com:6665/

访问session:http://daily886.com:6665/test

访问session统计:http://daily886.com:6665/count

 

参考:https://www.golang123.com/book/9?chapterID=171

参考:https://github.com/astaxie/session

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值