Procházet zdrojové kódy

MySQL session: fixed problem using mysql as session store, Fixes #1681

Torkel Ödegaard před 10 roky
rodič
revize
7eb45e1799
28 změnil soubory, kde provedl 1509 přidání a 641 odebrání
  1. 2 0
      CHANGELOG.md
  2. 1 1
      Godeps/Godeps.json
  3. 2 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/.gitignore
  4. 5 1
      Godeps/_workspace/src/github.com/macaron-contrib/session/README.md
  5. 12 28
      Godeps/_workspace/src/github.com/macaron-contrib/session/file.go
  6. 98 59
      Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis.go
  7. 1 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis.goconvey
  8. 105 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis_test.go
  9. 74 96
      Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache.go
  10. 1 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache.goconvey
  11. 107 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache_test.go
  12. 18 18
      Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go
  13. 71 77
      Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql.go
  14. 1 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql.goconvey
  15. 138 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql_test.go
  16. 203 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb.go
  17. 1 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb.goconvey
  18. 105 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb_test.go
  19. 196 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres.go
  20. 1 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres.goconvey
  21. 138 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres_test.go
  22. 0 211
      Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgresql.go
  23. 98 97
      Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis.go
  24. 1 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis.goconvey
  25. 107 0
      Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis_test.go
  26. 17 27
      Godeps/_workspace/src/github.com/macaron-contrib/session/session.go
  27. 1 1
      Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go
  28. 5 25
      Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go

+ 2 - 0
CHANGELOG.md

@@ -1,5 +1,7 @@
 # 2.0.0-RC1  (unreleased)
 # 2.0.0-RC1  (unreleased)
 
 
+**FIxes**
+- [Issue #1681](https://github.com/grafana/grafana/issues/1681). MySQL session: fixed problem using mysql as session store
 - [Issue #1671](https://github.com/grafana/grafana/issues/1671). Data sources: Fixed issue with changing default data source (should not require full page load to take effect, now fixed)
 - [Issue #1671](https://github.com/grafana/grafana/issues/1671). Data sources: Fixed issue with changing default data source (should not require full page load to take effect, now fixed)
 
 
 # 2.0.0-Beta1 (2015-03-30)
 # 2.0.0-Beta1 (2015-03-30)

+ 1 - 1
Godeps/Godeps.json

@@ -47,7 +47,7 @@
 		},
 		},
 		{
 		{
 			"ImportPath": "github.com/macaron-contrib/session",
 			"ImportPath": "github.com/macaron-contrib/session",
-			"Rev": "65b8817c40cb5bdce08673a15fd2a648c2ba0e16"
+			"Rev": "31e841d95c7302b9ac456c830ea2d6dfcef4f84a"
 		},
 		},
 		{
 		{
 			"ImportPath": "github.com/mattn/go-sqlite3",
 			"ImportPath": "github.com/mattn/go-sqlite3",

+ 2 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/.gitignore

@@ -0,0 +1,2 @@
+ledis/tmp.db
+nodb/tmp.db

+ 5 - 1
Godeps/_workspace/src/github.com/macaron-contrib/session/README.md

@@ -1,7 +1,7 @@
 session [![Build Status](https://drone.io/github.com/macaron-contrib/session/status.png)](https://drone.io/github.com/macaron-contrib/session/latest) [![](http://gocover.io/_badge/github.com/macaron-contrib/session)](http://gocover.io/github.com/macaron-contrib/session)
 session [![Build Status](https://drone.io/github.com/macaron-contrib/session/status.png)](https://drone.io/github.com/macaron-contrib/session/latest) [![](http://gocover.io/_badge/github.com/macaron-contrib/session)](http://gocover.io/github.com/macaron-contrib/session)
 =======
 =======
 
 
-Middleware session provides session management for [Macaron](https://github.com/Unknwon/macaron). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase and Ledis.
+Middleware session provides session management for [Macaron](https://github.com/Unknwon/macaron). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase, Ledis and Nodb.
 
 
 ### Installation
 ### Installation
 
 
@@ -12,6 +12,10 @@ Middleware session provides session management for [Macaron](https://github.com/
 - [API Reference](https://gowalker.org/github.com/macaron-contrib/session)
 - [API Reference](https://gowalker.org/github.com/macaron-contrib/session)
 - [Documentation](http://macaron.gogs.io/docs/middlewares/session)
 - [Documentation](http://macaron.gogs.io/docs/middlewares/session)
 
 
+## Credits
+
+This package is forked from [beego/session](https://github.com/astaxie/beego/tree/master/session) with reconstruction(over 80%).
+
 ## License
 ## License
 
 
 This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
 This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.

+ 12 - 28
Godeps/_workspace/src/github.com/macaron-contrib/session/file.go

@@ -28,17 +28,17 @@ import (
 	"github.com/Unknwon/com"
 	"github.com/Unknwon/com"
 )
 )
 
 
-// FileSessionStore represents a file session store implementation.
-type FileSessionStore struct {
+// FileStore represents a file session store implementation.
+type FileStore struct {
 	p    *FileProvider
 	p    *FileProvider
 	sid  string
 	sid  string
 	lock sync.RWMutex
 	lock sync.RWMutex
 	data map[interface{}]interface{}
 	data map[interface{}]interface{}
 }
 }
 
 
-// NewFileSessionStore creates and returns a file session store.
-func NewFileSessionStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileSessionStore {
-	return &FileSessionStore{
+// NewFileStore creates and returns a file session store.
+func NewFileStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileStore {
+	return &FileStore{
 		p:    p,
 		p:    p,
 		sid:  sid,
 		sid:  sid,
 		data: kv,
 		data: kv,
@@ -46,7 +46,7 @@ func NewFileSessionStore(p *FileProvider, sid string, kv map[interface{}]interfa
 }
 }
 
 
 // Set sets value to given key in session.
 // Set sets value to given key in session.
-func (s *FileSessionStore) Set(key, val interface{}) error {
+func (s *FileStore) Set(key, val interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -55,7 +55,7 @@ func (s *FileSessionStore) Set(key, val interface{}) error {
 }
 }
 
 
 // Get gets value by given key in session.
 // Get gets value by given key in session.
-func (s *FileSessionStore) Get(key interface{}) interface{} {
+func (s *FileStore) Get(key interface{}) interface{} {
 	s.lock.RLock()
 	s.lock.RLock()
 	defer s.lock.RUnlock()
 	defer s.lock.RUnlock()
 
 
@@ -63,7 +63,7 @@ func (s *FileSessionStore) Get(key interface{}) interface{} {
 }
 }
 
 
 // Delete delete a key from session.
 // Delete delete a key from session.
-func (s *FileSessionStore) Delete(key interface{}) error {
+func (s *FileStore) Delete(key interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -72,12 +72,12 @@ func (s *FileSessionStore) Delete(key interface{}) error {
 }
 }
 
 
 // ID returns current session ID.
 // ID returns current session ID.
-func (s *FileSessionStore) ID() string {
+func (s *FileStore) ID() string {
 	return s.sid
 	return s.sid
 }
 }
 
 
 // Release releases resource and save data to provider.
 // Release releases resource and save data to provider.
-func (s *FileSessionStore) Release() error {
+func (s *FileStore) Release() error {
 	data, err := EncodeGob(s.data)
 	data, err := EncodeGob(s.data)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -87,7 +87,7 @@ func (s *FileSessionStore) Release() error {
 }
 }
 
 
 // Flush deletes all session data.
 // Flush deletes all session data.
-func (s *FileSessionStore) Flush() error {
+func (s *FileStore) Flush() error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -97,7 +97,6 @@ func (s *FileSessionStore) Flush() error {
 
 
 // FileProvider represents a file session provider implementation.
 // FileProvider represents a file session provider implementation.
 type FileProvider struct {
 type FileProvider struct {
-	lock        sync.RWMutex
 	maxlifetime int64
 	maxlifetime int64
 	rootPath    string
 	rootPath    string
 }
 }
@@ -115,9 +114,6 @@ func (p *FileProvider) filepath(sid string) string {
 
 
 // Read returns raw session store by session ID.
 // Read returns raw session store by session ID.
 func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
 func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-
 	filename := p.filepath(sid)
 	filename := p.filepath(sid)
 	if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil {
 	if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil {
 		return nil, err
 		return nil, err
@@ -151,22 +147,16 @@ func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
-	return NewFileSessionStore(p, sid, kv), nil
+	return NewFileStore(p, sid, kv), nil
 }
 }
 
 
 // Exist returns true if session with given ID exists.
 // Exist returns true if session with given ID exists.
 func (p *FileProvider) Exist(sid string) bool {
 func (p *FileProvider) Exist(sid string) bool {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-
 	return com.IsFile(p.filepath(sid))
 	return com.IsFile(p.filepath(sid))
 }
 }
 
 
 // Destory deletes a session by session ID.
 // Destory deletes a session by session ID.
 func (p *FileProvider) Destory(sid string) error {
 func (p *FileProvider) Destory(sid string) error {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-
 	return os.Remove(p.filepath(sid))
 	return os.Remove(p.filepath(sid))
 }
 }
 
 
@@ -201,12 +191,9 @@ func (p *FileProvider) regenerate(oldsid, sid string) (err error) {
 
 
 // Regenerate regenerates a session store from old session ID to new one.
 // Regenerate regenerates a session store from old session ID to new one.
 func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) {
 func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) {
-	p.lock.Lock()
 	if err := p.regenerate(oldsid, sid); err != nil {
 	if err := p.regenerate(oldsid, sid); err != nil {
-		p.lock.Unlock()
 		return nil, err
 		return nil, err
 	}
 	}
-	p.lock.Unlock()
 
 
 	return p.Read(sid)
 	return p.Read(sid)
 }
 }
@@ -236,9 +223,6 @@ func (p *FileProvider) GC() {
 		return
 		return
 	}
 	}
 
 
-	p.lock.Lock()
-	defer p.lock.Unlock()
-
 	if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
 	if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err

+ 98 - 59
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis.go

@@ -16,26 +16,39 @@
 package session
 package session
 
 
 import (
 import (
+	"fmt"
+	"strings"
 	"sync"
 	"sync"
 
 
+	"github.com/Unknwon/com"
 	"github.com/siddontang/ledisdb/config"
 	"github.com/siddontang/ledisdb/config"
 	"github.com/siddontang/ledisdb/ledis"
 	"github.com/siddontang/ledisdb/ledis"
+	"gopkg.in/ini.v1"
 
 
 	"github.com/macaron-contrib/session"
 	"github.com/macaron-contrib/session"
 )
 )
 
 
-var c *ledis.DB
+// LedisStore represents a ledis session store implementation.
+type LedisStore struct {
+	c      *ledis.DB
+	sid    string
+	expire int64
+	lock   sync.RWMutex
+	data   map[interface{}]interface{}
+}
 
 
-// LedisSessionStore represents a ledis session store implementation.
-type LedisSessionStore struct {
-	sid         string
-	lock        sync.RWMutex
-	data        map[interface{}]interface{}
-	maxlifetime int64
+// NewLedisStore creates and returns a ledis session store.
+func NewLedisStore(c *ledis.DB, sid string, expire int64, kv map[interface{}]interface{}) *LedisStore {
+	return &LedisStore{
+		c:      c,
+		expire: expire,
+		sid:    sid,
+		data:   kv,
+	}
 }
 }
 
 
 // Set sets value to given key in session.
 // Set sets value to given key in session.
-func (s *LedisSessionStore) Set(key, val interface{}) error {
+func (s *LedisStore) Set(key, val interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -44,7 +57,7 @@ func (s *LedisSessionStore) Set(key, val interface{}) error {
 }
 }
 
 
 // Get gets value by given key in session.
 // Get gets value by given key in session.
-func (s *LedisSessionStore) Get(key interface{}) interface{} {
+func (s *LedisStore) Get(key interface{}) interface{} {
 	s.lock.RLock()
 	s.lock.RLock()
 	defer s.lock.RUnlock()
 	defer s.lock.RUnlock()
 
 
@@ -52,7 +65,7 @@ func (s *LedisSessionStore) Get(key interface{}) interface{} {
 }
 }
 
 
 // Delete delete a key from session.
 // Delete delete a key from session.
-func (s *LedisSessionStore) Delete(key interface{}) error {
+func (s *LedisStore) Delete(key interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -61,25 +74,26 @@ func (s *LedisSessionStore) Delete(key interface{}) error {
 }
 }
 
 
 // ID returns current session ID.
 // ID returns current session ID.
-func (s *LedisSessionStore) ID() string {
+func (s *LedisStore) ID() string {
 	return s.sid
 	return s.sid
 }
 }
 
 
 // Release releases resource and save data to provider.
 // Release releases resource and save data to provider.
-func (s *LedisSessionStore) Release() error {
+func (s *LedisStore) Release() error {
 	data, err := session.EncodeGob(s.data)
 	data, err := session.EncodeGob(s.data)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	if err = c.Set([]byte(s.sid), data); err != nil {
+
+	if err = s.c.Set([]byte(s.sid), data); err != nil {
 		return err
 		return err
 	}
 	}
-	_, err = c.Expire([]byte(s.sid), s.maxlifetime)
+	_, err = s.c.Expire([]byte(s.sid), s.expire)
 	return err
 	return err
 }
 }
 
 
 // Flush deletes all session data.
 // Flush deletes all session data.
-func (s *LedisSessionStore) Flush() error {
+func (s *LedisStore) Flush() error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -89,30 +103,54 @@ func (s *LedisSessionStore) Flush() error {
 
 
 // LedisProvider represents a ledis session provider implementation.
 // LedisProvider represents a ledis session provider implementation.
 type LedisProvider struct {
 type LedisProvider struct {
-	maxlifetime int64
-	savePath    string
-}
-
-// Init initializes memory session provider.
-func (p *LedisProvider) Init(maxlifetime int64, savePath string) error {
-	p.maxlifetime = maxlifetime
-	p.savePath = savePath
-	cfg := new(config.Config)
-	cfg.DataDir = p.savePath
-	var err error
-	nowLedis, err := ledis.Open(cfg)
-	c, err = nowLedis.Select(0)
+	c      *ledis.DB
+	expire int64
+}
+
+// Init initializes ledis session provider.
+// configs: data_dir=./app.db,db=0
+func (p *LedisProvider) Init(expire int64, configs string) error {
+	p.expire = expire
+
+	cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1)))
 	if err != nil {
 	if err != nil {
-		println(err)
-		return nil
+		return err
 	}
 	}
-	return nil
+
+	db := 0
+	opt := new(config.Config)
+	for k, v := range cfg.Section("").KeysHash() {
+		switch k {
+		case "data_dir":
+			opt.DataDir = v
+		case "db":
+			db = com.StrTo(v).MustInt()
+		default:
+			return fmt.Errorf("session/ledis: unsupported option '%s'", k)
+		}
+	}
+
+	l, err := ledis.Open(opt)
+	if err != nil {
+		return fmt.Errorf("session/ledis: error opening db: %v", err)
+	}
+	p.c, err = l.Select(db)
+	return err
 }
 }
 
 
 // Read returns raw session store by session ID.
 // Read returns raw session store by session ID.
 func (p *LedisProvider) Read(sid string) (session.RawStore, error) {
 func (p *LedisProvider) Read(sid string) (session.RawStore, error) {
-	kvs, err := c.Get([]byte(sid))
+	if !p.Exist(sid) {
+		if err := p.c.Set([]byte(sid), []byte("")); err != nil {
+			return nil, err
+		}
+	}
+
 	var kv map[interface{}]interface{}
 	var kv map[interface{}]interface{}
+	kvs, err := p.c.Get([]byte(sid))
+	if err != nil {
+		return nil, err
+	}
 	if len(kvs) == 0 {
 	if len(kvs) == 0 {
 		kv = make(map[interface{}]interface{})
 		kv = make(map[interface{}]interface{})
 	} else {
 	} else {
@@ -121,41 +159,40 @@ func (p *LedisProvider) Read(sid string) (session.RawStore, error) {
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
-	ls := &LedisSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
-	return ls, nil
+
+	return NewLedisStore(p.c, sid, p.expire, kv), nil
 }
 }
 
 
 // Exist returns true if session with given ID exists.
 // Exist returns true if session with given ID exists.
 func (p *LedisProvider) Exist(sid string) bool {
 func (p *LedisProvider) Exist(sid string) bool {
-	count, _ := c.Exists([]byte(sid))
-	if count == 0 {
-		return false
-	} else {
-		return true
-	}
+	count, err := p.c.Exists([]byte(sid))
+	return err == nil && count > 0
 }
 }
 
 
 // Destory deletes a session by session ID.
 // Destory deletes a session by session ID.
 func (p *LedisProvider) Destory(sid string) error {
 func (p *LedisProvider) Destory(sid string) error {
-	_, err := c.Del([]byte(sid))
+	_, err := p.c.Del([]byte(sid))
 	return err
 	return err
 }
 }
 
 
 // Regenerate regenerates a session store from old session ID to new one.
 // Regenerate regenerates a session store from old session ID to new one.
-func (p *LedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
-	count, _ := c.Exists([]byte(sid))
-	if count == 0 {
-		// oldsid doesn't exists, set the new sid directly
-		// ignore error here, since if it return error
-		// the existed value will be 0
-		c.Set([]byte(sid), []byte(""))
-		c.Expire([]byte(sid), p.maxlifetime)
-	} else {
-		data, _ := c.Get([]byte(oldsid))
-		c.Set([]byte(sid), data)
-		c.Expire([]byte(sid), p.maxlifetime)
+func (p *LedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
 	}
 	}
-	kvs, err := c.Get([]byte(sid))
+
+	kvs := make([]byte, 0)
+	if p.Exist(oldsid) {
+		if kvs, err = p.c.Get([]byte(oldsid)); err != nil {
+			return nil, err
+		} else if _, err = p.c.Del([]byte(oldsid)); err != nil {
+			return nil, err
+		}
+	}
+	if err = p.c.SetEX([]byte(sid), p.expire, kvs); err != nil {
+		return nil, err
+	}
+
 	var kv map[interface{}]interface{}
 	var kv map[interface{}]interface{}
 	if len(kvs) == 0 {
 	if len(kvs) == 0 {
 		kv = make(map[interface{}]interface{})
 		kv = make(map[interface{}]interface{})
@@ -165,18 +202,20 @@ func (p *LedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error)
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
-	ls := &LedisSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
-	return ls, nil
+
+	return NewLedisStore(p.c, sid, p.expire, kv), nil
 }
 }
 
 
 // Count counts and returns number of sessions.
 // Count counts and returns number of sessions.
 func (p *LedisProvider) Count() int {
 func (p *LedisProvider) Count() int {
-	// FIXME
-	return 0
+	// FIXME: how come this library does not have DbSize() method?
+	return -1
 }
 }
 
 
 // GC calls GC to clean expired sessions.
 // GC calls GC to clean expired sessions.
-func (p *LedisProvider) GC() {}
+func (p *LedisProvider) GC() {
+	// FIXME: wtf???
+}
 
 
 func init() {
 func init() {
 	session.Register("ledis", &LedisProvider{})
 	session.Register("ledis", &LedisProvider{})

+ 1 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis.goconvey

@@ -0,0 +1 @@
+ignore

+ 105 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis_test.go

@@ -0,0 +1,105 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/Unknwon/macaron"
+	. "github.com/smartystreets/goconvey/convey"
+
+	"github.com/macaron-contrib/session"
+)
+
+func Test_LedisProvider(t *testing.T) {
+	Convey("Test ledis session provider", t, func() {
+		opt := session.Options{
+			Provider:       "ledis",
+			ProviderConfig: "data_dir=./tmp.db",
+		}
+
+		Convey("Basic operation", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				sess.Set("uname", "unknwon")
+			})
+			m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := raw.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+			})
+			m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
+				sid := sess.ID()
+				So(sid, ShouldNotBeEmpty)
+
+				raw, err := sess.Read(sid)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := sess.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+
+				So(sess.Delete("uname"), ShouldBeNil)
+				So(sess.Get("uname"), ShouldBeNil)
+
+				So(sess.Destory(ctx), ShouldBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+
+			cookie := resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/reg", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+
+			cookie = resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/get", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+
+			Convey("Regenrate empty session", func() {
+				m.Get("/empty", func(ctx *macaron.Context, sess session.Store) {
+					raw, err := sess.RegenerateId(ctx)
+					So(err, ShouldBeNil)
+					So(raw, ShouldNotBeNil)
+				})
+
+				resp = httptest.NewRecorder()
+				req, err = http.NewRequest("GET", "/empty", nil)
+				So(err, ShouldBeNil)
+				req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
+				m.ServeHTTP(resp, req)
+			})
+		})
+	})
+}

+ 74 - 96
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache.go

@@ -16,6 +16,7 @@
 package session
 package session
 
 
 import (
 import (
+	"fmt"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 
 
@@ -24,20 +25,35 @@ import (
 	"github.com/macaron-contrib/session"
 	"github.com/macaron-contrib/session"
 )
 )
 
 
-var (
-	client *memcache.Client
-)
+// MemcacheStore represents a memcache session store implementation.
+type MemcacheStore struct {
+	c      *memcache.Client
+	sid    string
+	expire int32
+	lock   sync.RWMutex
+	data   map[interface{}]interface{}
+}
+
+// NewMemcacheStore creates and returns a memcache session store.
+func NewMemcacheStore(c *memcache.Client, sid string, expire int32, kv map[interface{}]interface{}) *MemcacheStore {
+	return &MemcacheStore{
+		c:      c,
+		sid:    sid,
+		expire: expire,
+		data:   kv,
+	}
+}
 
 
-// MemcacheSessionStore represents a memcache session store implementation.
-type MemcacheSessionStore struct {
-	sid         string
-	lock        sync.RWMutex
-	data        map[interface{}]interface{}
-	maxlifetime int64
+func NewItem(sid string, data []byte, expire int32) *memcache.Item {
+	return &memcache.Item{
+		Key:        sid,
+		Value:      data,
+		Expiration: expire,
+	}
 }
 }
 
 
 // Set sets value to given key in session.
 // Set sets value to given key in session.
-func (s *MemcacheSessionStore) Set(key, val interface{}) error {
+func (s *MemcacheStore) Set(key, val interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -46,7 +62,7 @@ func (s *MemcacheSessionStore) Set(key, val interface{}) error {
 }
 }
 
 
 // Get gets value by given key in session.
 // Get gets value by given key in session.
-func (s *MemcacheSessionStore) Get(key interface{}) interface{} {
+func (s *MemcacheStore) Get(key interface{}) interface{} {
 	s.lock.RLock()
 	s.lock.RLock()
 	defer s.lock.RUnlock()
 	defer s.lock.RUnlock()
 
 
@@ -54,7 +70,7 @@ func (s *MemcacheSessionStore) Get(key interface{}) interface{} {
 }
 }
 
 
 // Delete delete a key from session.
 // Delete delete a key from session.
-func (s *MemcacheSessionStore) Delete(key interface{}) error {
+func (s *MemcacheStore) Delete(key interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -63,26 +79,22 @@ func (s *MemcacheSessionStore) Delete(key interface{}) error {
 }
 }
 
 
 // ID returns current session ID.
 // ID returns current session ID.
-func (s *MemcacheSessionStore) ID() string {
+func (s *MemcacheStore) ID() string {
 	return s.sid
 	return s.sid
 }
 }
 
 
 // Release releases resource and save data to provider.
 // Release releases resource and save data to provider.
-func (s *MemcacheSessionStore) Release() error {
+func (s *MemcacheStore) Release() error {
 	data, err := session.EncodeGob(s.data)
 	data, err := session.EncodeGob(s.data)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	return client.Set(&memcache.Item{
-		Key:        s.sid,
-		Value:      data,
-		Expiration: int32(s.maxlifetime),
-	})
+	return s.c.Set(NewItem(s.sid, data, s.expire))
 }
 }
 
 
 // Flush deletes all session data.
 // Flush deletes all session data.
-func (s *MemcacheSessionStore) Flush() error {
+func (s *MemcacheStore) Flush() error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -90,43 +102,33 @@ func (s *MemcacheSessionStore) Flush() error {
 	return nil
 	return nil
 }
 }
 
 
-// MemProvider represents a memcache session provider implementation.
-type MemProvider struct {
-	maxlifetime int64
-	conninfo    []string
-	poolsize    int
-	password    string
-}
-
-// Init initializes memory session provider.
-// connStrs can be multiple connection strings separate by ;
-// e.g. 127.0.0.1:9090
-func (p *MemProvider) Init(maxlifetime int64, connStrs string) error {
-	p.maxlifetime = maxlifetime
-	p.conninfo = strings.Split(connStrs, ";")
-	client = memcache.New(p.conninfo...)
-	return nil
+// MemcacheProvider represents a memcache session provider implementation.
+type MemcacheProvider struct {
+	c      *memcache.Client
+	expire int32
 }
 }
 
 
-func (p *MemProvider) connectInit() error {
-	client = memcache.New(p.conninfo...)
+// Init initializes memcache session provider.
+// connStrs: 127.0.0.1:9090;127.0.0.1:9091
+func (p *MemcacheProvider) Init(expire int64, connStrs string) error {
+	p.expire = int32(expire)
+	p.c = memcache.New(strings.Split(connStrs, ";")...)
 	return nil
 	return nil
 }
 }
 
 
 // Read returns raw session store by session ID.
 // Read returns raw session store by session ID.
-func (p *MemProvider) Read(sid string) (session.RawStore, error) {
-	if client == nil {
-		if err := p.connectInit(); err != nil {
+func (p *MemcacheProvider) Read(sid string) (session.RawStore, error) {
+	if !p.Exist(sid) {
+		if err := p.c.Set(NewItem(sid, []byte(""), p.expire)); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
 
 
-	item, err := client.Get(sid)
+	var kv map[interface{}]interface{}
+	item, err := p.c.Get(sid)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-
-	var kv map[interface{}]interface{}
 	if len(item.Value) == 0 {
 	if len(item.Value) == 0 {
 		kv = make(map[interface{}]interface{})
 		kv = make(map[interface{}]interface{})
 	} else {
 	} else {
@@ -136,86 +138,62 @@ func (p *MemProvider) Read(sid string) (session.RawStore, error) {
 		}
 		}
 	}
 	}
 
 
-	rs := &MemcacheSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
-	return rs, nil
+	return NewMemcacheStore(p.c, sid, p.expire, kv), nil
 }
 }
 
 
 // Exist returns true if session with given ID exists.
 // Exist returns true if session with given ID exists.
-func (p *MemProvider) Exist(sid string) bool {
-	if client == nil {
-		if err := p.connectInit(); err != nil {
-			return false
-		}
-	}
-
-	if item, err := client.Get(sid); err != nil || len(item.Value) == 0 {
-		return false
-	} else {
-		return true
-	}
+func (p *MemcacheProvider) Exist(sid string) bool {
+	_, err := p.c.Get(sid)
+	return err == nil
 }
 }
 
 
 // Destory deletes a session by session ID.
 // Destory deletes a session by session ID.
-func (p *MemProvider) Destory(sid string) error {
-	if client == nil {
-		if err := p.connectInit(); err != nil {
-			return err
-		}
-	}
-
-	return client.Delete(sid)
+func (p *MemcacheProvider) Destory(sid string) error {
+	return p.c.Delete(sid)
 }
 }
 
 
 // Regenerate regenerates a session store from old session ID to new one.
 // Regenerate regenerates a session store from old session ID to new one.
-func (p *MemProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
-	if client == nil {
-		if err := p.connectInit(); err != nil {
-			return nil, err
-		}
+func (p *MemcacheProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
 	}
 	}
 
 
-	var contain []byte
-	if item, err := client.Get(sid); err != nil || len(item.Value) == 0 {
-		// oldsid doesn't exists, set the new sid directly
-		// ignore error here, since if it return error
-		// the existed value will be 0
-		item.Key = sid
-		item.Value = []byte("")
-		item.Expiration = int32(p.maxlifetime)
-		client.Set(item)
-	} else {
-		client.Delete(oldsid)
+	item := NewItem(sid, []byte(""), p.expire)
+	if p.Exist(oldsid) {
+		item, err = p.c.Get(oldsid)
+		if err != nil {
+			return nil, err
+		} else if err = p.c.Delete(oldsid); err != nil {
+			return nil, err
+		}
 		item.Key = sid
 		item.Key = sid
-		item.Value = item.Value
-		item.Expiration = int32(p.maxlifetime)
-		client.Set(item)
-		contain = item.Value
+	}
+	if err = p.c.Set(item); err != nil {
+		return nil, err
 	}
 	}
 
 
 	var kv map[interface{}]interface{}
 	var kv map[interface{}]interface{}
-	if len(contain) == 0 {
+	if len(item.Value) == 0 {
 		kv = make(map[interface{}]interface{})
 		kv = make(map[interface{}]interface{})
 	} else {
 	} else {
-		var err error
-		kv, err = session.DecodeGob(contain)
+		kv, err = session.DecodeGob(item.Value)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
 
 
-	rs := &MemcacheSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
-	return rs, nil
+	return NewMemcacheStore(p.c, sid, p.expire, kv), nil
 }
 }
 
 
 // Count counts and returns number of sessions.
 // Count counts and returns number of sessions.
-func (p *MemProvider) Count() int {
-	// FIXME
-	return 0
+func (p *MemcacheProvider) Count() int {
+	// FIXME: how come this library does not have Stats method?
+	return -1
 }
 }
 
 
 // GC calls GC to clean expired sessions.
 // GC calls GC to clean expired sessions.
-func (p *MemProvider) GC() {}
+func (p *MemcacheProvider) GC() {}
 
 
 func init() {
 func init() {
-	session.Register("memcache", &MemProvider{})
+	session.Register("memcache", &MemcacheProvider{})
 }
 }

+ 1 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache.goconvey

@@ -0,0 +1 @@
+ignore

+ 107 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache_test.go

@@ -0,0 +1,107 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/Unknwon/macaron"
+	. "github.com/smartystreets/goconvey/convey"
+
+	"github.com/macaron-contrib/session"
+)
+
+func Test_MemcacheProvider(t *testing.T) {
+	Convey("Test memcache session provider", t, func() {
+		opt := session.Options{
+			Provider:       "memcache",
+			ProviderConfig: "127.0.0.1:9090",
+		}
+
+		Convey("Basic operation", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				sess.Set("uname", "unknwon")
+			})
+			m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := raw.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+			})
+			m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
+				sid := sess.ID()
+				So(sid, ShouldNotBeEmpty)
+
+				raw, err := sess.Read(sid)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := sess.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+
+				So(sess.Delete("uname"), ShouldBeNil)
+				So(sess.Get("uname"), ShouldBeNil)
+
+				So(sess.Destory(ctx), ShouldBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+
+			cookie := resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/reg", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+
+			cookie = resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/get", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+		})
+
+		Convey("Regenrate empty session", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
+			m.ServeHTTP(resp, req)
+		})
+	})
+}

+ 18 - 18
Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go

@@ -22,17 +22,17 @@ import (
 	"time"
 	"time"
 )
 )
 
 
-// MemSessionStore represents a in-memory session store implementation.
-type MemSessionStore struct {
+// MemStore represents a in-memory session store implementation.
+type MemStore struct {
 	sid        string
 	sid        string
 	lock       sync.RWMutex
 	lock       sync.RWMutex
 	data       map[interface{}]interface{}
 	data       map[interface{}]interface{}
 	lastAccess time.Time
 	lastAccess time.Time
 }
 }
 
 
-// NewMemSessionStore creates and returns a memory session store.
-func NewMemSessionStore(sid string) *MemSessionStore {
-	return &MemSessionStore{
+// NewMemStore creates and returns a memory session store.
+func NewMemStore(sid string) *MemStore {
+	return &MemStore{
 		sid:        sid,
 		sid:        sid,
 		data:       make(map[interface{}]interface{}),
 		data:       make(map[interface{}]interface{}),
 		lastAccess: time.Now(),
 		lastAccess: time.Now(),
@@ -40,7 +40,7 @@ func NewMemSessionStore(sid string) *MemSessionStore {
 }
 }
 
 
 // Set sets value to given key in session.
 // Set sets value to given key in session.
-func (s *MemSessionStore) Set(key, val interface{}) error {
+func (s *MemStore) Set(key, val interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -49,15 +49,15 @@ func (s *MemSessionStore) Set(key, val interface{}) error {
 }
 }
 
 
 // Get gets value by given key in session.
 // Get gets value by given key in session.
-func (s *MemSessionStore) Get(key interface{}) interface{} {
+func (s *MemStore) Get(key interface{}) interface{} {
 	s.lock.RLock()
 	s.lock.RLock()
 	defer s.lock.RUnlock()
 	defer s.lock.RUnlock()
 
 
 	return s.data[key]
 	return s.data[key]
 }
 }
 
 
-// Delete delete a key from session.
-func (s *MemSessionStore) Delete(key interface{}) error {
+// Delete deletes a key from session.
+func (s *MemStore) Delete(key interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -66,17 +66,17 @@ func (s *MemSessionStore) Delete(key interface{}) error {
 }
 }
 
 
 // ID returns current session ID.
 // ID returns current session ID.
-func (s *MemSessionStore) ID() string {
+func (s *MemStore) ID() string {
 	return s.sid
 	return s.sid
 }
 }
 
 
 // Release releases resource and save data to provider.
 // Release releases resource and save data to provider.
-func (_ *MemSessionStore) Release() error {
+func (_ *MemStore) Release() error {
 	return nil
 	return nil
 }
 }
 
 
 // Flush deletes all session data.
 // Flush deletes all session data.
-func (s *MemSessionStore) Flush() error {
+func (s *MemStore) Flush() error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -105,7 +105,7 @@ func (p *MemProvider) update(sid string) error {
 	defer p.lock.Unlock()
 	defer p.lock.Unlock()
 
 
 	if e, ok := p.data[sid]; ok {
 	if e, ok := p.data[sid]; ok {
-		e.Value.(*MemSessionStore).lastAccess = time.Now()
+		e.Value.(*MemStore).lastAccess = time.Now()
 		p.list.MoveToFront(e)
 		p.list.MoveToFront(e)
 		return nil
 		return nil
 	}
 	}
@@ -122,14 +122,14 @@ func (p *MemProvider) Read(sid string) (_ RawStore, err error) {
 		if err = p.update(sid); err != nil {
 		if err = p.update(sid); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-		return e.Value.(*MemSessionStore), nil
+		return e.Value.(*MemStore), nil
 	}
 	}
 
 
 	// Create a new session.
 	// Create a new session.
 	p.lock.Lock()
 	p.lock.Lock()
 	defer p.lock.Unlock()
 	defer p.lock.Unlock()
 
 
-	s := NewMemSessionStore(sid)
+	s := NewMemStore(sid)
 	p.data[sid] = p.list.PushBack(s)
 	p.data[sid] = p.list.PushBack(s)
 	return s, nil
 	return s, nil
 }
 }
@@ -173,7 +173,7 @@ func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	s.(*MemSessionStore).sid = sid
+	s.(*MemStore).sid = sid
 	p.data[sid] = p.list.PushBack(s)
 	p.data[sid] = p.list.PushBack(s)
 	return s, nil
 	return s, nil
 }
 }
@@ -193,11 +193,11 @@ func (p *MemProvider) GC() {
 			break
 			break
 		}
 		}
 
 
-		if (e.Value.(*MemSessionStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
+		if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
 			p.lock.RUnlock()
 			p.lock.RUnlock()
 			p.lock.Lock()
 			p.lock.Lock()
 			p.list.Remove(e)
 			p.list.Remove(e)
-			delete(p.data, e.Value.(*MemSessionStore).sid)
+			delete(p.data, e.Value.(*MemStore).sid)
 			p.lock.Unlock()
 			p.lock.Unlock()
 			p.lock.RLock()
 			p.lock.RLock()
 		} else {
 		} else {

+ 71 - 77
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql.go

@@ -17,6 +17,8 @@ package session
 
 
 import (
 import (
 	"database/sql"
 	"database/sql"
+	"fmt"
+	"log"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
@@ -25,16 +27,25 @@ import (
 	"github.com/macaron-contrib/session"
 	"github.com/macaron-contrib/session"
 )
 )
 
 
-// MysqlSessionStore represents a mysql session store implementation.
-type MysqlSessionStore struct {
+// MysqlStore represents a mysql session store implementation.
+type MysqlStore struct {
 	c    *sql.DB
 	c    *sql.DB
 	sid  string
 	sid  string
 	lock sync.RWMutex
 	lock sync.RWMutex
 	data map[interface{}]interface{}
 	data map[interface{}]interface{}
 }
 }
 
 
+// NewMysqlStore creates and returns a mysql session store.
+func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore {
+	return &MysqlStore{
+		c:    c,
+		sid:  sid,
+		data: kv,
+	}
+}
+
 // Set sets value to given key in session.
 // Set sets value to given key in session.
-func (s *MysqlSessionStore) Set(key, val interface{}) error {
+func (s *MysqlStore) Set(key, val interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -43,7 +54,7 @@ func (s *MysqlSessionStore) Set(key, val interface{}) error {
 }
 }
 
 
 // Get gets value by given key in session.
 // Get gets value by given key in session.
-func (s *MysqlSessionStore) Get(key interface{}) interface{} {
+func (s *MysqlStore) Get(key interface{}) interface{} {
 	s.lock.RLock()
 	s.lock.RLock()
 	defer s.lock.RUnlock()
 	defer s.lock.RUnlock()
 
 
@@ -51,7 +62,7 @@ func (s *MysqlSessionStore) Get(key interface{}) interface{} {
 }
 }
 
 
 // Delete delete a key from session.
 // Delete delete a key from session.
-func (s *MysqlSessionStore) Delete(key interface{}) error {
+func (s *MysqlStore) Delete(key interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -60,24 +71,24 @@ func (s *MysqlSessionStore) Delete(key interface{}) error {
 }
 }
 
 
 // ID returns current session ID.
 // ID returns current session ID.
-func (s *MysqlSessionStore) ID() string {
+func (s *MysqlStore) ID() string {
 	return s.sid
 	return s.sid
 }
 }
 
 
 // Release releases resource and save data to provider.
 // Release releases resource and save data to provider.
-func (s *MysqlSessionStore) Release() error {
-	defer s.c.Close()
+func (s *MysqlStore) Release() error {
 	data, err := session.EncodeGob(s.data)
 	data, err := session.EncodeGob(s.data)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	_, err = s.c.Exec("UPDATE session set `session_data`=?, `session_expiry`=? where session_key=?",
+
+	_, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?",
 		data, time.Now().Unix(), s.sid)
 		data, time.Now().Unix(), s.sid)
 	return err
 	return err
 }
 }
 
 
 // Flush deletes all session data.
 // Flush deletes all session data.
-func (s *MysqlSessionStore) Flush() error {
+func (s *MysqlStore) Flush() error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -87,113 +98,96 @@ func (s *MysqlSessionStore) Flush() error {
 
 
 // MysqlProvider represents a mysql session provider implementation.
 // MysqlProvider represents a mysql session provider implementation.
 type MysqlProvider struct {
 type MysqlProvider struct {
-	maxlifetime int64
-	connStr     string
+	c      *sql.DB
+	expire int64
 }
 }
 
 
-func (p *MysqlProvider) connectInit() *sql.DB {
-	db, e := sql.Open("mysql", p.connStr)
-	if e != nil {
-		return nil
-	}
-	return db
-}
+// Init initializes mysql session provider.
+// connStr: username:password@protocol(address)/dbname?param=value
+func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
+	p.expire = expire
 
 
-// Init initializes memory session provider.
-func (p *MysqlProvider) Init(maxlifetime int64, connStr string) error {
-	p.maxlifetime = maxlifetime
-	p.connStr = connStr
-	return nil
+	p.c, err = sql.Open("mysql", connStr)
+	if err != nil {
+		return err
+	}
+	return p.c.Ping()
 }
 }
 
 
 // Read returns raw session store by session ID.
 // Read returns raw session store by session ID.
 func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
 func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
-	c := p.connectInit()
-	row := c.QueryRow("select session_data from session where session_key=?", sid)
-	var sessiondata []byte
-	err := row.Scan(&sessiondata)
+	var data []byte
+	err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
 	if err == sql.ErrNoRows {
 	if err == sql.ErrNoRows {
-		c.Exec("insert into session(`session_key`,`session_data`,`session_expiry`) values(?,?,?)",
+		_, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
 			sid, "", time.Now().Unix())
 			sid, "", time.Now().Unix())
 	}
 	}
+	if err != nil {
+		return nil, err
+	}
+
 	var kv map[interface{}]interface{}
 	var kv map[interface{}]interface{}
-	if len(sessiondata) == 0 {
+	if len(data) == 0 {
 		kv = make(map[interface{}]interface{})
 		kv = make(map[interface{}]interface{})
 	} else {
 	} else {
-		kv, err = session.DecodeGob(sessiondata)
+		kv, err = session.DecodeGob(data)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
-	rs := &MysqlSessionStore{c: c, sid: sid, data: kv}
-	return rs, nil
+
+	return NewMysqlStore(p.c, sid, kv), nil
 }
 }
 
 
 // Exist returns true if session with given ID exists.
 // Exist returns true if session with given ID exists.
 func (p *MysqlProvider) Exist(sid string) bool {
 func (p *MysqlProvider) Exist(sid string) bool {
-	c := p.connectInit()
-	defer c.Close()
-
-	row := c.QueryRow("select session_data from session where session_key=?", sid)
-	var sessiondata []byte
-	err := row.Scan(&sessiondata)
-	if err == sql.ErrNoRows {
-		return false
-	} else {
-		return true
+	var data []byte
+	err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
+	if err != nil && err != sql.ErrNoRows {
+		panic("session/mysql: error checking existence: " + err.Error())
 	}
 	}
+	return err != sql.ErrNoRows
 }
 }
 
 
 // Destory deletes a session by session ID.
 // Destory deletes a session by session ID.
-func (p *MysqlProvider) Destory(sid string) (err error) {
-	c := p.connectInit()
-	if _, err = c.Exec("DELETE FROM session where session_key=?", sid); err != nil {
-		return err
-	}
-	return c.Close()
+func (p *MysqlProvider) Destory(sid string) error {
+	_, err := p.c.Exec("DELETE FROM session WHERE `key`=?", sid)
+	return err
 }
 }
 
 
 // Regenerate regenerates a session store from old session ID to new one.
 // Regenerate regenerates a session store from old session ID to new one.
-func (p *MysqlProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
-	c := p.connectInit()
-	row := c.QueryRow("select session_data from session where session_key=?", oldsid)
-	var sessiondata []byte
-	err := row.Scan(&sessiondata)
-	if err == sql.ErrNoRows {
-		c.Exec("insert into session(`session_key`,`session_data`,`session_expiry`) values(?,?,?)", oldsid, "", time.Now().Unix())
+func (p *MysqlProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
 	}
 	}
-	c.Exec("update session set `session_key`=? where session_key=?", sid, oldsid)
-	var kv map[interface{}]interface{}
-	if len(sessiondata) == 0 {
-		kv = make(map[interface{}]interface{})
-	} else {
-		kv, err = session.DecodeGob(sessiondata)
-		if err != nil {
+
+	if !p.Exist(oldsid) {
+		if _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
+			oldsid, "", time.Now().Unix()); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
-	rs := &MysqlSessionStore{c: c, sid: sid, data: kv}
-	return rs, nil
+
+	if _, err = p.c.Exec("UPDATE session SET `key`=? WHERE `key`=?", sid, oldsid); err != nil {
+		return nil, err
+	}
+
+	return p.Read(sid)
 }
 }
 
 
 // Count counts and returns number of sessions.
 // Count counts and returns number of sessions.
-func (p *MysqlProvider) Count() int {
-	c := p.connectInit()
-	defer c.Close()
-
-	var total int
-	err := c.QueryRow("SELECT count(*) as num from session").Scan(&total)
-	if err != nil {
-		return 0
+func (p *MysqlProvider) Count() (total int) {
+	if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
+		panic("session/mysql: error counting records: " + err.Error())
 	}
 	}
 	return total
 	return total
 }
 }
 
 
 // GC calls GC to clean expired sessions.
 // GC calls GC to clean expired sessions.
-func (mp *MysqlProvider) GC() {
-	c := mp.connectInit()
-	c.Exec("DELETE from session where session_expiry < ?", time.Now().Unix()-mp.maxlifetime)
-	c.Close()
+func (p *MysqlProvider) GC() {
+	if _, err := p.c.Exec("DELETE FROM session WHERE UNIX_TIMESTAMP(NOW()) - expiry > ?", p.expire); err != nil {
+		log.Printf("session/mysql: error garbage collecting: %v", err)
+	}
 }
 }
 
 
 func init() {
 func init() {

+ 1 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql.goconvey

@@ -0,0 +1 @@
+ignore

+ 138 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql_test.go

@@ -0,0 +1,138 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+	"time"
+
+	"github.com/Unknwon/macaron"
+	. "github.com/smartystreets/goconvey/convey"
+
+	"github.com/macaron-contrib/session"
+)
+
+func Test_MysqlProvider(t *testing.T) {
+	Convey("Test mysql session provider", t, func() {
+		opt := session.Options{
+			Provider:       "mysql",
+			ProviderConfig: "root:@tcp(localhost:3306)/macaron?charset=utf8",
+		}
+
+		Convey("Basic operation", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				sess.Set("uname", "unknwon")
+			})
+			m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := raw.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+			})
+			m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
+				sid := sess.ID()
+				So(sid, ShouldNotBeEmpty)
+
+				raw, err := sess.Read(sid)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+				So(raw.Release(), ShouldBeNil)
+
+				uname := sess.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+
+				So(sess.Delete("uname"), ShouldBeNil)
+				So(sess.Get("uname"), ShouldBeNil)
+
+				So(sess.Destory(ctx), ShouldBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+
+			cookie := resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/reg", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+
+			cookie = resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/get", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+		})
+
+		Convey("Regenrate empty session", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				So(sess.Destory(ctx), ShouldBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf48; Path=/;")
+			m.ServeHTTP(resp, req)
+		})
+
+		Convey("GC session", func() {
+			m := macaron.New()
+			opt2 := opt
+			opt2.Gclifetime = 1
+			m.Use(session.Sessioner(opt2))
+
+			m.Get("/", func(sess session.Store) {
+				sess.Set("uname", "unknwon")
+				So(sess.ID(), ShouldNotBeEmpty)
+				uname := sess.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+
+				So(sess.Flush(), ShouldBeNil)
+				So(sess.Get("uname"), ShouldBeNil)
+
+				time.Sleep(2 * time.Second)
+				sess.GC()
+				So(sess.Count(), ShouldEqual, 0)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+		})
+	})
+}

+ 203 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb.go

@@ -0,0 +1,203 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"fmt"
+	"sync"
+
+	"github.com/lunny/nodb"
+	"github.com/lunny/nodb/config"
+
+	"github.com/macaron-contrib/session"
+)
+
+// NodbStore represents a nodb session store implementation.
+type NodbStore struct {
+	c      *nodb.DB
+	sid    string
+	expire int64
+	lock   sync.RWMutex
+	data   map[interface{}]interface{}
+}
+
+// NewNodbStore creates and returns a ledis session store.
+func NewNodbStore(c *nodb.DB, sid string, expire int64, kv map[interface{}]interface{}) *NodbStore {
+	return &NodbStore{
+		c:      c,
+		expire: expire,
+		sid:    sid,
+		data:   kv,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *NodbStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *NodbStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *NodbStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *NodbStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (s *NodbStore) Release() error {
+	data, err := session.EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	if err = s.c.Set([]byte(s.sid), data); err != nil {
+		return err
+	}
+	_, err = s.c.Expire([]byte(s.sid), s.expire)
+	return err
+}
+
+// Flush deletes all session data.
+func (s *NodbStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// NodbProvider represents a ledis session provider implementation.
+type NodbProvider struct {
+	c      *nodb.DB
+	expire int64
+}
+
+// Init initializes nodb session provider.
+func (p *NodbProvider) Init(expire int64, configs string) error {
+	p.expire = expire
+
+	cfg := new(config.Config)
+	cfg.DataDir = configs
+	dbs, err := nodb.Open(cfg)
+	if err != nil {
+		return fmt.Errorf("session/nodb: error opening db: %v", err)
+	}
+
+	p.c, err = dbs.Select(0)
+	return err
+}
+
+// Read returns raw session store by session ID.
+func (p *NodbProvider) Read(sid string) (session.RawStore, error) {
+	if !p.Exist(sid) {
+		if err := p.c.Set([]byte(sid), []byte("")); err != nil {
+			return nil, err
+		}
+	}
+
+	var kv map[interface{}]interface{}
+	kvs, err := p.c.Get([]byte(sid))
+	if err != nil {
+		return nil, err
+	}
+	if len(kvs) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob(kvs)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewNodbStore(p.c, sid, p.expire, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *NodbProvider) Exist(sid string) bool {
+	count, err := p.c.Exists([]byte(sid))
+	return err == nil && count > 0
+}
+
+// Destory deletes a session by session ID.
+func (p *NodbProvider) Destory(sid string) error {
+	_, err := p.c.Del([]byte(sid))
+	return err
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *NodbProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	kvs := make([]byte, 0)
+	if p.Exist(oldsid) {
+		if kvs, err = p.c.Get([]byte(oldsid)); err != nil {
+			return nil, err
+		} else if _, err = p.c.Del([]byte(oldsid)); err != nil {
+			return nil, err
+		}
+	}
+
+	if err = p.c.Set([]byte(sid), kvs); err != nil {
+		return nil, err
+	} else if _, err = p.c.Expire([]byte(sid), p.expire); err != nil {
+		return nil, err
+	}
+
+	var kv map[interface{}]interface{}
+	if len(kvs) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob([]byte(kvs))
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewNodbStore(p.c, sid, p.expire, kv), nil
+}
+
+// Count counts and returns number of sessions.
+func (p *NodbProvider) Count() int {
+	// FIXME: how come this library does not have DbSize() method?
+	return -1
+}
+
+// GC calls GC to clean expired sessions.
+func (p *NodbProvider) GC() {}
+
+func init() {
+	session.Register("nodb", &NodbProvider{})
+}

+ 1 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb.goconvey

@@ -0,0 +1 @@
+ignore

+ 105 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb_test.go

@@ -0,0 +1,105 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/Unknwon/macaron"
+	. "github.com/smartystreets/goconvey/convey"
+
+	"github.com/macaron-contrib/session"
+)
+
+func Test_LedisProvider(t *testing.T) {
+	Convey("Test nodb session provider", t, func() {
+		opt := session.Options{
+			Provider:       "nodb",
+			ProviderConfig: "./tmp.db",
+		}
+
+		Convey("Basic operation", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				sess.Set("uname", "unknwon")
+			})
+			m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := raw.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+			})
+			m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
+				sid := sess.ID()
+				So(sid, ShouldNotBeEmpty)
+
+				raw, err := sess.Read(sid)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := sess.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+
+				So(sess.Delete("uname"), ShouldBeNil)
+				So(sess.Get("uname"), ShouldBeNil)
+
+				So(sess.Destory(ctx), ShouldBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+
+			cookie := resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/reg", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+
+			cookie = resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/get", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+
+			Convey("Regenrate empty session", func() {
+				m.Get("/empty", func(ctx *macaron.Context, sess session.Store) {
+					raw, err := sess.RegenerateId(ctx)
+					So(err, ShouldBeNil)
+					So(raw, ShouldNotBeNil)
+				})
+
+				resp = httptest.NewRecorder()
+				req, err = http.NewRequest("GET", "/empty", nil)
+				So(err, ShouldBeNil)
+				req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
+				m.ServeHTTP(resp, req)
+			})
+		})
+	})
+}

+ 196 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres.go

@@ -0,0 +1,196 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"database/sql"
+	"fmt"
+	"log"
+	"sync"
+	"time"
+
+	_ "github.com/lib/pq"
+
+	"github.com/macaron-contrib/session"
+)
+
+// PostgresStore represents a postgres session store implementation.
+type PostgresStore struct {
+	c    *sql.DB
+	sid  string
+	lock sync.RWMutex
+	data map[interface{}]interface{}
+}
+
+// NewPostgresStore creates and returns a postgres session store.
+func NewPostgresStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *PostgresStore {
+	return &PostgresStore{
+		c:    c,
+		sid:  sid,
+		data: kv,
+	}
+}
+
+// Set sets value to given key in session.
+func (s *PostgresStore) Set(key, value interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = value
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *PostgresStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete delete a key from session.
+func (s *PostgresStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *PostgresStore) ID() string {
+	return s.sid
+}
+
+// save postgres session values to database.
+// must call this method to save values to database.
+func (s *PostgresStore) Release() error {
+	data, err := session.EncodeGob(s.data)
+	if err != nil {
+		return err
+	}
+
+	_, err = s.c.Exec("UPDATE session SET data=$1, expiry=$2 WHERE key=$3",
+		data, time.Now().Unix(), s.sid)
+	return err
+}
+
+// Flush deletes all session data.
+func (s *PostgresStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// PostgresProvider represents a postgres session provider implementation.
+type PostgresProvider struct {
+	c           *sql.DB
+	maxlifetime int64
+}
+
+// Init initializes postgres session provider.
+// connStr: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
+func (p *PostgresProvider) Init(maxlifetime int64, connStr string) (err error) {
+	p.maxlifetime = maxlifetime
+
+	p.c, err = sql.Open("postgres", connStr)
+	if err != nil {
+		return err
+	}
+	return p.c.Ping()
+}
+
+// Read returns raw session store by session ID.
+func (p *PostgresProvider) Read(sid string) (session.RawStore, error) {
+	var data []byte
+	err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
+	if err == sql.ErrNoRows {
+		_, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
+			sid, "", time.Now().Unix())
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	var kv map[interface{}]interface{}
+	if len(data) == 0 {
+		kv = make(map[interface{}]interface{})
+	} else {
+		kv, err = session.DecodeGob(data)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return NewPostgresStore(p.c, sid, kv), nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *PostgresProvider) Exist(sid string) bool {
+	var data []byte
+	err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
+	if err != nil && err != sql.ErrNoRows {
+		panic("session/postgres: error checking existence: " + err.Error())
+	}
+	return err != sql.ErrNoRows
+}
+
+// Destory deletes a session by session ID.
+func (p *PostgresProvider) Destory(sid string) error {
+	_, err := p.c.Exec("DELETE FROM session WHERE key=$1", sid)
+	return err
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *PostgresProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	if !p.Exist(oldsid) {
+		if _, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
+			oldsid, "", time.Now().Unix()); err != nil {
+			return nil, err
+		}
+	}
+
+	if _, err = p.c.Exec("UPDATE session SET key=$1 WHERE key=$2", sid, oldsid); err != nil {
+		return nil, err
+	}
+
+	return p.Read(sid)
+}
+
+// Count counts and returns number of sessions.
+func (p *PostgresProvider) Count() (total int) {
+	if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
+		panic("session/postgres: error counting records: " + err.Error())
+	}
+	return total
+}
+
+// GC calls GC to clean expired sessions.
+func (p *PostgresProvider) GC() {
+	if _, err := p.c.Exec("DELETE FROM session WHERE EXTRACT(EPOCH FROM NOW()) - expiry > $1", p.maxlifetime); err != nil {
+		log.Printf("session/postgres: error garbage collecting: %v", err)
+	}
+}
+
+func init() {
+	session.Register("postgres", &PostgresProvider{})
+}

+ 1 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres.goconvey

@@ -0,0 +1 @@
+ignore

+ 138 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres_test.go

@@ -0,0 +1,138 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+	"time"
+
+	"github.com/Unknwon/macaron"
+	. "github.com/smartystreets/goconvey/convey"
+
+	"github.com/macaron-contrib/session"
+)
+
+func Test_PostgresProvider(t *testing.T) {
+	Convey("Test postgres session provider", t, func() {
+		opt := session.Options{
+			Provider:       "postgres",
+			ProviderConfig: "user=jiahuachen dbname=macaron port=5432 sslmode=disable",
+		}
+
+		Convey("Basic operation", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				sess.Set("uname", "unknwon")
+			})
+			m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := raw.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+			})
+			m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
+				sid := sess.ID()
+				So(sid, ShouldNotBeEmpty)
+
+				raw, err := sess.Read(sid)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+				So(raw.Release(), ShouldBeNil)
+
+				uname := sess.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+
+				So(sess.Delete("uname"), ShouldBeNil)
+				So(sess.Get("uname"), ShouldBeNil)
+
+				So(sess.Destory(ctx), ShouldBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+
+			cookie := resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/reg", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+
+			cookie = resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/get", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+		})
+
+		Convey("Regenrate empty session", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				So(sess.Destory(ctx), ShouldBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf48; Path=/;")
+			m.ServeHTTP(resp, req)
+		})
+
+		Convey("GC session", func() {
+			m := macaron.New()
+			opt2 := opt
+			opt2.Gclifetime = 1
+			m.Use(session.Sessioner(opt2))
+
+			m.Get("/", func(sess session.Store) {
+				sess.Set("uname", "unknwon")
+				So(sess.ID(), ShouldNotBeEmpty)
+				uname := sess.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+
+				So(sess.Flush(), ShouldBeNil)
+				So(sess.Get("uname"), ShouldBeNil)
+
+				time.Sleep(2 * time.Second)
+				sess.GC()
+				So(sess.Count(), ShouldEqual, 0)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+		})
+	})
+}

+ 0 - 211
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgresql.go

@@ -1,211 +0,0 @@
-// Copyright 2013 Beego Authors
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
-	"database/sql"
-	"sync"
-	"time"
-
-	_ "github.com/lib/pq"
-
-	"github.com/macaron-contrib/session"
-)
-
-// PostgresqlSessionStore represents a postgresql session store implementation.
-type PostgresqlSessionStore struct {
-	c    *sql.DB
-	sid  string
-	lock sync.RWMutex
-	data map[interface{}]interface{}
-}
-
-// Set sets value to given key in session.
-func (s *PostgresqlSessionStore) Set(key, value interface{}) error {
-	s.lock.Lock()
-	defer s.lock.Unlock()
-
-	s.data[key] = value
-	return nil
-}
-
-// Get gets value by given key in session.
-func (s *PostgresqlSessionStore) Get(key interface{}) interface{} {
-	s.lock.RLock()
-	defer s.lock.RUnlock()
-
-	return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *PostgresqlSessionStore) Delete(key interface{}) error {
-	s.lock.Lock()
-	defer s.lock.Unlock()
-
-	delete(s.data, key)
-	return nil
-}
-
-// ID returns current session ID.
-func (s *PostgresqlSessionStore) ID() string {
-	return s.sid
-}
-
-// save postgresql session values to database.
-// must call this method to save values to database.
-func (s *PostgresqlSessionStore) Release() error {
-	defer s.c.Close()
-
-	data, err := session.EncodeGob(s.data)
-	if err != nil {
-		return err
-	}
-
-	_, err = s.c.Exec("UPDATE session set session_data=$1, session_expiry=$2 where session_key=$3",
-		data, time.Now().Format(time.RFC3339), s.sid)
-	return err
-}
-
-// Flush deletes all session data.
-func (s *PostgresqlSessionStore) Flush() error {
-	s.lock.Lock()
-	defer s.lock.Unlock()
-
-	s.data = make(map[interface{}]interface{})
-	return nil
-}
-
-// PostgresqlProvider represents a postgresql session provider implementation.
-type PostgresqlProvider struct {
-	maxlifetime int64
-	connStr     string
-}
-
-func (p *PostgresqlProvider) connectInit() *sql.DB {
-	db, e := sql.Open("postgres", p.connStr)
-	if e != nil {
-		return nil
-	}
-	return db
-}
-
-// Init initializes memory session provider.
-func (p *PostgresqlProvider) Init(maxlifetime int64, connStr string) error {
-	p.maxlifetime = maxlifetime
-	p.connStr = connStr
-	return nil
-}
-
-// Read returns raw session store by session ID.
-func (p *PostgresqlProvider) Read(sid string) (session.RawStore, error) {
-	c := p.connectInit()
-	row := c.QueryRow("select session_data from session where session_key=$1", sid)
-	var sessiondata []byte
-	err := row.Scan(&sessiondata)
-	if err == sql.ErrNoRows {
-		_, err = c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)",
-			sid, "", time.Now().Format(time.RFC3339))
-
-		if err != nil {
-			return nil, err
-		}
-	} else if err != nil {
-		return nil, err
-	}
-
-	var kv map[interface{}]interface{}
-	if len(sessiondata) == 0 {
-		kv = make(map[interface{}]interface{})
-	} else {
-		kv, err = session.DecodeGob(sessiondata)
-		if err != nil {
-			return nil, err
-		}
-	}
-	rs := &PostgresqlSessionStore{c: c, sid: sid, data: kv}
-	return rs, nil
-}
-
-// Exist returns true if session with given ID exists.
-func (p *PostgresqlProvider) Exist(sid string) bool {
-	c := p.connectInit()
-	defer c.Close()
-	row := c.QueryRow("select session_data from session where session_key=$1", sid)
-	var sessiondata []byte
-	err := row.Scan(&sessiondata)
-
-	if err == sql.ErrNoRows {
-		return false
-	} else {
-		return true
-	}
-}
-
-// Destory deletes a session by session ID.
-func (p *PostgresqlProvider) Destory(sid string) (err error) {
-	c := p.connectInit()
-	if _, err = c.Exec("DELETE FROM session where session_key=$1", sid); err != nil {
-		return err
-	}
-	return c.Close()
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (p *PostgresqlProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
-	c := p.connectInit()
-	row := c.QueryRow("select session_data from session where session_key=$1", oldsid)
-	var sessiondata []byte
-	err := row.Scan(&sessiondata)
-	if err == sql.ErrNoRows {
-		c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)",
-			oldsid, "", time.Now().Format(time.RFC3339))
-	}
-	c.Exec("update session set session_key=$1 where session_key=$2", sid, oldsid)
-	var kv map[interface{}]interface{}
-	if len(sessiondata) == 0 {
-		kv = make(map[interface{}]interface{})
-	} else {
-		kv, err = session.DecodeGob(sessiondata)
-		if err != nil {
-			return nil, err
-		}
-	}
-	rs := &PostgresqlSessionStore{c: c, sid: sid, data: kv}
-	return rs, nil
-}
-
-// Count counts and returns number of sessions.
-func (p *PostgresqlProvider) Count() int {
-	c := p.connectInit()
-	defer c.Close()
-	var total int
-	err := c.QueryRow("SELECT count(*) as num from session").Scan(&total)
-	if err != nil {
-		return 0
-	}
-	return total
-}
-
-// GC calls GC to clean expired sessions.
-func (mp *PostgresqlProvider) GC() {
-	c := mp.connectInit()
-	c.Exec("DELETE from session where EXTRACT(EPOCH FROM (current_timestamp - session_expiry)) > $1", mp.maxlifetime)
-	c.Close()
-}
-
-func init() {
-	session.Register("postgresql", &PostgresqlProvider{})
-}

+ 98 - 97
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis.go

@@ -16,31 +16,39 @@
 package session
 package session
 
 
 import (
 import (
-	"strconv"
+	"fmt"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
+	"time"
 
 
-	"github.com/beego/redigo/redis"
+	"github.com/Unknwon/com"
+	"gopkg.in/ini.v1"
+	"gopkg.in/redis.v2"
 
 
 	"github.com/macaron-contrib/session"
 	"github.com/macaron-contrib/session"
 )
 )
 
 
-// redis max pool size
-var MAX_POOL_SIZE = 100
-
-var redisPool chan redis.Conn
+// RedisStore represents a redis session store implementation.
+type RedisStore struct {
+	c        *redis.Client
+	sid      string
+	duration time.Duration
+	lock     sync.RWMutex
+	data     map[interface{}]interface{}
+}
 
 
-// RedisSessionStore represents a redis session store implementation.
-type RedisSessionStore struct {
-	p           *redis.Pool
-	sid         string
-	lock        sync.RWMutex
-	data        map[interface{}]interface{}
-	maxlifetime int64
+// NewRedisStore creates and returns a redis session store.
+func NewRedisStore(c *redis.Client, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore {
+	return &RedisStore{
+		c:        c,
+		sid:      sid,
+		duration: dur,
+		data:     kv,
+	}
 }
 }
 
 
 // Set sets value to given key in session.
 // Set sets value to given key in session.
-func (s *RedisSessionStore) Set(key, val interface{}) error {
+func (s *RedisStore) Set(key, val interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -49,7 +57,7 @@ func (s *RedisSessionStore) Set(key, val interface{}) error {
 }
 }
 
 
 // Get gets value by given key in session.
 // Get gets value by given key in session.
-func (s *RedisSessionStore) Get(key interface{}) interface{} {
+func (s *RedisStore) Get(key interface{}) interface{} {
 	s.lock.RLock()
 	s.lock.RLock()
 	defer s.lock.RUnlock()
 	defer s.lock.RUnlock()
 
 
@@ -57,7 +65,7 @@ func (s *RedisSessionStore) Get(key interface{}) interface{} {
 }
 }
 
 
 // Delete delete a key from session.
 // Delete delete a key from session.
-func (s *RedisSessionStore) Delete(key interface{}) error {
+func (s *RedisStore) Delete(key interface{}) error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -66,26 +74,22 @@ func (s *RedisSessionStore) Delete(key interface{}) error {
 }
 }
 
 
 // ID returns current session ID.
 // ID returns current session ID.
-func (s *RedisSessionStore) ID() string {
+func (s *RedisStore) ID() string {
 	return s.sid
 	return s.sid
 }
 }
 
 
 // Release releases resource and save data to provider.
 // Release releases resource and save data to provider.
-func (s *RedisSessionStore) Release() error {
-	c := s.p.Get()
-	defer c.Close()
-
+func (s *RedisStore) Release() error {
 	data, err := session.EncodeGob(s.data)
 	data, err := session.EncodeGob(s.data)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	_, err = c.Do("SETEX", s.sid, s.maxlifetime, string(data))
-	return err
+	return s.c.SetEx(s.sid, s.duration, string(data)).Err()
 }
 }
 
 
 // Flush deletes all session data.
 // Flush deletes all session data.
-func (s *RedisSessionStore) Flush() error {
+func (s *RedisStore) Flush() error {
 	s.lock.Lock()
 	s.lock.Lock()
 	defer s.lock.Unlock()
 	defer s.lock.Unlock()
 
 
@@ -95,59 +99,65 @@ func (s *RedisSessionStore) Flush() error {
 
 
 // RedisProvider represents a redis session provider implementation.
 // RedisProvider represents a redis session provider implementation.
 type RedisProvider struct {
 type RedisProvider struct {
-	maxlifetime int64
-	connAddr    string
-	poolsize    int
-	password    string
-	poollist    *redis.Pool
-}
-
-// Init initializes memory session provider.
-// connStr: <redis server addr>,<pool size>,<password>
-// e.g. 127.0.0.1:6379,100,macaron
-func (p *RedisProvider) Init(maxlifetime int64, connStr string) error {
-	p.maxlifetime = maxlifetime
-	configs := strings.Split(connStr, ",")
-	if len(configs) > 0 {
-		p.connAddr = configs[0]
+	c        *redis.Client
+	duration time.Duration
+}
+
+// Init initializes redis session provider.
+// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
+func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
+	p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime))
+	if err != nil {
+		return err
 	}
 	}
-	if len(configs) > 1 {
-		poolsize, err := strconv.Atoi(configs[1])
-		if err != nil || poolsize <= 0 {
-			p.poolsize = MAX_POOL_SIZE
-		} else {
-			p.poolsize = poolsize
-		}
-	} else {
-		p.poolsize = MAX_POOL_SIZE
+
+	cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1)))
+	if err != nil {
+		return err
 	}
 	}
-	if len(configs) > 2 {
-		p.password = configs[2]
+
+	opt := &redis.Options{
+		Network: "tcp",
 	}
 	}
-	p.poollist = redis.NewPool(func() (redis.Conn, error) {
-		c, err := redis.Dial("tcp", p.connAddr)
-		if err != nil {
-			return nil, err
-		}
-		if p.password != "" {
-			if _, err := c.Do("AUTH", p.password); err != nil {
-				c.Close()
-				return nil, err
+	for k, v := range cfg.Section("").KeysHash() {
+		switch k {
+		case "network":
+			opt.Network = v
+		case "addr":
+			opt.Addr = v
+		case "password":
+			opt.Password = v
+		case "db":
+			opt.DB = com.StrTo(v).MustInt64()
+		case "pool_size":
+			opt.PoolSize = com.StrTo(v).MustInt()
+		case "idle_timeout":
+			opt.IdleTimeout, err = time.ParseDuration(v + "s")
+			if err != nil {
+				return fmt.Errorf("error parsing idle timeout: %v", err)
 			}
 			}
+		default:
+			return fmt.Errorf("session/redis: unsupported option '%s'", k)
 		}
 		}
-		return c, err
-	}, p.poolsize)
+	}
 
 
-	return p.poollist.Get().Err()
+	p.c = redis.NewClient(opt)
+	return p.c.Ping().Err()
 }
 }
 
 
 // Read returns raw session store by session ID.
 // Read returns raw session store by session ID.
 func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
 func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
-	c := p.poollist.Get()
-	defer c.Close()
+	if !p.Exist(sid) {
+		if err := p.c.Set(sid, "").Err(); err != nil {
+			return nil, err
+		}
+	}
 
 
-	kvs, err := redis.String(c.Do("GET", sid))
 	var kv map[interface{}]interface{}
 	var kv map[interface{}]interface{}
+	kvs, err := p.c.Get(sid).Result()
+	if err != nil {
+		return nil, err
+	}
 	if len(kvs) == 0 {
 	if len(kvs) == 0 {
 		kv = make(map[interface{}]interface{})
 		kv = make(map[interface{}]interface{})
 	} else {
 	} else {
@@ -157,48 +167,41 @@ func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
 		}
 		}
 	}
 	}
 
 
-	rs := &RedisSessionStore{p: p.poollist, sid: sid, data: kv, maxlifetime: p.maxlifetime}
-	return rs, nil
+	return NewRedisStore(p.c, sid, p.duration, kv), nil
 }
 }
 
 
 // Exist returns true if session with given ID exists.
 // Exist returns true if session with given ID exists.
 func (p *RedisProvider) Exist(sid string) bool {
 func (p *RedisProvider) Exist(sid string) bool {
-	c := p.poollist.Get()
-	defer c.Close()
-
-	if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 {
-		return false
-	} else {
-		return true
-	}
+	has, err := p.c.Exists(sid).Result()
+	return err == nil && has
 }
 }
 
 
 // Destory deletes a session by session ID.
 // Destory deletes a session by session ID.
 func (p *RedisProvider) Destory(sid string) error {
 func (p *RedisProvider) Destory(sid string) error {
-	c := p.poollist.Get()
-	defer c.Close()
-
-	_, err := c.Do("DEL", sid)
-	return err
+	return p.c.Del(sid).Err()
 }
 }
 
 
 // Regenerate regenerates a session store from old session ID to new one.
 // Regenerate regenerates a session store from old session ID to new one.
-func (p *RedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
-	c := p.poollist.Get()
-	defer c.Close()
-
-	if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 {
-		// oldsid doesn't exists, set the new sid directly
-		// ignore error here, since if it return error
-		// the existed value will be 0
-		c.Do("SET", sid, "", "EX", p.maxlifetime)
-	} else {
-		c.Do("RENAME", oldsid, sid)
-		c.Do("EXPIRE", sid, p.maxlifetime)
+func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	} else if !p.Exist(oldsid) {
+		// Make a fake old session.
+		if err = p.c.SetEx(oldsid, p.duration, "").Err(); err != nil {
+			return nil, err
+		}
+	}
+
+	if err = p.c.Rename(oldsid, sid).Err(); err != nil {
+		return nil, err
 	}
 	}
 
 
-	kvs, err := redis.String(c.Do("GET", sid))
 	var kv map[interface{}]interface{}
 	var kv map[interface{}]interface{}
+	kvs, err := p.c.Get(sid).Result()
+	if err != nil {
+		return nil, err
+	}
+
 	if len(kvs) == 0 {
 	if len(kvs) == 0 {
 		kv = make(map[interface{}]interface{})
 		kv = make(map[interface{}]interface{})
 	} else {
 	} else {
@@ -208,14 +211,12 @@ func (p *RedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error)
 		}
 		}
 	}
 	}
 
 
-	rs := &RedisSessionStore{p: p.poollist, sid: sid, data: kv, maxlifetime: p.maxlifetime}
-	return rs, nil
+	return NewRedisStore(p.c, sid, p.duration, kv), nil
 }
 }
 
 
 // Count counts and returns number of sessions.
 // Count counts and returns number of sessions.
 func (p *RedisProvider) Count() int {
 func (p *RedisProvider) Count() int {
-	// FIXME
-	return 0
+	return int(p.c.DbSize().Val())
 }
 }
 
 
 // GC calls GC to clean expired sessions.
 // GC calls GC to clean expired sessions.

+ 1 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis.goconvey

@@ -0,0 +1 @@
+ignore

+ 107 - 0
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis_test.go

@@ -0,0 +1,107 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/Unknwon/macaron"
+	. "github.com/smartystreets/goconvey/convey"
+
+	"github.com/macaron-contrib/session"
+)
+
+func Test_RedisProvider(t *testing.T) {
+	Convey("Test redis session provider", t, func() {
+		opt := session.Options{
+			Provider:       "redis",
+			ProviderConfig: "addr=:6379",
+		}
+
+		Convey("Basic operation", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				sess.Set("uname", "unknwon")
+			})
+			m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := raw.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+			})
+			m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
+				sid := sess.ID()
+				So(sid, ShouldNotBeEmpty)
+
+				raw, err := sess.Read(sid)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+
+				uname := sess.Get("uname")
+				So(uname, ShouldNotBeNil)
+				So(uname, ShouldEqual, "unknwon")
+
+				So(sess.Delete("uname"), ShouldBeNil)
+				So(sess.Get("uname"), ShouldBeNil)
+
+				So(sess.Destory(ctx), ShouldBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+
+			cookie := resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/reg", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+
+			cookie = resp.Header().Get("Set-Cookie")
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/get", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", cookie)
+			m.ServeHTTP(resp, req)
+		})
+
+		Convey("Regenrate empty session", func() {
+			m := macaron.New()
+			m.Use(session.Sessioner(opt))
+			m.Get("/", func(ctx *macaron.Context, sess session.Store) {
+				raw, err := sess.RegenerateId(ctx)
+				So(err, ShouldBeNil)
+				So(raw, ShouldNotBeNil)
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/", nil)
+			So(err, ShouldBeNil)
+			req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
+			m.ServeHTTP(resp, req)
+		})
+	})
+}

+ 17 - 27
Godeps/_workspace/src/github.com/macaron-contrib/session/session.go

@@ -13,7 +13,7 @@
 // License for the specific language governing permissions and limitations
 // License for the specific language governing permissions and limitations
 // under the License.
 // under the License.
 
 
-// Package session a middleware that provides the session manager of Macaron.
+// Package session a middleware that provides the session management of Macaron.
 package session
 package session
 
 
 // NOTE: last sync 000033e on Nov 4, 2014.
 // NOTE: last sync 000033e on Nov 4, 2014.
@@ -28,7 +28,7 @@ import (
 	"github.com/Unknwon/macaron"
 	"github.com/Unknwon/macaron"
 )
 )
 
 
-const _VERSION = "0.1.1"
+const _VERSION = "0.1.6"
 
 
 func Version() string {
 func Version() string {
 	return _VERSION
 	return _VERSION
@@ -37,11 +37,11 @@ func Version() string {
 // RawStore is the interface that operates the session data.
 // RawStore is the interface that operates the session data.
 type RawStore interface {
 type RawStore interface {
 	// Set sets value to given key in session.
 	// Set sets value to given key in session.
-	Set(key, value interface{}) error
+	Set(interface{}, interface{}) error
 	// Get gets value by given key in session.
 	// Get gets value by given key in session.
-	Get(key interface{}) interface{}
-	// Delete delete a key from session.
-	Delete(key interface{}) error
+	Get(interface{}) interface{}
+	// Delete deletes a key from session.
+	Delete(interface{}) error
 	// ID returns current session ID.
 	// ID returns current session ID.
 	ID() string
 	ID() string
 	// Release releases session resource and save data to provider.
 	// Release releases session resource and save data to provider.
@@ -54,7 +54,7 @@ type RawStore interface {
 type Store interface {
 type Store interface {
 	RawStore
 	RawStore
 	// Read returns raw session store by session ID.
 	// Read returns raw session store by session ID.
-	Read(sid string) (RawStore, error)
+	Read(string) (RawStore, error)
 	// Destory deletes a session.
 	// Destory deletes a session.
 	Destory(*macaron.Context) error
 	Destory(*macaron.Context) error
 	// RegenerateId regenerates a session store from old session ID to new one.
 	// RegenerateId regenerates a session store from old session ID to new one.
@@ -111,7 +111,7 @@ func prepareOptions(options []Options) Options {
 	if len(opt.Provider) == 0 {
 	if len(opt.Provider) == 0 {
 		opt.Provider = sec.Key("PROVIDER").MustString("memory")
 		opt.Provider = sec.Key("PROVIDER").MustString("memory")
 	}
 	}
-	if len(opt.ProviderConfig) == 0 && opt.Provider == "file" {
+	if len(opt.ProviderConfig) == 0 {
 		opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions")
 		opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions")
 	}
 	}
 	if len(opt.CookieName) == 0 {
 	if len(opt.CookieName) == 0 {
@@ -155,7 +155,7 @@ func Sessioner(options ...Options) macaron.Handler {
 	return func(ctx *macaron.Context) {
 	return func(ctx *macaron.Context) {
 		sess, err := manager.Start(ctx)
 		sess, err := manager.Start(ctx)
 		if err != nil {
 		if err != nil {
-			panic("session: " + err.Error())
+			panic("session(start): " + err.Error())
 		}
 		}
 
 
 		// Get flash.
 		// Get flash.
@@ -187,8 +187,8 @@ func Sessioner(options ...Options) macaron.Handler {
 
 
 		ctx.Next()
 		ctx.Next()
 
 
-		if sess.Release() != nil {
-			panic("session: " + err.Error())
+		if err = sess.Release(); err != nil {
+			panic("session(release): " + err.Error())
 		}
 		}
 	}
 	}
 }
 }
@@ -242,17 +242,14 @@ type Manager struct {
 func NewManager(name string, opt Options) (*Manager, error) {
 func NewManager(name string, opt Options) (*Manager, error) {
 	p, ok := providers[name]
 	p, ok := providers[name]
 	if !ok {
 	if !ok {
-		return nil, fmt.Errorf("session: unknown provider ‘%q’(forgotten import?)", name)
-	}
-	if err := p.Init(opt.Maxlifetime, opt.ProviderConfig); err != nil {
-		return nil, err
+		return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name)
 	}
 	}
-	return &Manager{p, opt}, nil
+	return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig)
 }
 }
 
 
 // sessionId generates a new session ID with rand string, unix nano time, remote addr by hash function.
 // sessionId generates a new session ID with rand string, unix nano time, remote addr by hash function.
 func (m *Manager) sessionId() string {
 func (m *Manager) sessionId() string {
-	return hex.EncodeToString(generateRandomKey(m.opt.IDLength))
+	return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2))
 }
 }
 
 
 // Start starts a session by generating new one
 // Start starts a session by generating new one
@@ -315,16 +312,9 @@ func (m *Manager) Destory(ctx *macaron.Context) error {
 func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) {
 func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) {
 	sid := m.sessionId()
 	sid := m.sessionId()
 	oldsid := ctx.GetCookie(m.opt.CookieName)
 	oldsid := ctx.GetCookie(m.opt.CookieName)
-	if len(oldsid) == 0 {
-		sess, err = m.provider.Read(oldsid)
-		if err != nil {
-			return nil, err
-		}
-	} else {
-		sess, err = m.provider.Regenerate(oldsid, sid)
-		if err != nil {
-			return nil, err
-		}
+	sess, err = m.provider.Regenerate(oldsid, sid)
+	if err != nil {
+		return nil, err
 	}
 	}
 	ck := &http.Cookie{
 	ck := &http.Cookie{
 		Name:     m.opt.CookieName,
 		Name:     m.opt.CookieName,

+ 1 - 1
Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go

@@ -42,7 +42,7 @@ func Test_Sessioner(t *testing.T) {
 		m.ServeHTTP(resp, req)
 		m.ServeHTTP(resp, req)
 	})
 	})
 
 
-	Convey("Register invalid provider that", t, func() {
+	Convey("Register invalid provider", t, func() {
 		Convey("Provider not exists", func() {
 		Convey("Provider not exists", func() {
 			defer func() {
 			defer func() {
 				So(recover(), ShouldNotBeNil)
 				So(recover(), ShouldNotBeNil)

+ 5 - 25
Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go

@@ -24,39 +24,19 @@ import (
 	"github.com/Unknwon/com"
 	"github.com/Unknwon/com"
 )
 )
 
 
-func init() {
-	gob.Register([]interface{}{})
-	gob.Register(map[int]interface{}{})
-	gob.Register(map[string]interface{}{})
-	gob.Register(map[interface{}]interface{}{})
-	gob.Register(map[string]string{})
-	gob.Register(map[int]string{})
-	gob.Register(map[int]int{})
-	gob.Register(map[int]int64{})
-}
-
 func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
 func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
 	for _, v := range obj {
 	for _, v := range obj {
 		gob.Register(v)
 		gob.Register(v)
 	}
 	}
 	buf := bytes.NewBuffer(nil)
 	buf := bytes.NewBuffer(nil)
-	enc := gob.NewEncoder(buf)
-	err := enc.Encode(obj)
-	if err != nil {
-		return []byte(""), err
-	}
-	return buf.Bytes(), nil
+	err := gob.NewEncoder(buf).Encode(obj)
+	return buf.Bytes(), err
 }
 }
 
 
-func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) {
+func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) {
 	buf := bytes.NewBuffer(encoded)
 	buf := bytes.NewBuffer(encoded)
-	dec := gob.NewDecoder(buf)
-	var out map[interface{}]interface{}
-	err := dec.Decode(&out)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
+	err = gob.NewDecoder(buf).Decode(&out)
+	return out, err
 }
 }
 
 
 // generateRandomKey creates a random key with the given strength.
 // generateRandomKey creates a random key with the given strength.