gin框架08-会话控制

1.coolie是什么?

HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出。
cookie就是解决HTTP协议无状态的方案之一,中文是小甜饼的意思。
Cookie实际上就是服务保存在浏览器上的一段消息。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求。
Cookie由服务器创建,并发送给浏览器,最终由浏览器保存。


cookie.png
2.Cookie的用途

保持用户登录状态
电商网站:京东购物车

3.Cookie的使用

测试服务端发送cookie给客户端,客户端请求时携带cookie

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "time"
)
// 定义中间件
func myTime(c *gin.Context){
    start := time.Now()
    c.Next()
    // 统计时间
    since := time.Since(start)
    fmt.Println("程序用时:",since)
}

func main() {
    // 1. 创建路由器
    r := gin.Default()
    // 服务端要给客户端cookie
    r.GET("cookie", func(c *gin.Context) {
        // 获取客户端地址是否携带cookie
        cookie,err := c.Cookie("key_cookie")
        if err !=nil{
            cookie = "NotSet"
            //设置cookie
            // name名字, value值 string,
            // maxAge int,单位秒(过期时间)
            // path, cookie所在的目录  domain域名 string,
            // secure是否能只能通过https访问, httpOnly是否允许别人通过js获取自己的cookie bool
            c.SetCookie("key_cookie","value_cookie",60,"/",
                "localhost",false,true)
        }
        fmt.Printf("cookie的值是:%s\n",cookie)
    })

    // 3.监听端口,默认8080
    r.Run(":8000")
}

访问.png
cookie练习

模拟实现权限验证中间件
有2个路由,login和home
login用于设置cookie
home是访问查看信息的请求
在请求home之前,先跑中间件代码,检验是否存在cookie


例子.png
package main

import (

"github.com/gin-gonic/gin"
    "net/http"
)
// 定义中间件
func AuthMiddleWare()gin.HandlerFunc{
    return func(c *gin.Context) {
        // 获取客户端cookie并校验费
        if cookie,err:=c.Cookie("abc");err==nil{
            if cookie == "123"{
                c.Next()
                return
            }
        }
        // 返回错误
        c.JSON(http.StatusUnauthorized,gin.H{"error":"err"})
        // 若验证不通过,不再调用后续的函数处理
        c.Abort()
        return
    }
}

func main() {
    // 1. 创建路由器
    r := gin.Default()
    //
    r.GET("/login", func(c *gin.Context) {
        // 设置cookie
        c.SetCookie("abc","123",60,"/",
            "localhost",false,true)
        // 返回信息
        c.String(200,"Login success!")
    })
    r.GET("/home",AuthMiddleWare(), func(c *gin.Context) {
        c.JSON(200,gin.H{"data":"home"})
    })
    // 3.监听端口,默认8080
    r.Run(":8000")
}

访问/home/login进行测试

Cookie的缺点

1、不安全,明文
2、增加带宽消耗
3、可以被禁用
4、cookie有上限

Session是什么

Session可以弥补Cookie的不足,Session必须依赖于Cookie才能使用,生成一个SessionId放在Cookie里传给客户端就可以


image.png

Session中间件开发

1.设计一个通用的Session服务,支持内存存储和redis存储
2.session模块设计
本质上k-v系统,通过key进行增删改查
session可以存储在内存或者redis(2个版本)


session

session接口设计:
1、SET()
2、GET()
3、DEL()
4、SAVE() :session存储,redis的实现延迟加载
SessionMgr接口设计:
1、Init():初始化,加载redis地址
2、CreatSession():创建一个线的session
3、GetSeesion():通过sessionId获取对应的session对象
MemorySession设计:
定义MemorySession对象(字段:存放所有session、存kv的map,读写锁)
构造函数,为了获取对象
1、SET()
2、GET()
3、DEL()
4、SAVE()
MemorySessionMgr设计:
定义MemorySession对象(字段:存放所有session的map,读写锁)
构造函数
1、Init()
2、CreatSession()
3、GetSeesion()
RedisSession设计:
定义RedisSession对象(字段:sessionId,存放kv的map,读写锁,redis连接池,记录内存中map是否被修改的标记)
构造函数
1、SET():将session存到内存中的map
2、GET():取数据,实现延迟加载
3、DEL()
4、SAVE():将session存到redis
RedisSessionMgr设计:
定义RedisSessionMgr对象(字段:redis地址、redis密码、连接池、读写锁,大map)
构造函数
1、Init()
2、CreatSession()
3、GetSeesion()


session

session_mgr.go
package session

// 定义管理者,管理所有session
type SessionMgr interface {
    // 初始化
    Init(addr string,options ...string)(err error)
    CreateSession() (session Session,err error)
    Get(sessionId string)(Session Session,err error)
}

session.go

package session

type Session interface {
    Set(key string,value interface{})error
    Get(key string)(interface{},error)
    Del(key string)error
    Save()error
}

session_mgr.go

package session

// 定义管理者,管理所有session
type SessionMgr interface {
    // 初始化
    Init(addr string,options ...string)(err error)
    CreateSession() (session Session,err error)
    Get(sessionId string)(Session Session,err error)
}

memory.go

package session

import (
    "errors"
    "github.com/gogo/protobuf/plugin/defaultcheck"
    "google.golang.org/genproto/googleapis/datastore/admin/v1"
    admin "google.golang.org/genproto/googleapis/datastore/admin/v1beta1"
    "google.golang.org/grpc/credentials/oauth"
    "sync"
)

// 对象
//MemorySession设计:
//定义MemorySession对象(字段:存放所有session、存kv的map,读写锁)
//构造函数,为了获取对象
//1、SET()
//2、GET()
//3、DEL()
//4、SAVE()
type MemorySession struct{
    sessionId string
    // 存kv
    data map[string]interface{}
    rwlock sync.RWMutex
}
// 构造函数
func NewMemorySession(id string)*MemorySession{
    s := &MemorySession{
        sessionId: id,
        data:      make(map[string]interface{},16),
        //rwlock:    sync.RWMutex{},
    }
    return s
}
func (m *MemorySession) Set(key string,value interface{})(err error){
    // 加锁
    m.rwlock.Lock()
    defer m.rwlock.Unlock()
    // 设置值
    m.data[key] = value
    return
}

func (m *MemorySession) Get(key string)(value interface{},err error){
    m.rwlock.Lock()
    defer m.rwlock.Unlock()
    value,ok := m.data[key]
    if !ok {
        err = errors.New("key not exists in session")
        return
    }
    return
}

func (m *MemorySession) Del(key string)(err error){
    m.rwlock.Lock()
    defer m.rwlock.Unlock()
    delete(m.data,key)
    return
}

func(m *MemorySession) Save()(err error){
    return
}

memory_session_mgr.go

package session

import (
    uuid "github.com/satori/go.uuid"
    "sync"
)

//MemorySessionMgr设计:
//定义MemorySession对象(字段:存放所有session的map,读写锁)
//构造函数
//1、Init()
//2、CreatSession()
//3、GetSeesion()

// 定义对象
type MemorySessionMgr struct {
    sessionMap map[string]Session
    rwlock     sync.RWMutex
}

//构造函数
func NewMemnorySession() *MemorySessionMgr {
    sr:=&MemorySessionMgr{
        sessionMap: make(map[string]Session,1024),
    }
    return sr
}
func (s *MemorySessionMgr) Init(addr string,option ...string)(err error){
    return
}
func (s *MemorySessionMgr)CreateSession()(session Session,err error){
    s.rwlock.Lock()
    defer s.rwlock.Unlock()
    // go get github.com/google/uuid
    // 用uuid作为sessionId
    id, err := uuid.NewV4()
    if err !=nil{
        return
    }
    // 转string
    sessionId := id.String()
    // 创建session
    session = NewMemnorySession(sessionId)
    return
}

