最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

gin?session中間件使用及源碼流程分析

 更新時(shí)間:2023年10月10日 11:23:26   作者:一只小蝸牛  
這篇文章主要為大家介紹了gin?session中間件使用及源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

概述

一般PC 端網(wǎng)站開發(fā)都會(huì)談到Session,服務(wù)端開啟Session機(jī)制,客戶端在第一次訪問服務(wù)端時(shí),服務(wù)端會(huì)生成sessionId通過cookie 機(jī)制回傳到客戶端保存,之后每次客戶端訪問服務(wù)端時(shí)都會(huì)通過cookie機(jī)制帶sessionId到服務(wù)端,服務(wù)端通過解析SessionID找到請求的Session會(huì)話信息;

Session信息都是保存在服務(wù)器中的,類似:SessionID =》 session信息;至于session信息具體內(nèi)容是什么,這個(gè)要根據(jù)具體的業(yè)務(wù)邏輯來確定,但是普遍是用戶信息;

服務(wù)端保存Session的方式很多:文件,緩存,數(shù)據(jù)庫等,所以衍生出來的session的載體也有很多:redis,文件,mysql,memcached 等等;其中每一種載體都有著自己的優(yōu)劣,根據(jù)不同的業(yè)務(wù)場景可以選取合適的載體;

下面我們主要介紹 redis 作為載體:

Gin中的 Session

gin中Session的實(shí)現(xiàn)主要依賴于Gin-session中間件實(shí)現(xiàn) (https://github.com/gin-contri... 通過注入不同的 store 從而實(shí)現(xiàn)不同的載體保存Session信息 :

主要包括:

  • cookie-based
  • Redis
  • memcached
  • MongoDB
  • memstore

簡單調(diào)用

創(chuàng)建一個(gè)新的store并將中間件注入到gin的路由器中。需要使用的時(shí)候在HandlerFunc內(nèi)部用 sessions.Default(c)即可獲取到session

// 創(chuàng)建載體方式對象(cookie-based)
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("sessionId", store))
r.GET("/hello", func(c *gin.Context) {
    // session中間使用
    session := sessions.Default(c)
    if session.Get("hello") != "world" {
        session.Set("hello", "world")
        session.Save()
    }
    ....
})

Gin-session 源碼流程

下面我們以使用頻率較高的 redis 作為 store 來看看 Gin-session 主要工作流程

簡單調(diào)用

router := gin.Default()
// @Todo 創(chuàng)建store對象
store, err := sessions.NewRedisStore(10, "tcp", "localhost:6379", "", []byte("secret"))
    if err != nil {log.Fatal(" sessions.NewRedisStore err is :%v", err)}
router.GET("/admin", func(c *gin.Context) {
        session := sessions.Default(c)
        var count int
        v := session.Get("count")
        if v == nil {
            count = 0
        } else {
            count = v.(int)
            count++
        }
        session.Set("count", count)
        session.Save()
        c.JSON(200, gin.H{"count": count})
    })

創(chuàng)建 store 對象

底層的 store 創(chuàng)建

func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) (*RediStore, error) {
    return NewRediStoreWithPool(&redis.Pool{
        MaxIdle:     size,
        IdleTimeout: 240 * time.Second,
        TestOnBorrow: func(c redis.Conn, t time.Time) error {
            _, err := c.Do("PING")
            return err
        },
        Dial: func() (redis.Conn, error) {
            return dial(network, address, password)
        },
    }, keyPairs...)
}
// NewRediStoreWithPool instantiates a RediStore with a *redis.Pool passed in.
func NewRediStoreWithPool(pool *redis.Pool, keyPairs ...[]byte) (*RediStore, error) {
    rs := &RediStore{
        // http://godoc.org/github.com/gomodule/redigo/redis#Pool
        Pool:   pool,
        Codecs: securecookie.CodecsFromPairs(keyPairs...),
        Options: &sessions.Options{
            Path:   "/", // 客戶端的 Path
            MaxAge: sessionExpire, // 客戶端的Expires/MaxAge
        },
        DefaultMaxAge: 60 * 20, // 過期時(shí)間是20分鐘
        maxLength:     4096, // 最大長度
        keyPrefix:     "session_", // key 前綴
        serializer:    GobSerializer{}, // 內(nèi)部序列化采用了Gob庫
    }
    // @Todo 嘗試創(chuàng)建連接
    _, err := rs.ping()
    return rs, err
}

根據(jù)函數(shù)可以看到,根據(jù)傳入?yún)?shù)(size:連接數(shù), network:連接協(xié)議,address:服務(wù)地址,password:密碼)初始化一個(gè) redis.Pool 對象,通過傳入 redis.Pool 對象 和 一些默認(rèn)的參數(shù)初始化 RediStore 對象

作為中間件在 gin router層調(diào)用

