mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
296 lines
8.2 KiB
Go
296 lines
8.2 KiB
Go
|
// Memcache session support for Gorilla Web Toolkit,
|
||
|
// without Google App Engine dependency.
|
||
|
package gsm
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/base32"
|
||
|
"encoding/gob"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"github.com/bradfitz/gomemcache/memcache"
|
||
|
"github.com/gorilla/securecookie"
|
||
|
"github.com/gorilla/sessions"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// NewMemcacheStoreWithValueStorer returns a new MemcacheStore backed by a ValueStorer.
|
||
|
// You need to provide the memcache client and
|
||
|
// an optional prefix for the keys we store.
|
||
|
// A ValueStorer is used to store an encrypted sessionID. The encrypted sessionID is used to access
|
||
|
// memcache and get the session values.
|
||
|
func NewMemcacheStoreWithValueStorer(client *memcache.Client, valueStorer ValueStorer, keyPrefix string, keyPairs ...[]byte) *MemcacheStore {
|
||
|
|
||
|
if client == nil {
|
||
|
panic("Cannot have nil memcache client")
|
||
|
}
|
||
|
|
||
|
if valueStorer == nil {
|
||
|
panic("Cannot have nil ValueStorer")
|
||
|
}
|
||
|
|
||
|
return &MemcacheStore{
|
||
|
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||
|
Options: &sessions.Options{
|
||
|
Path: "/",
|
||
|
MaxAge: 86400 * 30,
|
||
|
},
|
||
|
KeyPrefix: keyPrefix,
|
||
|
Client: client,
|
||
|
StoreMethod: StoreMethodSecureCookie,
|
||
|
ValueStorer: valueStorer,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NewMemcacheStore returns a new MemcacheStore.
|
||
|
// You need to provide the memcache client and
|
||
|
// an optional prefix for the keys we store
|
||
|
func NewMemcacheStore(client *memcache.Client, keyPrefix string, keyPairs ...[]byte) *MemcacheStore {
|
||
|
return NewMemcacheStoreWithValueStorer(client, &CookieStorer{}, keyPrefix, keyPairs...)
|
||
|
}
|
||
|
|
||
|
type StoreMethod string
|
||
|
|
||
|
// take your pick on how to store the values in memcache
|
||
|
const (
|
||
|
StoreMethodSecureCookie = StoreMethod("securecookie") // security
|
||
|
StoreMethodGob = StoreMethod("gob") // speed
|
||
|
StoreMethodJson = StoreMethod("json") // simplicity; warning: only string keys allowed and rest of data must be JSON.Marshal compatible
|
||
|
)
|
||
|
|
||
|
// MemcacheStore stores sessions in memcache
|
||
|
//
|
||
|
type MemcacheStore struct {
|
||
|
Codecs []securecookie.Codec
|
||
|
Options *sessions.Options // default configuration
|
||
|
Client *memcache.Client
|
||
|
KeyPrefix string
|
||
|
Logging int // set to > 0 to enable logging (using log.Printf)
|
||
|
StoreMethod StoreMethod
|
||
|
ValueStorer ValueStorer
|
||
|
}
|
||
|
|
||
|
// MaxLength restricts the maximum length of new sessions to l.
|
||
|
// If l is 0 there is no limit to the size of a session, use with caution.
|
||
|
// The default for a new MemcacheStore is 4096.
|
||
|
func (s *MemcacheStore) MaxLength(l int) {
|
||
|
for _, c := range s.Codecs {
|
||
|
if codec, ok := c.(*securecookie.SecureCookie); ok {
|
||
|
codec.MaxLength(l)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get returns a session for the given name after adding it to the registry.
|
||
|
//
|
||
|
// See CookieStore.Get().
|
||
|
func (s *MemcacheStore) Get(r *http.Request, name string) (*sessions.Session, error) {
|
||
|
return sessions.GetRegistry(r).Get(s, name)
|
||
|
}
|
||
|
|
||
|
// New returns a session for the given name without adding it to the registry.
|
||
|
//
|
||
|
// See CookieStore.New().
|
||
|
func (s *MemcacheStore) New(r *http.Request, name string) (*sessions.Session, error) {
|
||
|
session := sessions.NewSession(s, name)
|
||
|
opts := *s.Options
|
||
|
session.Options = &opts
|
||
|
session.IsNew = true
|
||
|
var err error
|
||
|
if value, errCookie := s.ValueStorer.GetValueForSessionName(r, name); errCookie == nil {
|
||
|
err = securecookie.DecodeMulti(name, value, &session.ID, s.Codecs...)
|
||
|
if err == nil {
|
||
|
err = s.load(session)
|
||
|
if err == nil {
|
||
|
session.IsNew = false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return session, err
|
||
|
}
|
||
|
|
||
|
// Save adds a single session to the response.
|
||
|
func (s *MemcacheStore) Save(r *http.Request, w http.ResponseWriter,
|
||
|
session *sessions.Session) error {
|
||
|
if session.ID == "" {
|
||
|
// Because the ID is used in the filename, encode it to
|
||
|
// use alphanumeric characters only.
|
||
|
session.ID = strings.TrimRight(
|
||
|
base32.StdEncoding.EncodeToString(
|
||
|
securecookie.GenerateRandomKey(32)), "=")
|
||
|
}
|
||
|
if err := s.save(session); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
|
||
|
s.Codecs...)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if err := s.ValueStorer.SetValueForSessionName(w, session.Name(), encoded, session.Options); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// save writes encoded session.Values using the memcache client
|
||
|
func (s *MemcacheStore) save(session *sessions.Session) error {
|
||
|
|
||
|
key := s.KeyPrefix + session.ID
|
||
|
|
||
|
switch s.StoreMethod {
|
||
|
|
||
|
case StoreMethodSecureCookie:
|
||
|
|
||
|
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
|
||
|
s.Codecs...)
|
||
|
if err != nil {
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: set (method: securecookie, encoding error: %v)", err)
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = s.Client.Set(&memcache.Item{Key: key, Value: []byte(encoded), Expiration: int32(session.Options.MaxAge)})
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: set (method: securecookie, session name: %v, memcache key: %v, memcache value: %v, error: %v)", session.Name(), key, encoded, err)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
|
||
|
case StoreMethodGob:
|
||
|
|
||
|
buf := &bytes.Buffer{}
|
||
|
enc := gob.NewEncoder(buf)
|
||
|
if err := enc.Encode(session.Values); err != nil {
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: set (method: gob, encoding error: %v)", err)
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
bufbytes := buf.Bytes()
|
||
|
|
||
|
err := s.Client.Set(&memcache.Item{Key: key, Value: bufbytes, Expiration: int32(session.Options.MaxAge)})
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: set (method: gob, session name: %v, memcache key: %v, memcache value len: %v, error: %v)", session.Name(), key, len(bufbytes), err)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
|
||
|
case StoreMethodJson:
|
||
|
|
||
|
vals := make(map[string]interface{}, len(session.Values))
|
||
|
for k, v := range session.Values {
|
||
|
ks, ok := k.(string)
|
||
|
if !ok {
|
||
|
err := fmt.Errorf("Non-string key value, cannot jsonize: %v", k)
|
||
|
log.Printf("gorilla-sessions-memcache: set (method: json, encoding error: %v)", err)
|
||
|
return err
|
||
|
}
|
||
|
vals[ks] = v
|
||
|
}
|
||
|
|
||
|
bufbytes, err := json.Marshal(vals)
|
||
|
if err != nil {
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: set (method: json, encoding error: %v)", err)
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = s.Client.Set(&memcache.Item{Key: key, Value: bufbytes, Expiration: int32(session.Options.MaxAge)})
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: set (method: json, session name: %v, memcache key: %v, memcache value: %v, error: %v)", session.Name(), key, string(bufbytes), err)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
|
||
|
default:
|
||
|
panic("Unknown StoreMethod: " + string(s.StoreMethod))
|
||
|
}
|
||
|
|
||
|
panic("Unreachable")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// load reads a file and decodes its content into session.Values.
|
||
|
func (s *MemcacheStore) load(session *sessions.Session) error {
|
||
|
|
||
|
key := s.KeyPrefix + session.ID
|
||
|
|
||
|
it, err := s.Client.Get(key)
|
||
|
if s.Logging > 0 {
|
||
|
if s.StoreMethod == StoreMethodJson {
|
||
|
log.Printf("gorilla-sessions-memcache: get (method: %s, session name: %v, memcache key: %v, memcache value: %v, error: %v)", s.StoreMethod, session.Name(), key, string(it.Value), err)
|
||
|
} else {
|
||
|
log.Printf("gorilla-sessions-memcache: get (method: %s, session name: %v, memcache key: %v, memcache value len: %v, error: %v)", s.StoreMethod, session.Name(), key, len(it.Value), err)
|
||
|
}
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
switch s.StoreMethod {
|
||
|
|
||
|
case StoreMethodSecureCookie:
|
||
|
|
||
|
if err = securecookie.DecodeMulti(session.Name(), string(it.Value),
|
||
|
&session.Values, s.Codecs...); err != nil {
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: get (method: securecookie, decoding error: %v)", err)
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
|
||
|
case StoreMethodGob:
|
||
|
|
||
|
buf := bytes.NewBuffer(it.Value)
|
||
|
dec := gob.NewDecoder(buf)
|
||
|
|
||
|
err = dec.Decode(&session.Values)
|
||
|
if err != nil {
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: get (method: gob, decoding error: %v)", err)
|
||
|
}
|
||
|
}
|
||
|
return err
|
||
|
|
||
|
case StoreMethodJson:
|
||
|
|
||
|
vals := make(map[string]interface{})
|
||
|
|
||
|
err := json.Unmarshal(it.Value, &vals)
|
||
|
if err != nil {
|
||
|
if s.Logging > 0 {
|
||
|
log.Printf("gorilla-sessions-memcache: get (method: json, decoding error: %v)", err)
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
for k, v := range vals {
|
||
|
session.Values[k] = v
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
|
||
|
default:
|
||
|
panic("Unknown StoreMethod: " + string(s.StoreMethod))
|
||
|
|
||
|
}
|
||
|
|
||
|
panic("Unreachable")
|
||
|
return nil
|
||
|
}
|