memory_session_mgr.go

package session

import (
    "errors"
    "github.com/satori/go.uuid"
    "sync"
)

//MemorySessionMgr设计:
//定义MemorySession对象(字段:存放所有session的map,读写锁)
//构造函数
//1、Init()
//2、CreatSession()
//3、GetSeesion()

// 定义对象
type MemorySessionMgr struct {
    sessionMap map[string]Session
    rwlock     sync.RWMutex
}

//构造函数
func NewMemnorySessionMgr(sessionId string) *MemorySessionMgr {
    sr:=&MemorySessionMgr{
        sessionMap: make(map[string]Session,1024),
    }
    return sr
}
func (s *MemorySessionMgr) Init(addr string,option ...string)(err error){
    return
}
// 创建session
func (s *MemorySessionMgr)CreateSession()(session Session,err error){
    s.rwlock.Lock()
    defer s.rwlock.Unlock()
    // go get github.com/google/uuid
    // 用uuid作为sessionId
    id, err := uuid.NewV4()
    if err !=nil{
        return
    }
    // 转string
    sessionId := id.String()
    // 创建session
    session = NewMemnorySessionMgr(sessionId)
    // 加入到大map
    s.sessionMap[sessionId]=session
    return
}

func (s *MemorySessionMgr)Get(sessionId string)(session Session,err error){
    s.rwlock.Lock()
    defer s.rwlock.Unlock()
    session,ok:=s.sessionMap[sessionId]
    if !ok{
        err=errors.New("session not exit")
        return
    }
    return
}

redis_session.go

package session

import (
    "encoding/json"
    "errors"
    "github.com/gomodule/redigo/redis"
    "sync"
)

//RedisSession设计:
//定义RedisSession对象(字段:sessionId,存放kv的map,读写锁,redis连接池,记录内存中map是否被修改的标记)
//构造函数
//1、SET():将session存到内存中的map
//2、GET():取数据,实现延迟加载
//3、DEL()
//4、SAVE():将session存到redis

type RedisSession struct {
    sessionId string
    pool *redis.Pool
    // 设置session,可以先放在内存map中
    // 批量导入redis,提升性能
    sessionMap map[string]interface{}
    // 读写锁
    rwlock sync.RWMutex
    // 记录内存中map是否被操作
    flag int
}
//用常量定义状态
const(
    // 内存数据没变化
    SessionFlagNone = iota
    // 有变化
    SessionFlagModify
)

// 构造函数
func NewRedisSession(id string,pool *redis.Pool)*RedisSession{
    s:= &RedisSession{
        sessionId:  id,
        pool:pool,
        sessionMap: make(map[string]interface{},16),

        flag:       SessionFlagNone,
    }
    return s
}

func (r *RedisSession)Set(key string,value interface{})(err error){
    // 加锁
    r.rwlock.Lock()
    defer r.rwlock.Unlock()
    // 设置值
    r.sessionMap[key] = value
    // 标志记录
    r.flag = SessionFlagModify
    return
}

// session存储 到redis
func (r *RedisSession)Save()(err error){
    // 加锁
    r.rwlock.Lock()
    defer r.rwlock.Unlock()
    // 若数据不变,不需存
    if r.flag != SessionFlagModify{
        return
    }
    // 从内存中的sessionMap进行序列化
    data,err := json.Marshal(r.sessionMap)
    if err!=nil{
        return
    }
    // 获取redis连接
    conn := r.pool.Get()
    // 保存kv
    _, err = conn.Do("SET",r.sessionId,string(data))
    // 改变状态
    r.flag = SessionFlagNone
    if err != nil{
        return
    }
    return
}

func (r *RedisSession)Get(key string)(result interface{},err error){
    // 加锁
    r.rwlock.Lock()
    defer r.rwlock.Unlock()
    // 先判断内存
    result,ok :=  r.sessionMap[key]
    if !ok{
        err = errors.New("key not exists")
    }
    return

}