作為 gin 中間件使用并不復(fù)雜,就是把HandlerFunc放到group.Handlers數(shù)組后面;

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

 下面看看這個(gè)中間件的方法實(shí)現(xiàn)了什么: sessions.Sessions("sessionId", store),

const (
    DefaultKey  = "github.com/gin-gonic/contrib/sessions"
    errorFormat = "[sessions] ERROR! %s\n"
)


func Sessions(name string, store Store) gin.HandlerFunc {
    return func(c *gin.Context) {
        s := &session{name, c.Request, store, nil, false, c.Writer}
        c.Set(DefaultKey, s)
        defer context.Clear(c.Request)
        c.Next()
    }
}

type session struct {
    name    string
    request *http.Request
    store   Store
    session *sessions.Session
    written bool
    writer  http.ResponseWriter
}

我們可以看到他 HandlerFunc 的實(shí)現(xiàn):

  • 創(chuàng)建一個(gè)session對象(包括:request信息,Store 存儲(chǔ)載體redis RediStore 對象...)
  • 把創(chuàng)建的的session對象 set 到 *gin.Context 鍵值對中;key 為一個(gè)定值:DefaultKey(github.com/gin-gonic/contrib/sessions)
  • 路由層只會(huì)在初始化的時(shí)候執(zhí)行一次,而且 store 是捆綁在 Session 中,因此每一個(gè) session 都會(huì)指向同一個(gè)store

獲取session實(shí)現(xiàn)

我們可以到在 router 中間件中已經(jīng)創(chuàng)建好 session 對象并且 set 到對應(yīng)的 gin.Context 中,那么我們只需要調(diào)用 sessions.Default(c) 出來即可;

// shortcut to get session
func Default(c *gin.Context) Session {
    return c.MustGet(DefaultKey).(Session)
}

注意:返回的類型是Session的接口定義,gin.Context 中 set 的是session具體實(shí)現(xiàn);

讀取session值

通過簡單的代碼獲取session的值

// @Todo SessionKey 中獲取用戶信息
session := sessions.Default(c)
sessionInfo := session.Get(public.AdminSessionInfoKey)

上面已經(jīng)有提到過了,sessions.Default 返回的 Session 的接口定義類,其定義了Get()這個(gè)方法接口,實(shí)際的方法實(shí)現(xiàn)還在session中。

type session struct {
    name    string
    request *http.Request
    store   Store
    session *sessions.Session // 實(shí)際內(nèi)部數(shù)據(jù)交換
    written bool
    writer  http.ResponseWriter
}
func (s *session) Get(key interface{}) interface{} {
    // 通過s.Session() 獲取具體的session;具體的值保存在 Values 這個(gè)map 中
    return s.Session().Values[key]
}
func (s *session) Session() *sessions.Session {
    if s.session == nil {
        var err error
        s.session, err = s.store.Get(s.request, s.name)
        if err != nil {
            log.Printf(errorFormat, err)
        }
    }
    return s.session
}

通過觀察 session 的結(jié)構(gòu)體, 里面包含著 session sessions.Session 對象,這個(gè)要跟 之前的 Session 接口定義區(qū)分開;這里的 sessions.Session 是真正保存的session 對象;其結(jié)構(gòu)體如下:(gorilla/sessions庫)

// Session stores the values and optional configuration for a session.
type Session struct {
    // The ID of the session, generated by stores. It should not be used for
    // user data.
    ID string
    // Values contains the user-data for the session.
    Values  map[interface{}]interface{}
    Options *Options
    IsNew   bool
    store   Store
    name    string
}

OK! 知道 s.session 是什么后,那么 s.Session().Values[key] 也變得非常好理解了,其實(shí) Values 這個(gè)屬性其實(shí)是個(gè)map,其中保存的就是我們 set 在session中的具體值;我們繼續(xù)往下走。。。

當(dāng) s.session 是空的時(shí)候,我們就通過 s.store.Get(s.request, s.name) 獲?。?/p>

// s.request 請求
// s.name session名
s.session, err = s.store.Get(s.request, s.name)

注意:s.request: 請求 和 s.name: session名 什么時(shí)候注入的呢? 其實(shí)我們這里可以回顧下上面:

// @Todo 在路由層注入session的時(shí)候 Seesions 方法其實(shí)就初始化了這個(gè)session的name 和其他值,只是保存的session是 nil
sessions.Sessions("sessionId", store)

言歸正傳,我們繼續(xù)往下走,上面通過 store.Get 來獲取 session;因?yàn)檫@里的我們分析的是redis載體,所以 store 是 RediStore 我們看下他的GET方法:

// Get returns a session for the given name after adding it to the registry.
//
// See gorilla/sessions FilesystemStore.Get().
func (s *RediStore) Get(r *http.Request, name string) (*sessions.Session, error) {
    return sessions.GetRegistry(r).Get(s, name)
}

