alexedwards / scs

HTTP Session Management for Go
MIT License
2.13k stars 166 forks source link

Separate modification and saving of session data #36

Closed alexedwards closed 5 years ago

alexedwards commented 6 years ago

See discussion in https://github.com/alexedwards/scs/issues/23

This could be addressed by implementing Middleware which uses a custom ResponseWriter to intercept HTTP Responses, save the session data to the store and set the session cookie - similar to v0.1 of SCS (https://github.com/alexedwards/scs/blob/v0.1.0/session/manager.go)

Downside is that this causes problems with frameworks/applications which also try to call WriteHeader. See https://github.com/alexedwards/scs/issues/15

A workaround might be to set the session cookie header each time the session is modified (or even loaded) and only handle saving to the store in the middleware. I think that would work for most stores, except the CookieStore, which needs the response headers to not have already been written.

jpfluger commented 6 years ago

Ultimately scs is one key ingredient to a greater solution. Since the greater solution is often based on a framework (with its own custom http handlers), it seems that fixing this issue is best left to middleware which would need to check if there's session data awaiting save and then to actually save the session data.

I now have a working example of separating writeHeader from saveStore. Although I touched multiple files, here's a gist for sessions.go. It's backwards compatible - no breaking changes.

I implemented the AutoSave idea successfully tested with Redis but should work for CookieStore. It will need to be cleaned up for locking mutex. In the gist, key modifications include:

Here's a small example of using it in echo middleware

session := config.SessionManager.Load(c.Request())

if config.SessionManager.Opts().IdleTimeout() <= 0 {
    err := session.TouchHeader(c.Response())
    if err != nil {
        c.Error(fmt.Errorf("could not touch header for session; %v", err))
        return nil
    }
} else {
    err := session.Touch(c.Response())
    if err != nil {
        c.Error(fmt.Errorf("could not touch session; %v", err))
        return nil
    }
}

defer session.Save(c.Response())

if err := next(c); err != nil {
    c.Error(err)
}

return nil

Hopefully this helps move the conversation forward.

alexedwards commented 5 years ago

This issue was fixed as part of the v2.0.0 release.