// 从redis里再次加载
func (r *RedisSession)loadFromRedis()(err error){
    conn := r.pool.Get()
    reply, err:= conn.Do("GET",r.sessionId)
    if err!=nil{
        return
    }
    // 转字符串
    data, err := redis.String(reply,err)
    if err!= nil{
        return
    }
    // 取到的东西,反序列化到内存的map
    err = json.Unmarshal([]byte(data),&r.sessionMap)
    if err !=nil{
        return
    }
    return
}

func (r * RedisSession) Del(key string)(err error){
    r.rwlock.Lock()
    defer r.rwlock.Unlock()
    r.flag = SessionFlagModify
    delete(r.sessionMap,key)
    return 
}

redis_session_mgr.go

package session

import (
    "errors"
    "github.com/gomodule/redigo/redis"
    uuid "github.com/satori/go.uuid"
    "sync"
    "time"
)

//RedisSessionMgr设计:
//定义RedisSessionMgr对象(字段:redis地址、redis密码、连接池、读写锁,大map)
//构造函数
//1、Init()
//2、CreatSession()
//3、GetSeesion()

type  RedisSessionMgr struct {
    // redis地址
    addr string
    // 密码
    password string
    // 连接池
    pool *redis.Pool
    // 锁
    rwlock sync.RWMutex
    // 大map
    SessionMap map[string]Session
}

func (r *RedisSessionMgr)Init(addr string,options ...string)(err error){
    // 若狗其他参数
    if len(options)>0{
        r.password = options[0]
    }
    // 创建连接池
    r.pool=myPool(addr,r.password)
    r.addr = addr
    return
}

func myPool(addr, password string) *redis.Pool {
    return &redis.Pool{
        Dial: func() (conn redis.Conn , e error) {
            conn, err := redis.Dial("tcp",addr)
            if err!=nil{
                return nil,err
            }
            // 若有密码,判断
            if _,err := conn.Do("AUTO",password);err !=nil{
                conn.Close()
                return nil,err
            }
            return conn,err
        },
        DialContext:     nil,
        TestOnBorrow: func(conn redis.Conn, t time.Time) error {
            _,err := conn.Do("PING")
            return err
        },
        MaxIdle:         64,
        MaxActive:       1000,
        IdleTimeout:     time.Duration(time.Second),
        Wait:            false,
        MaxConnLifetime: 0,
    }
}

func (r *RedisSession)CreateSession() (session Session,err error){
    // 加锁
    r.rwlock.Lock()
    defer r.rwlock.Unlock()
    // 用uuid作为sessionId
    id,err := uuid.NewV4()
    if err!=nil{
        return
    }
    // 转string
    sessionId := id.String()
    // 创建session
    session = NewRedisSession(sessionId,r.pool)
    // 加入到大map
    r.sessionMap[sessionId] = session
    return
}
// 构造
//func NewRedisSessionMgr() SessionMgr{
//  sr := &RedisSessionMgr{
//      SessionMap: make(map[string]Session,32),
//  }
//  return  sr
//}

func (r *RedisSessionMgr) Get(sessionId string)(session Session,err error){
    r.rwlock.Lock()
    defer r.rwlock.Unlock()
    session,ok := r.SessionMap[sessionId]
    if !ok{
        err = errors.New("session not exists")
        return
    }
    return
}

init.go 版本控制

package session

import "fmt"

// 中间件让用户选择使用哪个版本
var (
    sessionMgr SessionMgr
)

func Init(provider string,addr string,options ...string)(err error){
    switch provider {
    case "memory":
        sessionMgr = NewMemnorySessionMgr()
    case "redis":
        sessionMgr = NewRedisSession()
    default:
        fmt.Println("不支持")
        return
    }
    err = sessionMgr.Init(addr,options...)
    return
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

友情链接更多精彩内容