我們可以看到:通過 sessions.GetRegistry(r) 獲取到一個(gè) Registry ;然后通過 Registry 的 GET方法獲取一個(gè)session;

我們來看看他的實(shí)現(xiàn):

// GetRegistry 本質(zhì)就是返回一個(gè) Registry 的一個(gè)結(jié)構(gòu)體
func GetRegistry(r *http.Request) *Registry {
    registry := context.Get(r, registryKey)
    if registry != nil {
        return registry.(*Registry)
    }
    newRegistry := &Registry{
        request:  r,
        sessions: make(map[string]sessionInfo),
    }
    context.Set(r, registryKey, newRegistry)
    return newRegistry
}
// Registry stores sessions used during a request.
type Registry struct {
    request  *http.Request
    sessions map[string]sessionInfo
}
// Get registers and returns a session for the given name and session store.
//
// It returns a new session if there are no sessions registered for the name.
func (s *Registry) Get(store Store, name string) (session *Session, err error) {
    if !isCookieNameValid(name) {
        return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name)
    }
    if info, ok := s.sessions[name]; ok {
        session, err = info.s, info.e
    } else {
        session, err = store.New(s.request, name)
        session.name = name
        s.sessions[name] = sessionInfo{s: session, e: err}
    }
    session.store = store
    return
}

其實(shí)不難看出 GetRegistry 本質(zhì)上就是返回了一個(gè) Registry 結(jié)構(gòu)體;然后結(jié)合 Get 方法我們可以看出其實(shí) Registry 結(jié)構(gòu)體本質(zhì)上是維護(hù)著一個(gè) key -》 value 的映射關(guān)系; 而其中的 key 就是我們 開始在路由注入的 session name , value 就是我們保存的 sessionInfo;

所以我們也可以理解:Registry 的作用就是維護(hù)一個(gè)業(yè)務(wù)session名到對應(yīng)session的映射,隔離了session。當(dāng)session不存在時(shí),需要調(diào)用 store.New(s.request, name) 來新建一個(gè)session:

// New returns a session for the given name without adding it to the registry.
//
// See gorilla/sessions FilesystemStore.New().
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
    var (
        err error
        ok  bool
    )
    // @Todo 初始化一個(gè)業(yè)務(wù)的session 
    session := sessions.NewSession(s, name)
    // make a copy
    options := *s.Options
    session.Options = &options
    session.IsNew = true
    // @Todo 根據(jù)session_name讀取cookie中的sessionID
    if c, errCookie := r.Cookie(name); errCookie == nil {
        // @Todo 編解碼器對cookie值進(jìn)行解碼
        err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
        if err == nil {
            // @Todo redis中根據(jù)sessionID 獲取具體的 sessionInfo
            ok, err = s.load(session)
            session.IsNew = !(err == nil && ok) // not new if no error and data available
        }
    }
    return session, err
}

跑了這么久。。終于看到從 cookie 中讀取 sessionID,然后 根據(jù)SessionID 從 redis 中把我們的session加載出來;

寫入 session 值

其實(shí) 寫入 和 讀取 差別不是很大:

// @Todo 寫值入口 (其實(shí)就是session map 中賦值一下)
func (s *session) Set(key interface{}, val interface{}) {
    s.Session().Values[key] = val
    s.written = true
}
func (s *session) Save() error {
    if s.Written() {
        e := s.Session().Save(s.request, s.writer)
        if e == nil {
            s.written = false
        }
        return e
    }
    return nil
}
// Save is a convenience method to save this session. It is the same as calling
// store.Save(request, response, session). You should call Save before writing to
// the response or returning from the handler.
func (s *Session) Save(r *http.Request, w http.ResponseWriter) error {
    return s.store.Save(r, w, s)
}
// Save adds a single session to the response.
func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
    // @Todo 刪除session并把cookie中也強(qiáng)制過期
    if session.Options.MaxAge <= 0 {
        if err := s.delete(session); err != nil {
            return err
        }
        http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
    } else {
        // @Todo 如果沒SessionID 就隨機(jī)生成一個(gè)sessionID (并發(fā)來的時(shí)候是否會(huì)生成相同SessionID)
        if session.ID == "" {
            session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
        }
        // @Todo 將session的值寫入redis
        if err := s.save(session); err != nil {
            return err
        }
        // @Todo cookie編碼一下
        encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
        if err != nil {
            return err
        }
        // @Todo 根據(jù)session的屬性,寫入 cookie (SessionID, path, maxAge等)
        http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
    }
    return nil
}

其實(shí)我們可以看到最后執(zhí)行 save 的最終實(shí)現(xiàn)還是放在了 RediStore 對象中;也是上面的最后一個(gè)方法,所以我們重點(diǎn)看看最后一個(gè)方法:

  • 如果沒SessionID 就隨機(jī)生成一個(gè)sessionID (并發(fā)來的時(shí)候是否會(huì)生成相同SessionID)
  • 將session的值寫入redis
  • cookie編碼一下
  • 根據(jù)session的屬性,寫入 cookie (SessionID, path, maxAge等)

