Commit 7eb45e17 by Torkel Ödegaard

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

parent 4af1dcd5
# 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)
# 2.0.0-Beta1 (2015-03-30)
......
......@@ -47,7 +47,7 @@
},
{
"ImportPath": "github.com/macaron-contrib/session",
"Rev": "65b8817c40cb5bdce08673a15fd2a648c2ba0e16"
"Rev": "31e841d95c7302b9ac456c830ea2d6dfcef4f84a"
},
{
"ImportPath": "github.com/mattn/go-sqlite3",
......
ledis/tmp.db
nodb/tmp.db
\ No newline at end of file
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
......@@ -12,6 +12,10 @@ Middleware session provides session management for [Macaron](https://github.com/
- [API Reference](https://gowalker.org/github.com/macaron-contrib/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
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
......@@ -28,17 +28,17 @@ import (
"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
sid string
lock sync.RWMutex
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,
sid: sid,
data: kv,
......@@ -46,7 +46,7 @@ func NewFileSessionStore(p *FileProvider, sid string, kv map[interface{}]interfa
}
// 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()
defer s.lock.Unlock()
......@@ -55,7 +55,7 @@ func (s *FileSessionStore) Set(key, val interface{}) error {
}
// 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()
defer s.lock.RUnlock()
......@@ -63,7 +63,7 @@ func (s *FileSessionStore) Get(key interface{}) interface{} {
}
// Delete delete a key from session.
func (s *FileSessionStore) Delete(key interface{}) error {
func (s *FileStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -72,12 +72,12 @@ func (s *FileSessionStore) Delete(key interface{}) error {
}
// ID returns current session ID.
func (s *FileSessionStore) ID() string {
func (s *FileStore) ID() string {
return s.sid
}
// Release releases resource and save data to provider.
func (s *FileSessionStore) Release() error {
func (s *FileStore) Release() error {
data, err := EncodeGob(s.data)
if err != nil {
return err
......@@ -87,7 +87,7 @@ func (s *FileSessionStore) Release() error {
}
// Flush deletes all session data.
func (s *FileSessionStore) Flush() error {
func (s *FileStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -97,7 +97,6 @@ func (s *FileSessionStore) Flush() error {
// FileProvider represents a file session provider implementation.
type FileProvider struct {
lock sync.RWMutex
maxlifetime int64
rootPath string
}
......@@ -115,9 +114,6 @@ func (p *FileProvider) filepath(sid string) string {
// Read returns raw session store by session ID.
func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
p.lock.Lock()
defer p.lock.Unlock()
filename := p.filepath(sid)
if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil {
return nil, err
......@@ -151,22 +147,16 @@ func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
return nil, err
}
}
return NewFileSessionStore(p, sid, kv), nil
return NewFileStore(p, sid, kv), nil
}
// Exist returns true if session with given ID exists.
func (p *FileProvider) Exist(sid string) bool {
p.lock.Lock()
defer p.lock.Unlock()
return com.IsFile(p.filepath(sid))
}
// Destory deletes a session by session ID.
func (p *FileProvider) Destory(sid string) error {
p.lock.Lock()
defer p.lock.Unlock()
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.
func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) {
p.lock.Lock()
if err := p.regenerate(oldsid, sid); err != nil {
p.lock.Unlock()
return nil, err
}
p.lock.Unlock()
return p.Read(sid)
}
......@@ -236,9 +223,6 @@ func (p *FileProvider) GC() {
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 != nil {
return err
......
......@@ -16,26 +16,39 @@
package session
import (
"fmt"
"strings"
"sync"
"github.com/Unknwon/com"
"github.com/siddontang/ledisdb/config"
"github.com/siddontang/ledisdb/ledis"
"gopkg.in/ini.v1"
"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.
func (s *LedisSessionStore) Set(key, val interface{}) error {
func (s *LedisStore) Set(key, val interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -44,7 +57,7 @@ func (s *LedisSessionStore) Set(key, val interface{}) error {
}
// 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()
defer s.lock.RUnlock()
......@@ -52,7 +65,7 @@ func (s *LedisSessionStore) Get(key interface{}) interface{} {
}
// Delete delete a key from session.
func (s *LedisSessionStore) Delete(key interface{}) error {
func (s *LedisStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -61,25 +74,26 @@ func (s *LedisSessionStore) Delete(key interface{}) error {
}
// ID returns current session ID.
func (s *LedisSessionStore) ID() string {
func (s *LedisStore) ID() string {
return s.sid
}
// Release releases resource and save data to provider.
func (s *LedisSessionStore) Release() error {
func (s *LedisStore) Release() error {
data, err := session.EncodeGob(s.data)
if err != nil {
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
}
_, err = c.Expire([]byte(s.sid), s.maxlifetime)
_, err = s.c.Expire([]byte(s.sid), s.expire)
return err
}
// Flush deletes all session data.
func (s *LedisSessionStore) Flush() error {
func (s *LedisStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -89,30 +103,54 @@ func (s *LedisSessionStore) Flush() error {
// LedisProvider represents a ledis session provider implementation.
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 {
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.
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{}
kvs, err := p.c.Get([]byte(sid))
if err != nil {
return nil, err
}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
......@@ -121,41 +159,40 @@ func (p *LedisProvider) Read(sid string) (session.RawStore, error) {
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.
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.
func (p *LedisProvider) Destory(sid string) error {
_, err := c.Del([]byte(sid))
_, err := p.c.Del([]byte(sid))
return err
}
// 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{}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
......@@ -165,18 +202,20 @@ func (p *LedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error)
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.
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.
func (p *LedisProvider) GC() {}
func (p *LedisProvider) GC() {
// FIXME: wtf???
}
func init() {
session.Register("ledis", &LedisProvider{})
......
// 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)
})
})
})
}
......@@ -16,6 +16,7 @@
package session
import (
"fmt"
"strings"
"sync"
......@@ -24,20 +25,35 @@ import (
"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.
func (s *MemcacheSessionStore) Set(key, val interface{}) error {
func (s *MemcacheStore) Set(key, val interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -46,7 +62,7 @@ func (s *MemcacheSessionStore) Set(key, val interface{}) error {
}
// 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()
defer s.lock.RUnlock()
......@@ -54,7 +70,7 @@ func (s *MemcacheSessionStore) Get(key interface{}) interface{} {
}
// Delete delete a key from session.
func (s *MemcacheSessionStore) Delete(key interface{}) error {
func (s *MemcacheStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -63,26 +79,22 @@ func (s *MemcacheSessionStore) Delete(key interface{}) error {
}
// ID returns current session ID.
func (s *MemcacheSessionStore) ID() string {
func (s *MemcacheStore) ID() string {
return s.sid
}
// Release releases resource and save data to provider.
func (s *MemcacheSessionStore) Release() error {
func (s *MemcacheStore) Release() error {
data, err := session.EncodeGob(s.data)
if err != nil {
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.
func (s *MemcacheSessionStore) Flush() error {
func (s *MemcacheStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -90,43 +102,33 @@ func (s *MemcacheSessionStore) Flush() error {
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
}
// 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
}
}
item, err := client.Get(sid)
var kv map[interface{}]interface{}
item, err := p.c.Get(sid)
if err != nil {
return nil, err
}
var kv map[interface{}]interface{}
if len(item.Value) == 0 {
kv = make(map[interface{}]interface{})
} 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.
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.
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.
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.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{}
if len(contain) == 0 {
if len(item.Value) == 0 {
kv = make(map[interface{}]interface{})
} else {
var err error
kv, err = session.DecodeGob(contain)
kv, err = session.DecodeGob(item.Value)
if err != nil {
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.
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.
func (p *MemProvider) GC() {}
func (p *MemcacheProvider) GC() {}
func init() {
session.Register("memcache", &MemProvider{})
session.Register("memcache", &MemcacheProvider{})
}
// 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)
})
})
}
......@@ -22,17 +22,17 @@ import (
"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
lock sync.RWMutex
data map[interface{}]interface{}
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,
data: make(map[interface{}]interface{}),
lastAccess: time.Now(),
......@@ -40,7 +40,7 @@ func NewMemSessionStore(sid string) *MemSessionStore {
}
// 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()
defer s.lock.Unlock()
......@@ -49,15 +49,15 @@ func (s *MemSessionStore) Set(key, val interface{}) error {
}
// 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()
defer s.lock.RUnlock()
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()
defer s.lock.Unlock()
......@@ -66,17 +66,17 @@ func (s *MemSessionStore) Delete(key interface{}) error {
}
// ID returns current session ID.
func (s *MemSessionStore) ID() string {
func (s *MemStore) ID() string {
return s.sid
}
// Release releases resource and save data to provider.
func (_ *MemSessionStore) Release() error {
func (_ *MemStore) Release() error {
return nil
}
// Flush deletes all session data.
func (s *MemSessionStore) Flush() error {
func (s *MemStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -105,7 +105,7 @@ func (p *MemProvider) update(sid string) error {
defer p.lock.Unlock()
if e, ok := p.data[sid]; ok {
e.Value.(*MemSessionStore).lastAccess = time.Now()
e.Value.(*MemStore).lastAccess = time.Now()
p.list.MoveToFront(e)
return nil
}
......@@ -122,14 +122,14 @@ func (p *MemProvider) Read(sid string) (_ RawStore, err error) {
if err = p.update(sid); err != nil {
return nil, err
}
return e.Value.(*MemSessionStore), nil
return e.Value.(*MemStore), nil
}
// Create a new session.
p.lock.Lock()
defer p.lock.Unlock()
s := NewMemSessionStore(sid)
s := NewMemStore(sid)
p.data[sid] = p.list.PushBack(s)
return s, nil
}
......@@ -173,7 +173,7 @@ func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) {
return nil, err
}
s.(*MemSessionStore).sid = sid
s.(*MemStore).sid = sid
p.data[sid] = p.list.PushBack(s)
return s, nil
}
......@@ -193,11 +193,11 @@ func (p *MemProvider) GC() {
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.Lock()
p.list.Remove(e)
delete(p.data, e.Value.(*MemSessionStore).sid)
delete(p.data, e.Value.(*MemStore).sid)
p.lock.Unlock()
p.lock.RLock()
} else {
......
......@@ -17,6 +17,8 @@ package session
import (
"database/sql"
"fmt"
"log"
"sync"
"time"
......@@ -25,16 +27,25 @@ import (
"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
sid string
lock sync.RWMutex
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.
func (s *MysqlSessionStore) Set(key, val interface{}) error {
func (s *MysqlStore) Set(key, val interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -43,7 +54,7 @@ func (s *MysqlSessionStore) Set(key, val interface{}) error {
}
// 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()
defer s.lock.RUnlock()
......@@ -51,7 +62,7 @@ func (s *MysqlSessionStore) Get(key interface{}) interface{} {
}
// Delete delete a key from session.
func (s *MysqlSessionStore) Delete(key interface{}) error {
func (s *MysqlStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -60,24 +71,24 @@ func (s *MysqlSessionStore) Delete(key interface{}) error {
}
// ID returns current session ID.
func (s *MysqlSessionStore) ID() string {
func (s *MysqlStore) ID() string {
return s.sid
}
// 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)
if err != nil {
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)
return err
}
// Flush deletes all session data.
func (s *MysqlSessionStore) Flush() error {
func (s *MysqlStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -87,113 +98,96 @@ func (s *MysqlSessionStore) Flush() error {
// MysqlProvider represents a mysql session provider implementation.
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.
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 {
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())
}
if err != nil {
return nil, err
}
var kv map[interface{}]interface{}
if len(sessiondata) == 0 {
if len(data) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(sessiondata)
kv, err = session.DecodeGob(data)
if err != nil {
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.
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.
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.
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
}
}
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.
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
}
// 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() {
......
// 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)
})
})
}
// 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{})
}
// 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)
})
})
})
}
......@@ -17,6 +17,8 @@ package session
import (
"database/sql"
"fmt"
"log"
"sync"
"time"
......@@ -25,16 +27,25 @@ import (
"github.com/macaron-contrib/session"
)
// PostgresqlSessionStore represents a postgresql session store implementation.
type PostgresqlSessionStore struct {
// 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 *PostgresqlSessionStore) Set(key, value interface{}) error {
func (s *PostgresStore) Set(key, value interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -43,7 +54,7 @@ func (s *PostgresqlSessionStore) Set(key, value interface{}) error {
}
// Get gets value by given key in session.
func (s *PostgresqlSessionStore) Get(key interface{}) interface{} {
func (s *PostgresStore) Get(key interface{}) interface{} {
s.lock.RLock()
defer s.lock.RUnlock()
......@@ -51,7 +62,7 @@ func (s *PostgresqlSessionStore) Get(key interface{}) interface{} {
}
// Delete delete a key from session.
func (s *PostgresqlSessionStore) Delete(key interface{}) error {
func (s *PostgresStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -60,27 +71,25 @@ func (s *PostgresqlSessionStore) Delete(key interface{}) error {
}
// ID returns current session ID.
func (s *PostgresqlSessionStore) ID() string {
func (s *PostgresStore) ID() string {
return s.sid
}
// save postgresql session values to database.
// save postgres session values to database.
// must call this method to save values to database.
func (s *PostgresqlSessionStore) Release() error {
defer s.c.Close()
func (s *PostgresStore) Release() error {
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)
_, 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 *PostgresqlSessionStore) Flush() error {
func (s *PostgresStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -88,124 +97,100 @@ func (s *PostgresqlSessionStore) Flush() error {
return nil
}
// PostgresqlProvider represents a postgresql session provider implementation.
type PostgresqlProvider struct {
// PostgresProvider represents a postgres session provider implementation.
type PostgresProvider struct {
c *sql.DB
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 {
// 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.connStr = connStr
return nil
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 *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)
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 = 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 {
_, 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(sessiondata) == 0 {
if len(data) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(sessiondata)
kv, err = session.DecodeGob(data)
if err != nil {
return nil, err
}
}
rs := &PostgresqlSessionStore{c: c, sid: sid, data: kv}
return rs, nil
return NewPostgresStore(p.c, sid, kv), 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
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 *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()
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 *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))
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)
}
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 {
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
}
}
rs := &PostgresqlSessionStore{c: c, sid: sid, data: kv}
return rs, nil
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 *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
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 (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 (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("postgresql", &PostgresqlProvider{})
session.Register("postgres", &PostgresProvider{})
}
// 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)
})
})
}
......@@ -16,31 +16,39 @@
package session
import (
"strconv"
"fmt"
"strings"
"sync"
"time"
"github.com/beego/redigo/redis"
"github.com/Unknwon/com"
"gopkg.in/ini.v1"
"gopkg.in/redis.v2"
"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.
func (s *RedisSessionStore) Set(key, val interface{}) error {
func (s *RedisStore) Set(key, val interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -49,7 +57,7 @@ func (s *RedisSessionStore) Set(key, val interface{}) error {
}
// 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()
defer s.lock.RUnlock()
......@@ -57,7 +65,7 @@ func (s *RedisSessionStore) Get(key interface{}) interface{} {
}
// Delete delete a key from session.
func (s *RedisSessionStore) Delete(key interface{}) error {
func (s *RedisStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -66,26 +74,22 @@ func (s *RedisSessionStore) Delete(key interface{}) error {
}
// ID returns current session ID.
func (s *RedisSessionStore) ID() string {
func (s *RedisStore) ID() string {
return s.sid
}
// 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)
if err != nil {
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.
func (s *RedisSessionStore) Flush() error {
func (s *RedisStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
......@@ -95,59 +99,65 @@ func (s *RedisSessionStore) Flush() error {
// RedisProvider represents a redis session provider implementation.
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.
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{}
kvs, err := p.c.Get(sid).Result()
if err != nil {
return nil, err
}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} 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.
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.
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.
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{}
kvs, err := p.c.Get(sid).Result()
if err != nil {
return nil, err
}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} 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.
func (p *RedisProvider) Count() int {
// FIXME
return 0
return int(p.c.DbSize().Val())
}
// GC calls GC to clean expired sessions.
......
// 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)
})
})
}
......@@ -13,7 +13,7 @@
// License for the specific language governing permissions and limitations
// 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
// NOTE: last sync 000033e on Nov 4, 2014.
......@@ -28,7 +28,7 @@ import (
"github.com/Unknwon/macaron"
)
const _VERSION = "0.1.1"
const _VERSION = "0.1.6"
func Version() string {
return _VERSION
......@@ -37,11 +37,11 @@ func Version() string {
// RawStore is the interface that operates the session data.
type RawStore interface {
// 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(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() string
// Release releases session resource and save data to provider.
......@@ -54,7 +54,7 @@ type RawStore interface {
type Store interface {
RawStore
// Read returns raw session store by session ID.
Read(sid string) (RawStore, error)
Read(string) (RawStore, error)
// Destory deletes a session.
Destory(*macaron.Context) error
// 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 {
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")
}
if len(opt.CookieName) == 0 {
......@@ -155,7 +155,7 @@ func Sessioner(options ...Options) macaron.Handler {
return func(ctx *macaron.Context) {
sess, err := manager.Start(ctx)
if err != nil {
panic("session: " + err.Error())
panic("session(start): " + err.Error())
}
// Get flash.
......@@ -187,8 +187,8 @@ func Sessioner(options ...Options) macaron.Handler {
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) {
p, ok := providers[name]
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.
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
......@@ -315,16 +312,9 @@ func (m *Manager) Destory(ctx *macaron.Context) error {
func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) {
sid := m.sessionId()
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{
Name: m.opt.CookieName,
......
......@@ -42,7 +42,7 @@ func Test_Sessioner(t *testing.T) {
m.ServeHTTP(resp, req)
})
Convey("Register invalid provider that", t, func() {
Convey("Register invalid provider", t, func() {
Convey("Provider not exists", func() {
defer func() {
So(recover(), ShouldNotBeNil)
......
......@@ -24,39 +24,19 @@ import (
"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) {
for _, v := range obj {
gob.Register(v)
}
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)
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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment