SoFunction
Updated on 2025-03-05

gin session middleware usage and source code process analysis

Overview

Generally, PC website development talks about Session. The server opens the Session mechanism. When the client accesses the server for the first time, the server will generate a sessionId and pass it back to the client for saving through the cookie mechanism. After that, every time the client accesses the server, the sessionId will be brought to the server through the cookie mechanism. The server will find the requested Session session information by analyzing the SessionID;

Session information is stored in the server, similar to: SessionID =》 session information; as for the specific content of the session information, this must be determined based on the specific business logic, but it is generally user information;

There are many ways to save sessions on the server: files, caches, databases, etc., so there are also many derived session carriers: redis, files, mysql, memcached, etc.; each carrier has its own advantages and disadvantages, and a suitable carrier can be selected according to different business scenarios;

Below we mainly introduce redis as a carrier:

Session in Gin

The implementation of Session in gin mainly relies on the implementation of Gin-session middleware (/gin-contri...Use different stores to save Session information in different carriers:

Mainly including:

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

Simple call

Create a new store and inject middleware into gin's router. When you need to use it, use (c) inside HandlerFunc to get the session

// Create a carrier-based object (cookie-based)store := ([]byte("secret"))
(("sessionId", store))
("/hello", func(c *) {
    // Session is used in the middle    session := (c)
    if ("hello") != "world" {
        ("hello", "world")
        ()
    }
    ....
})

Gin-session source code process

Let’s take the redis that is more frequently used as a store to see the main workflow of Gin-session

Simple call

router := ()
// @Todo Create a store objectstore, err := (10, "tcp", "localhost:6379", "", []byte("secret"))
    if err != nil {("  err is :%v", err)}
("/admin", func(c *) {
        session := (c)
        var count int
        v := ("count")
        if v == nil {
            count = 0
        } else {
            count = v.(int)
            count++
        }
        ("count", count)
        ()
        (200, {"count": count})
    })

Create a store object

The underlying store creation

func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) (*RediStore, error) {
    return NewRediStoreWithPool(&{
        MaxIdle:     size,
        IdleTimeout: 240 * ,
        TestOnBorrow: func(c , t ) error {
            _, err := ("PING")
            return err
        },
        Dial: func() (, error) {
            return dial(network, address, password)
        },
    }, keyPairs...)
}
// NewRediStoreWithPool instantiates a RediStore with a * passed in.
func NewRediStoreWithPool(pool *, keyPairs ...[]byte) (*RediStore, error) {
    rs := &RediStore{
        // //gomodule/redigo/redis#Pool
        Pool:   pool,
        Codecs: (keyPairs...),
        Options: &{
            Path:   "/", // Client's Path            MaxAge: sessionExpire, // Expires/MaxAge of the client        },
        DefaultMaxAge: 60 * 20, // Expiration time is 20 minutes        maxLength:     4096, // Maximum length        keyPrefix:     "session_", // key prefix        serializer:    GobSerializer{}, // The internal serialization uses the Gob library    }
    // @Todo Try to create a connection    _, err := ()
    return rs, err
}

According to the function, you can see that an init parameter is initialized according to the incoming parameters (size: connection number, network: connection protocol, address: service address, password: password).Object, passed inObject and some default parameters initializationRediStoreObject

Called as middleware at the gin router layer

It is not complicated to use as a gin middleware, just put HandlerFunc behind the array;

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

Let’s take a look at what the middleware method implements: ("sessionId", store),

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


func Sessions(name string, store Store)  {
    return func(c *) {
        s := &session{name, , store, nil, false, }
        (DefaultKey, s)
        defer ()
        ()
    }
}

type session struct {
    name    string
    request *
    store   Store
    session *
    written bool
    writer  
}

We can see his implementation of HandlerFunc:

  • Create a session object (including: request information, Store storage carrier redisRediStoreObject...)
  • Set the created session object to*Key value pair; key is a fixed value: DefaultKey (/gin-gonic/contrib/sessions)
  • The routing layer will only be executed once during initialization, andstoreIt's tied toSession, so eachsessionAll will point to the samestore

Get session implementation

We can reach that the session object has been created in the router middleware and set to the corresponding one, then we only need to call (c);

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

Note: The returned type is the interface definition of Session, and the set in the session is the specific implementation;

Read session value

Get the session value through simple code

// @Todo SessionKey to get user informationsession := (c)
sessionInfo := ()

It has been mentioned above.The interface definition class of the returned Session defines the Get() method interface. The actual method implementation is still in the session.

type session struct {
    name    string
    request *
    store   Store
    session * // Actual internal data exchange    written bool
    writer  
}
func (s *session) Get(key interface{}) interface{} {
    // Get the specific session through(); the specific value is saved in the Values ​​map    return ().Values[key]
}
func (s *session) Session() * {
    if  == nil {
        var err error
        , err = (, )
        if err != nil {
            (errorFormat, err)
        }
    }
    return 
}

By observing the structure of the session, it containssession Object, this must be distinguished from the previous Session interface definition; hereIt is a truly saved session object; its structure is as follows: (gorilla/sessions library)

// 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! KnowWhat is it after, then().Values[key]It has also become very easy to understand. In fact, the Values ​​property is actually a map, which saves the specific value of our set in the session; let's continue to move down. . .

whenWhen it is empty, we pass(, )Acquiring;

//  ask// Session Name, err = (, )

Notice:: request and : session nameWhen was it injected? In fact, we can review the above here:

// @Todo When the session is injected into the routing layer, the Seesions method actually initializes the name and other values ​​of this session, but the saved session is nil("sessionId", store)

Let's get back to the point, we continue to go down and passCome and getsession; Because what we analyze here isredisCarrier, sostoreyesRediStoreLet's take a look at hisGETmethod:

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

We can see: By(r)Get oneRegistry; then passRegistryofGETMethod to get a session;

Let's take a look at his implementation:

// GetRegistry essentially returns a struct of a Registryfunc GetRegistry(r *) *Registry {
    registry := (r, registryKey)
    if registry != nil {
        return registry.(*Registry)
    }
    newRegistry := &Registry{
        request:  r,
        sessions: make(map[string]sessionInfo),
    }
    (r, registryKey, newRegistry)
    return newRegistry
}
// Registry stores sessions used during a request.
type Registry struct {
    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, ("sessions: invalid character in cookie name: %s", name)
    }
    if info, ok := [name]; ok {
        session, err = , 
    } else {
        session, err = (, name)
         = name
        [name] = sessionInfo{s: session, e: err}
    }
     = store
    return
}