基本完成:留一個(gè)簡單問題,當(dāng)請求并發(fā)的時(shí)候生成 SessionID 是否存在相同?

建議:聯(lián)合gin-session中間件,跟著看。。

以上就是gin session中間件使用及源碼分析的詳細(xì)內(nèi)容,更多關(guān)于gin session中間件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang常用加密解密算法總結(jié)(AES、DES、RSA、Sha1、MD5)

    golang常用加密解密算法總結(jié)(AES、DES、RSA、Sha1、MD5)

    在項(xiàng)目開發(fā)過程中,當(dāng)操作一些用戶的隱私信息,本文主要主要介紹了golang常用加密解密算法總結(jié)(AES、DES、RSA、Sha1MD5),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Go?1.22版本新特性前瞻

    Go?1.22版本新特性前瞻

    這篇文章主要為大家介紹了Go?1.22版本新特性前瞻,包含語言的變化,編譯器、運(yùn)行時(shí)與工具鏈等應(yīng)用對比詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • Go語言如何并發(fā)超時(shí)處理詳解

    Go語言如何并發(fā)超時(shí)處理詳解

    大家都知道golang并沒有在語言層次上提供超時(shí)操作,但可以通過一些小技巧實(shí)現(xiàn)超時(shí)。下面來一起看看吧,有需要的朋友們可以參考借鑒。
    2016-09-09
  • golang coroutine 的等待與死鎖用法

    golang coroutine 的等待與死鎖用法

    這篇文章主要介紹了golang coroutine 的等待與死鎖用法詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • Golang如何交叉編譯各個(gè)平臺(tái)的二進(jìn)制文件詳解

    Golang如何交叉編譯各個(gè)平臺(tái)的二進(jìn)制文件詳解

    這篇文章主要給大家介紹了關(guān)于Golang如何交叉編譯各個(gè)平臺(tái)的二進(jìn)制文件的相關(guān)資料,并介紹了golang如何讓編譯生產(chǎn)的二進(jìn)制文件變小,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • golang實(shí)現(xiàn)大文件上傳功能全過程

    golang實(shí)現(xiàn)大文件上傳功能全過程

    Go語言可以用來實(shí)現(xiàn)大文件傳輸,下面這篇文章主要給大家介紹了關(guān)于golang實(shí)現(xiàn)大文件上傳功能的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07
  • Go語言中程序是怎么編譯的實(shí)現(xiàn)

    Go語言中程序是怎么編譯的實(shí)現(xiàn)

    本文主要介紹了Go語言中程序是怎么編譯的實(shí)現(xiàn),深入探討Go語言的編譯機(jī)制和最新的模塊管理系統(tǒng)Go Modules的使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-06-06
  • GoLang string與strings.Builder使用對比詳解

    GoLang string與strings.Builder使用對比詳解

    這篇文章主要介紹了GoLang string與strings.Builder使用對比,Builder 用于使用 Write 方法有效地構(gòu)建字符串。它最大限度地減少了內(nèi)存復(fù)制。零值可以使用了。不要復(fù)制非零生成器
    2023-03-03
  • Go1.21新增內(nèi)置函數(shù)(built-in?functions)詳解

    Go1.21新增內(nèi)置函數(shù)(built-in?functions)詳解

    Go?1.21新增的內(nèi)置函數(shù)分別是?min、max?和?clear,這篇文章主要帶大家一起了解一下這幾個(gè)函數(shù)的用途和使用示例,感興趣的小伙伴可以學(xué)習(xí)一下
    2023-08-08
  • 一文帶你學(xué)會(huì)使用Go語言實(shí)現(xiàn)自己的MCP服務(wù)端

    一文帶你學(xué)會(huì)使用Go語言實(shí)現(xiàn)自己的MCP服務(wù)端

    這篇文章將帶大家速覽MCP的核心概念,并以Go語言為例,介紹如何開發(fā)MCP服務(wù)端和客戶端,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考一下
    2025-04-04

最新評(píng)論

突泉县| 桦南县| 承德市| 荥阳市| 法库县| 崇文区| 阿巴嘎旗| 二手房| 巫山县| 亳州市| 建阳市| 通渭县| 肇源县| 吴川市| 江油市| 沧州市| 阿拉尔市| 灯塔市| 密云县| 环江| 昌邑市| 星子县| 江孜县| 韶山市| 巴楚县| 论坛| 石棉县| 天柱县| 商河县| 肥东县| 蛟河市| 万宁市| 兴隆县| 淮滨县| 盐城市| 永登县| 义马市| 皮山县| 涟源市| 白朗县| 通道|