diff --git a/sessions.go b/sessions.go index d6bfb6e..9cadd33 100644 --- a/sessions.go +++ b/sessions.go @@ -10,7 +10,7 @@ import ( "net/http" "time" - "github.com/gorilla/context" + "golang.org/x/net/context" ) // Default flashes key. @@ -90,8 +90,8 @@ func (s *Session) AddFlash(value interface{}, vars ...string) { // 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) +func (s *Session) Save(c context.Context, r *http.Request, w http.ResponseWriter) error { + return s.store.Save(c, r, w, s) } // Name returns the name used to register the session. @@ -112,24 +112,22 @@ type sessionInfo struct { e error } -// contextKey is the type used to store the registry in the context. -type contextKey int - // registryKey is the key used to store the registry in the context. -const registryKey contextKey = 0 +var registryKey = "registry key" -// GetRegistry returns a registry instance for the current request. -func GetRegistry(r *http.Request) *Registry { - registry := context.Get(r, registryKey) - if registry != nil { - return registry.(*Registry) - } +// RegistryWithContext returns a new context with a context created for a request. +func RegistryWithContext(parent context.Context, r *http.Request) context.Context { newRegistry := &Registry{ request: r, sessions: make(map[string]sessionInfo), } - context.Set(r, registryKey, newRegistry) - return newRegistry + return context.WithValue(parent, ®istryKey, newRegistry) +} + +// RegistryFromContext returns a registry previously encoded in Context. +func RegistryFromContext(c context.Context) (*Registry, bool) { + r, ok := c.Value(®istryKey).(*Registry) + return r, ok } // Registry stores sessions used during a request. @@ -154,14 +152,14 @@ func (s *Registry) Get(store Store, name string) (session *Session, err error) { } // Save saves all sessions registered for the current request. -func (s *Registry) Save(w http.ResponseWriter) error { +func (s *Registry) Save(c context.Context, w http.ResponseWriter) error { var errMulti MultiError for name, info := range s.sessions { session := info.s if session.store == nil { errMulti = append(errMulti, fmt.Errorf( "sessions: missing store for session %q", name)) - } else if err := session.store.Save(s.request, w, session); err != nil { + } else if err := session.store.Save(c, s.request, w, session); err != nil { errMulti = append(errMulti, fmt.Errorf( "sessions: error saving session %q -- %v", name, err)) } @@ -179,8 +177,12 @@ func init() { } // Save saves all sessions used during the current request. -func Save(r *http.Request, w http.ResponseWriter) error { - return GetRegistry(r).Save(w) +func Save(c context.Context, r *http.Request, w http.ResponseWriter) error { + reg, ok := RegistryFromContext(c) + if !ok { + return ErrNoRegistry + } + return reg.Save(c, w) } // NewCookie returns an http.Cookie with the options set. It also sets diff --git a/sessions_test.go b/sessions_test.go index fcc69eb..d49e025 100644 --- a/sessions_test.go +++ b/sessions_test.go @@ -9,6 +9,8 @@ import ( "encoding/gob" "net/http" "testing" + + "golang.org/x/net/context" ) // ---------------------------------------------------------------------------- @@ -88,8 +90,9 @@ func TestFlashes(t *testing.T) { req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) rsp = NewRecorder() + ctx := RegistryWithContext(context.TODO(), req) // Get a session. - if session, err = store.Get(req, "session-key"); err != nil { + if session, err = store.Get(ctx, req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Get a flash. @@ -103,7 +106,7 @@ func TestFlashes(t *testing.T) { // Custom key. session.AddFlash("baz", "custom_key") // Save. - if err = Save(req, rsp); err != nil { + if err = Save(ctx, req, rsp); err != nil { t.Fatalf("Error saving session: %v", err) } hdr = rsp.Header() @@ -117,8 +120,9 @@ func TestFlashes(t *testing.T) { req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) req.Header.Add("Cookie", cookies[0]) rsp = NewRecorder() + ctx = RegistryWithContext(context.TODO(), req) // Get a session. - if session, err = store.Get(req, "session-key"); err != nil { + if session, err = store.Get(ctx, req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Check all saved values. @@ -150,8 +154,9 @@ func TestFlashes(t *testing.T) { req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) rsp = NewRecorder() + ctx = RegistryWithContext(context.TODO(), req) // Get a session. - if session, err = store.Get(req, "session-key"); err != nil { + if session, err = store.Get(ctx, req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Get a flash. @@ -162,7 +167,7 @@ func TestFlashes(t *testing.T) { // Add some flashes. session.AddFlash(&FlashMessage{42, "foo"}) // Save. - if err = Save(req, rsp); err != nil { + if err = Save(ctx, req, rsp); err != nil { t.Fatalf("Error saving session: %v", err) } hdr = rsp.Header() @@ -177,8 +182,9 @@ func TestFlashes(t *testing.T) { req, _ = http.NewRequest("GET", "http://localhost:8080/", nil) req.Header.Add("Cookie", cookies[0]) rsp = NewRecorder() + ctx = RegistryWithContext(context.TODO(), req) // Get a session. - if session, err = store.Get(req, "session-key"); err != nil { + if session, err = store.Get(ctx, req, "session-key"); err != nil { t.Fatalf("Error getting session: %v", err) } // Check all saved values. diff --git a/store.go b/store.go index e037e05..2750314 100644 --- a/store.go +++ b/store.go @@ -6,6 +6,7 @@ package sessions import ( "encoding/base32" + "fmt" "io/ioutil" "net/http" "os" @@ -14,6 +15,7 @@ import ( "sync" "github.com/gorilla/securecookie" + "golang.org/x/net/context" ) // Store is an interface for custom session stores. @@ -21,7 +23,7 @@ import ( // See CookieStore and FilesystemStore for examples. type Store interface { // Get should return a cached session. - Get(r *http.Request, name string) (*Session, error) + Get(ctx context.Context, r *http.Request, name string) (*Session, error) // New should create and return a new session. // @@ -30,7 +32,7 @@ type Store interface { New(r *http.Request, name string) (*Session, error) // Save should persist session to the underlying store implementation. - Save(r *http.Request, w http.ResponseWriter, s *Session) error + Save(ctx context.Context, r *http.Request, w http.ResponseWriter, s *Session) error } // CookieStore ---------------------------------------------------------------- @@ -69,6 +71,8 @@ type CookieStore struct { Options *Options // default configuration } +var ErrNoRegistry = fmt.Errorf("must provide context with Registry (RegistryWithContext method as middleware)") + // Get returns a session for the given name after adding it to the registry. // // It returns a new session if the sessions doesn't exist. Access IsNew on @@ -76,8 +80,12 @@ type CookieStore struct { // // It returns a new session and an error if the session exists but could // not be decoded. -func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) { - return GetRegistry(r).Get(s, name) +func (s *CookieStore) Get(ctx context.Context, r *http.Request, name string) (*Session, error) { + reg, ok := RegistryFromContext(ctx) + if !ok { + return nil, ErrNoRegistry + } + return reg.Get(s, name) } // New returns a session for the given name without adding it to the registry. @@ -102,7 +110,7 @@ func (s *CookieStore) New(r *http.Request, name string) (*Session, error) { } // Save adds a single session to the response. -func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter, +func (s *CookieStore) Save(ctx context.Context, r *http.Request, w http.ResponseWriter, session *Session) error { encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.Codecs...) @@ -179,8 +187,12 @@ func (s *FilesystemStore) MaxLength(l int) { // Get returns a session for the given name after adding it to the registry. // // See CookieStore.Get(). -func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) { - return GetRegistry(r).Get(s, name) +func (s *FilesystemStore) Get(ctx context.Context, r *http.Request, name string) (*Session, error) { + reg, ok := RegistryFromContext(ctx) + if !ok { + return nil, ErrNoRegistry + } + return reg.Get(s, name) } // New returns a session for the given name without adding it to the registry. @@ -205,7 +217,7 @@ func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) { } // Save adds a single session to the response. -func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter, +func (s *FilesystemStore) Save(ctx context.Context, r *http.Request, w http.ResponseWriter, session *Session) error { if session.ID == "" { // Because the ID is used in the filename, encode it to diff --git a/store_test.go b/store_test.go index 022acba..e5129e2 100644 --- a/store_test.go +++ b/store_test.go @@ -5,6 +5,8 @@ import ( "net/http" "net/http/httptest" "testing" + + "golang.org/x/net/context" ) // Test for GH-8 for CookieStore @@ -58,15 +60,17 @@ func TestGH2MaxLength(t *testing.T) { } w := httptest.NewRecorder() + ctx := context.TODO() session, err := store.New(req, "my session") session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2)) - err = session.Save(req, w) + + err = session.Save(ctx, req, w) if err == nil { t.Fatal("expected an error, got nil") } store.MaxLength(4096 * 3) // A bit more than the value size to account for encoding overhead. - err = session.Save(req, w) + err = session.Save(ctx, req, w) if err != nil { t.Fatal("failed to Save:", err) }