Actually it's not difficult to seeGetRegistryEssentially, it returns oneRegistryStructure; then combinedGetWe can see the methodRegistryThe structure essentially maintains a mapping relationship of key -》 value; and the key in it is the session name we started injecting in the route, and the value is the sessionInfo we saved;

So we can understand:RegistryThe function is to maintain the mapping from a business session name to the corresponding session and isolate the session. When the session does not exist, you need to call (, name) to create a new session:

// New returns a session for the given name without adding it to the registry.
//
// See gorilla/sessions ().
func (s *RediStore) New(r *, name string) (*, error) {
    var (
        err error
        ok  bool
    )
    // @Todo Initialize a service session    session := (s, name)
    // make a copy
    options := *
     = &options
     = true
    // @Todo Read sessionID in cookie based on session_name    if c, errCookie := (name); errCookie == nil {
        // @Todo codec decodes cookie values        err = (name, , &, ...)
        if err == nil {
            // @Todo redis to get specific sessionInfo based on sessionID            ok, err = (session)
             = !(err == nil && ok) // not new if no error and data available
        }
    }
    return session, err
}

Running for so long. . Finally, I saw that the sessionID is read from the cookie, and then load our session from redis based on the sessionID;

Write session value

In fact, the difference between writing and reading is not very big:

// @Todo write value entry (actually, it is to assign values ​​in the session map)func (s *session) Set(key interface{}, val interface{}) {
    ().Values[key] = val
     = true
}
func (s *session) Save() error {
    if () {
        e := ().Save(, )
        if e == nil {
             = false
        }
        return e
    }
    return nil
}
// Save is a convenience method to save this session. It is the same as calling
// (request, response, session). You should call Save before writing to
// the response or returning from the handler.
func (s *Session) Save(r *, w ) error {
    return (r, w, s)
}
// Save adds a single session to the response.
func (s *RediStore) Save(r *, w , session *) error {
    // @Todo Delete session and force expire in the cookie    if  <= 0 {
        if err := (session); err != nil {
            return err
        }
        (w, ((), "", ))
    } else {
        // @Todo If there is no SessionID, a sessionID will be generated randomly (will the same SessionID be generated when concurrently)        if  == "" {
             = (((32)), "=")
        }
        // @Todo Write the value of session to redis        if err := (session); err != nil {
            return err
        }
        // @Todo cookie encode it        encoded, err := ((), , ...)
        if err != nil {
            return err
        }
        // @Todo Write cookies (SessionID, path, maxAge, etc.) according to the session attributes        (w, ((), encoded, ))
    }
    return nil
}

In fact, we can see the final executionsaveThe final implementation is still placedRediStoreObject; is also the last method above, so let’s focus on the last method:

  • If there is no SessionID, a sessionID will be generated randomly (will the same SessionID be generated when concurrently)
  • Write the value of session to redis
  • Encode cookies
  • Write cookies (SessionID, path, maxAge, etc.) according to the session attributes

Basic completion: Leave a simple question: Does the generated SessionID exist when the request is concurrent?

Suggestion: Join gin-session middleware and follow. .

The above is the detailed content of the use of gin session middleware and source code analysis. For more information about gin session middleware, please pay attention to my other related articles!