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!