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