golang使用session

1、 为什么要写session

学过php的同学都知道,session直接用就是了,不用进行任何的封装,代码如下

session_start();
$_SESSION['aa'] = 1;
//一个超级简单的session定义就出来了
然后各个地方判断
if ($_SESSION['aa']) {
  //已经登录了
}

实际上php中我们能直接用session,是php-fpm提供给我们的,如我们使用swoole的时候就需要单独封装session后才能使用了,因为swoole并不依赖于php-fpm

2、session的原理

在开始之前,我们知道session是存在服务器端的,session用于服务器端和客户端交互的一个令牌,但是我们知道http是无状态的,无法确认哪个session是哪个客户端的,于是我们会使用 Cookie 来管理 Session,浏览器进入页面生成一个cookieid,用cookieid去对应session服务器端的数据。以弥补 HTTP 协议中不存在的状态管理功能。

在这里插入图片描述

3、session封装

session.go

package sessions

import (
	"sync"
	"time"
)

type Session interface {
	Set(key, value interface{})
	Get(key interface{}) interface{}
	Remove(key interface{}) error
	GetId() string
}

type SessionFromMemory struct {
	sid string                         //唯一标识
	lock sync.Mutex
	lastAccessedTime time.Time         //最后一次访问时间
	maxAge int64                       //超时时间
	data map[interface{}]interface{}   //主数据
}

const DEFEALT_TIME  = 1800

//实例化
func newSessionFromMemory() *SessionFromMemory {
	return &SessionFromMemory{
		data:             make(map[interface{}]interface{}),
		maxAge:           DEFEALT_TIME,
	}
}


func (si *SessionFromMemory) Set(key, value interface{})  {
	si.lock.Lock();
	defer si.lock.Unlock()
	si.data[key] = value
}

func (si *SessionFromMemory) Get(key interface{}) interface{}  {
	if value := si.data[key]; value != nil {
		return value
	}
	return nil
}

func (si *SessionFromMemory) Remove(key interface{}) error  {
	if value := si.data[key]; value != nil {
		delete(si.data, key)
	}
	return nil
}

func (si *SessionFromMemory) GetId() string  {
	return si.sid
}

provide.go

package sessions

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sync"
	"time"
)

type Provider interface {
	//初始化一个session,id根据需要生成后传入
	InitSession(sid string, maxAge int64) (Session, error)
	//根据sid,获得当前session
	SetSession(session Session) error
	//销毁session
	DestroySession(sid string) error
	//回收
	GCSession()
}


type FromMemory struct {
	lock      sync.Mutex
	sessions  map[string]Session
}

func newFromMemory() *FromMemory  {
	return &FromMemory{
		sessions: make(map[string]Session, 0),
	}
}

func (fm * FromMemory) InitSession(sid string, maxAge int64)  (Session, error) {
	fm.lock.Lock()
	defer fm.lock.Unlock()
	
	newSession := newSessionFromMemory()
	newSession.sid = sid
	if maxAge != 0 {
		newSession.maxAge = maxAge
	}
	newSession.lastAccessedTime = time.Now()
	
	fm.sessions[sid] = newSession
	fmt.Println(fm.sessions)
	return newSession, nil
}

func (fm *FromMemory) SetSession(session Session) error  {
	fm.sessions[session.GetId()] = session
	return nil
}

func (fm *FromMemory) DestroySession(sid string) error  {
	if _, ok := fm.sessions[sid]; ok {
		delete(fm.sessions, sid)
		return nil
	}
	return nil
}

func (fm *FromMemory) GCSession()  {
	sessions := fm.sessions

	if len(sessions) < 1 {
		return
	}
	fmt.Println("xxxxxxxxxxxxxx--gc-session", sessions)
	for k,v := range sessions {
		t := (v.(*SessionFromMemory).lastAccessedTime.Unix()) + (v.(*SessionFromMemory).maxAge)
		if t < time.Now().Unix() {
			fmt.Println("timeout------->", v)
		}
		delete(fm.sessions, k)
	}
}

type SessionManager struct {
	cookieName    string
	storage       Provider
	maxAge        int64
	lock          sync.Mutex
}

func NewSessionMange() *SessionManager {
	SessionManager := &SessionManager{
		cookieName: "lz_cookie",
		storage:    newFromMemory(),
		maxAge:     1800,
	}

	go SessionManager.GC()
	return SessionManager
}

