Gorilla Sessions RedisStore Source Code Analysis
To prevent data race, store.Get()
always creates a new session (or make a copy) and returns the session to user.
// Get a session.
// A copied one or new session.
session, _ = store.Get(req, "session-key")
// Add a value.
session.Values["foo"] = "bar"
// Save.
_ = sessions.Save(req, rsp)
Let’s check the source code, the Get()
method of Redistore:
func (s *RediStore) Get(r *http.Request, name string) (*sessions.Session, error) {
return sessions.GetRegistry(r).Get(s, name)
}
// GetRegistry returns a registry instance for the current request.
func GetRegistry(r *http.Request) *Registry {
var ctx = r.Context()
registry := ctx.Value(registryKey)
if registry != nil {
return registry.(*Registry)
}
newRegistry := &Registry{
request: r,
sessions: make(map[string]sessionInfo),
}
*r = *r.WithContext(context.WithValue(ctx, registryKey, newRegistry))
return newRegistry
}
For each request they are different, even that two request from a same client. So for each new request when the server call store.Get()
there will be a registry created. Only when you call store.Get()
more than once in a handler, the registry := ctx.Value(registryKey)
will return a non-nil registry
:
// Get a session.
session, _ = store.Get(req, "session-key")
// Add a value.
session.Values["foo"] = "bar"
// Save.
_ = sessions.Save(req, rsp)
// Get session again
// because the req is same, the Registry
// will be the old one created druing the first store.Get() call.
session, _ = store.Get(req, "session-key")
After gets the registry
, then its Registry.Get()
will be called:
// 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)
}
// because Registry is always created as a new one for each request
// ok always equals to false
// if you call store.Get() at a same handler twice or more,
// the Registry value will be the old one which created during the first store.Get() call
if info, ok := s.sessions[name]; ok {
session, err = info.s, info.e
} else {
// for each request, there will be a new session
// a brand new or a copied one
session, err = store.New(s.request, name)
session.name = name
s.sessions[name] = sessionInfo{s: session, e: err}
}
session.store = store
return
}
Let’s check how store.New
work:
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
var err error
session := sessions.NewSession(s, name)
// make a copy
options := *s.Options
session.Options = &options
session.IsNew = true
if c, errCookie := r.Cookie(name); errCookie == nil {
// verify the cookie
err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
// if the cookie is correct which means there is a corresponding session in the store
// therefore, load from redistore
// load() will makes a deep copy, which prevent data race
// user can use the returned session safely
if err == nil {
ok, err := s.load(session)
session.IsNew = !(err == nil && ok) // not new if no error and data available
}
}
return session, err
}
Let’s check store.load()
// load reads the session from redis.
// returns true if there is a sessoin data in DB
func (s *RediStore) load(session *sessions.Session) (bool, error) {
conn := s.Pool.Get()
defer conn.Close()
if err := conn.Err(); err != nil {
return false, err
}
data, err := conn.Do("GET", s.keyPrefix+session.ID)
if err != nil {
return false, err
}
if data == nil {
return false, nil // no data was associated with this key
}
b, err := redis.Bytes(data, err)
if err != nil {
return false, err
}
return true, s.serializer.Deserialize(b, session)
}
Source code: https://github.com/boj/redistore
查看其他文章