前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 如何使用session

Go 如何使用session

原创
作者头像
IT工作者
发布2022-06-30 11:16:11
5200
发布2022-06-30 11:16:11
举报
文章被收录于专栏:程序技术知识

Go 语言实现操作session不像cookie那样,net/http包里有现成函数可以很方便的使用,一些web服务用到session的话,没办法地自己敲代码实现。

Go具体实现session:

服务端可以通过内存、redis、数据库等存储session数据(本例只有内存)。

通过cookie将唯一SessionID发送到客户端

session.go

代码语言:javascript
复制
package session

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

//session存储方式接口
type Provider interface {
    //初始化一个session,sid根据需要生成后传入
    SessionInit(sid string) (Session, error)
    //根据sid,获取session
    SessionRead(sid string) (Session, error)
    //销毁session
    SessionDestroy(sid string) erro
    //回收
    SessionGC(maxLifeTime int64)
}

//Session操作接口
type Session interface {
    Set(key, value interface{}) erro
    Get(key interface{}) interface{}
    Delete(ket interface{}) erro
    SessionID() string
}

type Manager struct {
    cookieName  string
    lock        sync.Mutex //互斥锁
    provider    Provider   //存储session方式
    maxLifeTime int64      //有效期
}

//实例化一个session管理器
func NewSessionManager(provideName, cookieName string, maxLifeTime int64) (*Manager, error) {
    provide, ok := provides[provideName]
    if !ok {
        return nil, fmt.Errorf("session: unknown provide %q ", provideName)
    }
    return &Manager{cookieName: cookieName, provider: provide, maxLifeTime: maxLifeTime}, nil
}

//注册 由实现Provider接口的结构体调用
func Register(name string, provide Provider) {
    if provide == nil {
        panic("session: Register provide is nil")
    }
    if _, ok := provides[name]; ok {
        panic("session: Register called twice for provide " + name)
    }
    provides[name] = provide
}

var provides = make(map[string]Provider)

//生成sessionId
func (manager *Manager) sessionId() string {
    b := make([]byte, 32)
    if _, err := io.ReadFull(rand.Reader, b); err != nil {
        return ""
    }
    //加密
    return base64.URLEncoding.EncodeToString(b)
}

//判断当前请求的cookie中是否存在有效的session,存在返回,否则创建
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,
            MaxAge:   int(manager.maxLifeTime),
            Expires:  time.Now().Add(time.Duration(manager.maxLifeTime)),
            //MaxAge和Expires都可以设置cookie持久化时的过期时长,Expires是老式的过期方法,
            // 如果可以,应该使用MaxAge设置过期时间,但有些老版本的浏览器不支持MaxAge。
            // 如果要支持所有浏览器,要么使用Expires,要么同时使用MaxAge和Expires。
        }
        http.SetCookie(w, &cookie)
    } else {
        sid, _ := url.QueryUnescape(cookie.Value) //反转义特殊符号
        session, _ = manager.provider.SessionRead(sid)
    }
    return session
}

//销毁session 同时删除cookie
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()
        sid, _ := url.QueryUnescape(cookie.Value)
        manager.provider.SessionDestroy(sid)
        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() })
}



memory.go

package memory

import (
    "container/list"
    "example/example/public/session"
    "sync"
    "time"
)

var pder = &FromMemory{list: list.New()}

func init() {
    pder.sessions = make(map[string]*list.Element, 0)
    //注册  memory 调用的时候一定有一致
    session.Register("memory", pder)
}

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

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

//获取session
func (st *SessionStore) Get(key interface{}) interface{} {
    pder.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)
    pder.SessionUpdate(st.sid)
    return nil
}
func (st *SessionStore) SessionID() string {
    return st.sid
}

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

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

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

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

func (frommemory *FromMemory) SessionGC(maxLifeTime int64) {
    frommemory.lock.Lock()
    defer frommemory.lock.Unlock()
    for {
        element := frommemory.list.Back()
        if element == nil {
            break
        }
        if (element.Value.(*SessionStore).LastAccessedTime.Unix() + maxLifeTime) <
            time.Now().Unix() {
            frommemory.list.Remove(element)
            delete(frommemory.sessions, element.Value.(*SessionStore).sid)
        } else {
            break
        }
    }
}
func (frommemory *FromMemory) SessionUpdate(sid string) error {
    frommemory.lock.Lock()
    defer frommemory.lock.Unlock()
    if element, ok := frommemory.sessions[sid]; ok {
        element.Value.(*SessionStore).LastAccessedTime = time.Now()
        frommemory.list.MoveToFront(element)
        return nil
    }
    return nil
}

memory.go里面是sesson.go 的Provider 和Session 接口的具体实现,通过这种方式可以灵活的扩展存取session数据的方式。

调用实例:

main.go

代码语言:javascript
复制
package main

import (
    _ "example/example/public/memory"  //这里修改成你存放menory.go相应的目录
    "example/example/public/session" //这里修改成你存放session.go相应的目录
    "fmt"
    "github.com/aliyun/alibaba-cloud-sdk-go/sdk/log"
    "net/http"
)

var globalSessions *session.Manage

func init() {
    var err erro
    globalSessions, err = session.NewSessionManager("memory", "goSessionid", 3600)
    if err != nil {
        fmt.Println(err)
        return
    }
    go globalSessions.GC()
    fmt.Println("fd")
}

func sayHelloHandler(w http.ResponseWriter, r *http.Request) {

    cookie, err := r.Cookie("name")
    if err == nil {
        fmt.Println(cookie.Value)
        fmt.Println(cookie.Domain)
        fmt.Println(cookie.Expires)
    }
    //fmt.Fprintf(w, "Hello world!\n") //这个写入到w的是输出到客户端的
}
func login(w http.ResponseWriter, r *http.Request) {
    sess := globalSessions.SessionStart(w, r)
    val := sess.Get("username")
    if val != nil {
        fmt.Println(val)
    } else {
        sess.Set("username", "jerry")
        fmt.Println("set session")
    }
}
func loginOut(w http.ResponseWriter, r *http.Request) {
    //销毁
    globalSessions.SessionDestroy(w, r)
    fmt.Println("session destroy")
}

func main() {
    http.HandleFunc("/", sayHelloHandler) //    设置访问路由
    http.HandleFunc("/login", login)
    http.HandleFunc("/loginout", loginOut) //销毁
    log.Fatal(http.ListenAndServe(":8080", nil))
}

memory 包的应用方式用下划线,只需执行 memory的init方法即可。

运行 main.go

访问 http://localhost:8080/login 设置session

服务端输出:

set session

jerry

访问http://localhost:8080/loginout 销毁session

服务端输出:

session destroy

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档