func (m *SessionManager) GetCookieN() string  {
	return m.cookieName
}
const COOKIE_MAX_MAX_AGE  = time.Hour * 24 / time.Second  // 单位:秒。
func (m *SessionManager) BeginSession(w http.ResponseWriter, r *http.Request) Session  {
	m.lock.Lock()
	defer m.lock.Unlock()
	fmt.Println("cookie-name:",m.cookieName)
	cookie, err := r.Cookie(m.cookieName)
	fmt.Println(cookie, err)
	maxAge2 := int(COOKIE_MAX_MAX_AGE)
	if err != nil || cookie.Value == "" {
		fmt.Println("----------> current session not exists")
		sid := m.randomId()

		session,_ := m.storage.InitSession(sid, m.maxAge)
		maxAge := m.maxAge

		if maxAge == 0 {
			maxAge = session.(*SessionFromMemory).maxAge
		}

		//设置cookie

		//maxAge2 := int(COOKIE_MAX_MAX_AGE)
		uid_cookie := &http.Cookie{
			Name:  m.cookieName,
			Value:  url.QueryEscape(sid),
			Path:   "/",
			HttpOnly: false,
			MaxAge:  maxAge2,
		}
		http.SetCookie(w, uid_cookie) //设置到响应中
		return session
	} else {
		sid ,_ := url.QueryUnescape(cookie.Value)
		session := m.storage.(*FromMemory).sessions[sid]
		fmt.Println("sesssion----->", session)
		if session == nil {
			fmt.Println("-----------> current session is nil")
			//创建一个
			//sid := m.randomId()
			//根据保存session方式,如内存,数据库中创建
			newSession, _ := m.storage.InitSession(sid, m.maxAge) //该方法有自己的锁,多处调用到

			maxAge := m.maxAge

			if maxAge == 0 {
				maxAge = newSession.(*SessionFromMemory).maxAge
			}
			//用session的ID于cookie关联
			//cookie名字和失效时间由session管理器维护
			newCookie := http.Cookie{
				Name: m.cookieName,
				//这里是并发不安全的,但是这个方法已上锁
				Value:    url.QueryEscape(sid), //转义特殊符号@#¥%+*-等
				Path:     "/",
				HttpOnly: true,
				MaxAge:   maxAge2,
				Expires:  time.Now().Add(time.Duration(maxAge)),
			}
			http.SetCookie(w, &newCookie) //设置到响应中
			fmt.Println("-----------> current session exists")
			return newSession
		}
		return session
	}
}

//通过ID获取session
func (m *SessionManager) GetSessionById(sid string) Session {
	session := m.storage.(*FromMemory).sessions[sid]
	return session
}

//是否内存中存在
func (m *SessionManager) MemoryIsExists(sid string) bool {
	_, ok := m.storage.(*FromMemory).sessions[sid]
	if ok {
		return true
	}
	return false
}

//手动销毁session,同时删除cookie
func (m *SessionManager) Destroy(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie(m.cookieName)
	if err != nil || cookie.Value == "" {
		return
	} else {
		m.lock.Lock()
		defer m.lock.Unlock()

		sid, _ := url.QueryUnescape(cookie.Value)
		m.storage.DestroySession(sid)

		cookie2 := http.Cookie{
			MaxAge:  0,
			Name:    m.cookieName,
			Value:   "",
			Path:    "/",
			Expires: time.Now().Add(time.Duration(0)),
		}

		http.SetCookie(w, &cookie2)
	}
}

func (m *SessionManager) Update(w http.ResponseWriter, r *http.Request)  {
	m.lock.Lock()
	defer m.lock.Unlock()

	cookie, err := r.Cookie(m.cookieName)
	if err != nil {
		return
	}

	t := time.Now()
	sid, _ := url.QueryUnescape(cookie.Value)

	sessions := m.storage.(*FromMemory).sessions
	session := sessions[sid].(*SessionFromMemory)
	session.lastAccessedTime = t

	if m.maxAge != 0 {
		cookie.MaxAge = int(m.maxAge)
	} else {
		cookie.MaxAge =  int(session.maxAge)
	}
	http.SetCookie(w,cookie)
}

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

func (m *SessionManager) GC()  {
	m.lock.Lock()
	defer m.lock.Unlock()

	m.storage.GCSession()
	age2 := int(60 * time.Second)
	time.AfterFunc(time.Duration(age2), func() {
		m.GC()
	})
}

上面为封装的东西,接下来我们如何使用呢

package main

import (
	"library/sessions"
	"net/http"
)

var sessionM *sessions.SessionManager
sessionM = sessions.NewSessionMange()//建议把这个放在你的公共区域,用的时候只调用一次就行了,初始化

func TestSession(w http.ResponseWriter, r *http.Request)  {
	sessionV := sessionM.BeginSession(w, r)
	sessionV.Set("ass1", "test111111")
	fmt.Fprintln(w, sessionV.Get("ass1"))
}

func main() {
	http.HandleFunc("/test-session", TestSession)
	http.HandleFunc("/get-session", GetSessionTest)
	http.ListenAndServe(":8000", nil)
}

func GetSessionTest(w http.ResponseWriter, r *http.Request){
    http.SetCookie(w,uid_cookie)
	sessionV := sessionM.BeginSession(w, r)
	fmt.Fprintln(w, sessionV.Get("ass1"))
}

浏览器输入:127.0.0.1:端口号/test-session
最终输出结果test1111111
然后再打开:127.0.0.1:端口号/get-session
还是返回结果:test1111111
这个时候说明我们的session建成了

然后再找多个其它浏览器试试看。
打印下session的map查看是否收到了不同的key

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值