Updated session lib

This commit is contained in:
Torkel Ödegaard 2014-12-30 10:28:27 +01:00
parent e9fcca16bd
commit aafe2c5b98
19 changed files with 831 additions and 2205 deletions

2
Godeps/Godeps.json generated
View File

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

View File

@ -1,177 +1,16 @@
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 is the session manager of [Macaron](https://github.com/Unknwon/macaron). It can use many session providers, including cookie, memory, file, redis, memcache, PostgreSQL, MySQL, and couchbase. 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.
[API Reference](https://gowalker.org/github.com/macaron-contrib/session)
### Installation ### Installation
go get github.com/macaron-contrib/session go get github.com/macaron-contrib/session
## Usage ## Getting Help
```go
import (
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/session"
)
func main() {
m := macaron.Classic()
m.Use(session.Sessioner())
m.Get("/", func(sess session.Store) string {
sess.Set("session", "session middleware")
return sess.Get("session").(string)
})
m.Get("/signup", func(ctx *macaron.Context, f *session.Flash) {
f.Success("yes!!!")
f.Error("opps...")
// Use following fields in template
// - {{.Flash.SuccessMsg}}
// - {{.Flash.ErrorMsg}}
ctx.HTML(200, "signup", ctx.Data)
})
m.Run()
}
```
To use redis, memcache, PostgreSQL, MySQL, or couchbase as adapter, you should import their init functions:
```go
import (
_ "github.com/macaron-contrib/session/redis"
_ "github.com/macaron-contrib/session/memcache"
_ "github.com/macaron-contrib/session/postgres"
_ "github.com/macaron-contrib/session/mysql"
_ "github.com/macaron-contrib/session/couchbase"
)
```
## Options
`session.Sessioner` comes with a variety of configuration options:
```go
// ...
m.Use(session.Sessioner(session.Options{
Provider: "memory", // Name of provider.
Config: Config{
CookieName: "MacaronSession", // Key name store in cookie.
Gclifetime: 3600, // GC interval for memory adapter.
ProviderConfig: "./tmp", // Provider configuration string.
},
}))
// ...
```
### Example Options
- memory:
```go
// ...
m.Use(session.Sessioner(session.Options{
Provider: "memory", // Name of provider.
Config: Config{
CookieName: "MacaronSession", // Key name store in cookie.
Gclifetime: 3600, // GC interval for memory adapter.
ProviderConfig: "./tmp", // Provider configuration string.
},
}))
// ...
```
- file:
```go
// ...
m.Use(session.Sessioner(session.Options{
Provider: "file", // Name of provider.
Config: Config{
CookieName: "MacaronSession", // Key name store in cookie.
Gclifetime: 3600, // GC interval for memory adapter.
ProviderConfig: "./tmp", // Provider configuration string.
},
}))
// ...
```
- Redis:
```go
// ...
m.Use(session.Sessioner(session.Options{
Provider: "redis", // Name of provider.
Config: Config{
CookieName: "MacaronSession", // Key name store in cookie.
Gclifetime: 3600, // GC interval for memory adapter.
ProviderConfig: "127.0.0.1:6379,100,macaron", // Provider configuration string.
},
}))
// ...
```
- MySQL:
```go
// ...
m.Use(session.Sessioner(session.Options{
Provider: "mysql", // Name of provider.
Config: Config{
CookieName: "MacaronSession", // Key name store in cookie.
Gclifetime: 3600, // GC interval for memory adapter.
ProviderConfig: "username:password@protocol(address)/dbname?param=value", // Provider configuration string.
},
}))
// ...
```
- Cookie:
```go
// ...
m.Use(session.Sessioner(session.Options{
Provider: "cookie", // Name of provider.
Config: Config{
CookieName: "MacaronSession", // Key name store in cookie.
Gclifetime: 3600, // GC interval for memory adapter.
ProviderConfig: "{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}", // Provider configuration string.
},
}))
// ...
```
## How to write own provider?
When you develop a web app, maybe you want to write own provider because you must meet the requirements.
Writing a provider is easy. You only need to define two struct types
(Session and Provider), which satisfy the interface definition.
Maybe you will find the **memory** provider is a good example.
type Store interface {
Set(key, value interface{}) error //set session value
Get(key interface{}) interface{} //get session value
Delete(key interface{}) error //delete session value
SessionID() string //back current sessionID
SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data
Flush() error //delete all data
}
type Provider interface {
SessionInit(gclifetime int64, config string) error
SessionRead(sid string) (SessionStore, error)
SessionExist(sid string) bool
SessionRegenerate(oldsid, sid string) (Store, error)
SessionDestroy(sid string) error
SessionAll() int //get all active session
SessionGC()
}
- [API Reference](https://gowalker.org/github.com/macaron-contrib/session)
- [Documentation](http://macaron.gogs.io/docs/middlewares/session)
## License ## License

View File

@ -16,7 +16,6 @@
package session package session
import ( import (
"net/http"
"strings" "strings"
"sync" "sync"
@ -25,72 +24,78 @@ import (
"github.com/macaron-contrib/session" "github.com/macaron-contrib/session"
) )
var couchbpder = &CouchbaseProvider{} // CouchbaseSessionStore represents a couchbase session store implementation.
type CouchbaseSessionStore struct { type CouchbaseSessionStore struct {
b *couchbase.Bucket b *couchbase.Bucket
sid string sid string
lock sync.RWMutex lock sync.RWMutex
values map[interface{}]interface{} data map[interface{}]interface{}
maxlifetime int64 maxlifetime int64
} }
// Set sets value to given key in session.
func (s *CouchbaseSessionStore) 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 *CouchbaseSessionStore) Get(key interface{}) interface{} {
s.lock.RLock()
defer s.lock.RUnlock()
return s.data[key]
}
// Delete delete a key from session.
func (s *CouchbaseSessionStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.data, key)
return nil
}
// ID returns current session ID.
func (s *CouchbaseSessionStore) ID() string {
return s.sid
}
// Release releases resource and save data to provider.
func (s *CouchbaseSessionStore) Release() error {
defer s.b.Close()
data, err := session.EncodeGob(s.data)
if err != nil {
return err
}
return s.b.Set(s.sid, int(s.maxlifetime), data)
}
// Flush deletes all session data.
func (s *CouchbaseSessionStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = make(map[interface{}]interface{})
return nil
}
// CouchbaseProvider represents a couchbase session provider implementation.
type CouchbaseProvider struct { type CouchbaseProvider struct {
maxlifetime int64 maxlifetime int64
savePath string connStr string
pool string pool string
bucket string bucket string
b *couchbase.Bucket b *couchbase.Bucket
} }
func (cs *CouchbaseSessionStore) Set(key, value interface{}) error {
cs.lock.Lock()
defer cs.lock.Unlock()
cs.values[key] = value
return nil
}
func (cs *CouchbaseSessionStore) Get(key interface{}) interface{} {
cs.lock.RLock()
defer cs.lock.RUnlock()
if v, ok := cs.values[key]; ok {
return v
} else {
return nil
}
}
func (cs *CouchbaseSessionStore) Delete(key interface{}) error {
cs.lock.Lock()
defer cs.lock.Unlock()
delete(cs.values, key)
return nil
}
func (cs *CouchbaseSessionStore) Flush() error {
cs.lock.Lock()
defer cs.lock.Unlock()
cs.values = make(map[interface{}]interface{})
return nil
}
func (cs *CouchbaseSessionStore) SessionID() string {
return cs.sid
}
func (cs *CouchbaseSessionStore) SessionRelease(w http.ResponseWriter) {
defer cs.b.Close()
bo, err := session.EncodeGob(cs.values)
if err != nil {
return
}
cs.b.Set(cs.sid, int(cs.maxlifetime), bo)
}
func (cp *CouchbaseProvider) getBucket() *couchbase.Bucket { func (cp *CouchbaseProvider) getBucket() *couchbase.Bucket {
c, err := couchbase.Connect(cp.savePath) c, err := couchbase.Connect(cp.connStr)
if err != nil { if err != nil {
return nil return nil
} }
@ -108,32 +113,32 @@ func (cp *CouchbaseProvider) getBucket() *couchbase.Bucket {
return bucket return bucket
} }
// init couchbase session // Init initializes memory session provider.
// savepath like couchbase server REST/JSON URL // connStr is couchbase server REST/JSON URL
// e.g. http://host:port/, Pool, Bucket // e.g. http://host:port/, Pool, Bucket
func (cp *CouchbaseProvider) SessionInit(maxlifetime int64, savePath string) error { func (p *CouchbaseProvider) Init(maxlifetime int64, connStr string) error {
cp.maxlifetime = maxlifetime p.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",") configs := strings.Split(connStr, ",")
if len(configs) > 0 { if len(configs) > 0 {
cp.savePath = configs[0] p.connStr = configs[0]
} }
if len(configs) > 1 { if len(configs) > 1 {
cp.pool = configs[1] p.pool = configs[1]
} }
if len(configs) > 2 { if len(configs) > 2 {
cp.bucket = configs[2] p.bucket = configs[2]
} }
return nil return nil
} }
// read couchbase session by sid // Read returns raw session store by session ID.
func (cp *CouchbaseProvider) SessionRead(sid string) (session.RawStore, error) { func (p *CouchbaseProvider) Read(sid string) (session.RawStore, error) {
cp.b = cp.getBucket() p.b = p.getBucket()
var doc []byte var doc []byte
err := cp.b.Get(sid, &doc) err := p.b.Get(sid, &doc)
var kv map[interface{}]interface{} var kv map[interface{}]interface{}
if doc == nil { if doc == nil {
kv = make(map[interface{}]interface{}) kv = make(map[interface{}]interface{})
@ -144,38 +149,49 @@ func (cp *CouchbaseProvider) SessionRead(sid string) (session.RawStore, error) {
} }
} }
cs := &CouchbaseSessionStore{b: cp.b, sid: sid, values: kv, maxlifetime: cp.maxlifetime} cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime}
return cs, nil return cs, nil
} }
func (cp *CouchbaseProvider) SessionExist(sid string) bool { // Exist returns true if session with given ID exists.
cp.b = cp.getBucket() func (p *CouchbaseProvider) Exist(sid string) bool {
defer cp.b.Close() p.b = p.getBucket()
defer p.b.Close()
var doc []byte var doc []byte
if err := cp.b.Get(sid, &doc); err != nil || doc == nil { if err := p.b.Get(sid, &doc); err != nil || doc == nil {
return false return false
} else { } else {
return true return true
} }
} }
func (cp *CouchbaseProvider) SessionRegenerate(oldsid, sid string) (session.RawStore, error) { // Destory deletes a session by session ID.
cp.b = cp.getBucket() func (p *CouchbaseProvider) Destory(sid string) error {
p.b = p.getBucket()
defer p.b.Close()
p.b.Delete(sid)
return nil
}
// Regenerate regenerates a session store from old session ID to new one.
func (p *CouchbaseProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
p.b = p.getBucket()
var doc []byte var doc []byte
if err := cp.b.Get(oldsid, &doc); err != nil || doc == nil { if err := p.b.Get(oldsid, &doc); err != nil || doc == nil {
cp.b.Set(sid, int(cp.maxlifetime), "") p.b.Set(sid, int(p.maxlifetime), "")
} else { } else {
err := cp.b.Delete(oldsid) err := p.b.Delete(oldsid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, _ = cp.b.Add(sid, int(cp.maxlifetime), doc) _, _ = p.b.Add(sid, int(p.maxlifetime), doc)
} }
err := cp.b.Get(sid, &doc) err := p.b.Get(sid, &doc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -189,26 +205,19 @@ func (cp *CouchbaseProvider) SessionRegenerate(oldsid, sid string) (session.RawS
} }
} }
cs := &CouchbaseSessionStore{b: cp.b, sid: sid, values: kv, maxlifetime: cp.maxlifetime} cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime}
return cs, nil return cs, nil
} }
func (cp *CouchbaseProvider) SessionDestroy(sid string) error { // Count counts and returns number of sessions.
cp.b = cp.getBucket() func (p *CouchbaseProvider) Count() int {
defer cp.b.Close() // FIXME
cp.b.Delete(sid)
return nil
}
func (cp *CouchbaseProvider) SessionGC() {
return
}
func (cp *CouchbaseProvider) SessionAll() int {
return 0 return 0
} }
// GC calls GC to clean expired sessions.
func (p *CouchbaseProvider) GC() {}
func init() { func init() {
session.Register("couchbase", couchbpder) session.Register("couchbase", &CouchbaseProvider{})
} }

View File

@ -16,89 +16,89 @@
package session package session
import ( import (
"net/http"
"sync" "sync"
"github.com/astaxie/beego/session"
"github.com/siddontang/ledisdb/config" "github.com/siddontang/ledisdb/config"
"github.com/siddontang/ledisdb/ledis" "github.com/siddontang/ledisdb/ledis"
"github.com/macaron-contrib/session"
) )
var ledispder = &LedisProvider{}
var c *ledis.DB var c *ledis.DB
// ledis session store // LedisSessionStore represents a ledis session store implementation.
type LedisSessionStore struct { type LedisSessionStore struct {
sid string sid string
lock sync.RWMutex lock sync.RWMutex
values map[interface{}]interface{} data map[interface{}]interface{}
maxlifetime int64 maxlifetime int64
} }
// set value in ledis session // Set sets value to given key in session.
func (ls *LedisSessionStore) Set(key, value interface{}) error { func (s *LedisSessionStore) Set(key, val interface{}) error {
ls.lock.Lock() s.lock.Lock()
defer ls.lock.Unlock() defer s.lock.Unlock()
ls.values[key] = value
s.data[key] = val
return nil return nil
} }
// get value in ledis session // Get gets value by given key in session.
func (ls *LedisSessionStore) Get(key interface{}) interface{} { func (s *LedisSessionStore) Get(key interface{}) interface{} {
ls.lock.RLock() s.lock.RLock()
defer ls.lock.RUnlock() defer s.lock.RUnlock()
if v, ok := ls.values[key]; ok {
return v return s.data[key]
} else {
return nil
}
} }
// delete value in ledis session // Delete delete a key from session.
func (ls *LedisSessionStore) Delete(key interface{}) error { func (s *LedisSessionStore) Delete(key interface{}) error {
ls.lock.Lock() s.lock.Lock()
defer ls.lock.Unlock() defer s.lock.Unlock()
delete(ls.values, key)
delete(s.data, key)
return nil return nil
} }
// clear all values in ledis session // ID returns current session ID.
func (ls *LedisSessionStore) Flush() error { func (s *LedisSessionStore) ID() string {
ls.lock.Lock() return s.sid
defer ls.lock.Unlock()
ls.values = make(map[interface{}]interface{})
return nil
} }
// get ledis session id // Release releases resource and save data to provider.
func (ls *LedisSessionStore) SessionID() string { func (s *LedisSessionStore) Release() error {
return ls.sid data, err := session.EncodeGob(s.data)
}
// save session values to ledis
func (ls *LedisSessionStore) SessionRelease(w http.ResponseWriter) {
b, err := session.EncodeGob(ls.values)
if err != nil { if err != nil {
return return err
} }
c.Set([]byte(ls.sid), b) if err = c.Set([]byte(s.sid), data); err != nil {
c.Expire([]byte(ls.sid), ls.maxlifetime) return err
}
_, err = c.Expire([]byte(s.sid), s.maxlifetime)
return err
} }
// ledis session provider // Flush deletes all session data.
func (s *LedisSessionStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = make(map[interface{}]interface{})
return nil
}
// LedisProvider represents a ledis session provider implementation.
type LedisProvider struct { type LedisProvider struct {
maxlifetime int64 maxlifetime int64
savePath string savePath string
} }
// init ledis session // Init initializes memory session provider.
// savepath like ledis server saveDataPath,pool size func (p *LedisProvider) Init(maxlifetime int64, savePath string) error {
// e.g. 127.0.0.1:6379,100,astaxie p.maxlifetime = maxlifetime
func (lp *LedisProvider) SessionInit(maxlifetime int64, savePath string) error { p.savePath = savePath
lp.maxlifetime = maxlifetime
lp.savePath = savePath
cfg := new(config.Config) cfg := new(config.Config)
cfg.DataDir = lp.savePath cfg.DataDir = p.savePath
var err error var err error
nowLedis, err := ledis.Open(cfg) nowLedis, err := ledis.Open(cfg)
c, err = nowLedis.Select(0) c, err = nowLedis.Select(0)
@ -109,8 +109,8 @@ func (lp *LedisProvider) SessionInit(maxlifetime int64, savePath string) error {
return nil return nil
} }
// read ledis session by sid // Read returns raw session store by session ID.
func (lp *LedisProvider) SessionRead(sid string) (session.SessionStore, error) { func (p *LedisProvider) Read(sid string) (session.RawStore, error) {
kvs, err := c.Get([]byte(sid)) kvs, err := c.Get([]byte(sid))
var kv map[interface{}]interface{} var kv map[interface{}]interface{}
if len(kvs) == 0 { if len(kvs) == 0 {
@ -121,12 +121,12 @@ func (lp *LedisProvider) SessionRead(sid string) (session.SessionStore, error) {
return nil, err return nil, err
} }
} }
ls := &LedisSessionStore{sid: sid, values: kv, maxlifetime: lp.maxlifetime} ls := &LedisSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
return ls, nil return ls, nil
} }
// check ledis session exist by sid // Exist returns true if session with given ID exists.
func (lp *LedisProvider) SessionExist(sid string) bool { func (p *LedisProvider) Exist(sid string) bool {
count, _ := c.Exists([]byte(sid)) count, _ := c.Exists([]byte(sid))
if count == 0 { if count == 0 {
return false return false
@ -135,19 +135,25 @@ func (lp *LedisProvider) SessionExist(sid string) bool {
} }
} }
// generate new sid for ledis session // Destory deletes a session by session ID.
func (lp *LedisProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) { func (p *LedisProvider) Destory(sid string) error {
_, err := 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)) count, _ := c.Exists([]byte(sid))
if count == 0 { if count == 0 {
// oldsid doesn't exists, set the new sid directly // oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error // ignore error here, since if it return error
// the existed value will be 0 // the existed value will be 0
c.Set([]byte(sid), []byte("")) c.Set([]byte(sid), []byte(""))
c.Expire([]byte(sid), lp.maxlifetime) c.Expire([]byte(sid), p.maxlifetime)
} else { } else {
data, _ := c.Get([]byte(oldsid)) data, _ := c.Get([]byte(oldsid))
c.Set([]byte(sid), data) c.Set([]byte(sid), data)
c.Expire([]byte(sid), lp.maxlifetime) c.Expire([]byte(sid), p.maxlifetime)
} }
kvs, err := c.Get([]byte(sid)) kvs, err := c.Get([]byte(sid))
var kv map[interface{}]interface{} var kv map[interface{}]interface{}
@ -159,26 +165,19 @@ func (lp *LedisProvider) SessionRegenerate(oldsid, sid string) (session.SessionS
return nil, err return nil, err
} }
} }
ls := &LedisSessionStore{sid: sid, values: kv, maxlifetime: lp.maxlifetime} ls := &LedisSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
return ls, nil return ls, nil
} }
// delete ledis session by id // Count counts and returns number of sessions.
func (lp *LedisProvider) SessionDestroy(sid string) error { func (p *LedisProvider) Count() int {
c.Del([]byte(sid)) // FIXME
return nil
}
// Impelment method, no used.
func (lp *LedisProvider) SessionGC() {
return
}
// @todo
func (lp *LedisProvider) SessionAll() int {
return 0 return 0
} }
// GC calls GC to clean expired sessions.
func (p *LedisProvider) GC() {}
func init() { func init() {
session.Register("ledis", ledispder) session.Register("ledis", &LedisProvider{})
} }

View File

@ -16,7 +16,6 @@
package session package session
import ( import (
"net/http"
"strings" "strings"
"sync" "sync"
@ -26,73 +25,72 @@ import (
) )
var ( var (
mempder = &MemProvider{}
client *memcache.Client client *memcache.Client
) )
// memcache session store // MemcacheSessionStore represents a memcache session store implementation.
type MemcacheSessionStore struct { type MemcacheSessionStore struct {
sid string sid string
lock sync.RWMutex lock sync.RWMutex
values map[interface{}]interface{} data map[interface{}]interface{}
maxlifetime int64 maxlifetime int64
} }
// set value in memcache session // Set sets value to given key in session.
func (rs *MemcacheSessionStore) Set(key, value interface{}) error { func (s *MemcacheSessionStore) Set(key, val interface{}) error {
rs.lock.Lock() s.lock.Lock()
defer rs.lock.Unlock() defer s.lock.Unlock()
rs.values[key] = value
s.data[key] = val
return nil return nil
} }
// get value in memcache session // Get gets value by given key in session.
func (rs *MemcacheSessionStore) Get(key interface{}) interface{} { func (s *MemcacheSessionStore) Get(key interface{}) interface{} {
rs.lock.RLock() s.lock.RLock()
defer rs.lock.RUnlock() defer s.lock.RUnlock()
if v, ok := rs.values[key]; ok {
return v return s.data[key]
} else {
return nil
}
} }
// delete value in memcache session // Delete delete a key from session.
func (rs *MemcacheSessionStore) Delete(key interface{}) error { func (s *MemcacheSessionStore) Delete(key interface{}) error {
rs.lock.Lock() s.lock.Lock()
defer rs.lock.Unlock() defer s.lock.Unlock()
delete(rs.values, key)
delete(s.data, key)
return nil return nil
} }
// clear all values in memcache session // ID returns current session ID.
func (rs *MemcacheSessionStore) Flush() error { func (s *MemcacheSessionStore) ID() string {
rs.lock.Lock() return s.sid
defer rs.lock.Unlock()
rs.values = make(map[interface{}]interface{})
return nil
} }
// get redis session id // Release releases resource and save data to provider.
func (rs *MemcacheSessionStore) SessionID() string { func (s *MemcacheSessionStore) Release() error {
return rs.sid data, err := session.EncodeGob(s.data)
}
// save session values to redis
func (rs *MemcacheSessionStore) SessionRelease(w http.ResponseWriter) {
b, err := session.EncodeGob(rs.values)
if err != nil { if err != nil {
return return err
} }
client.Set(&memcache.Item{ return client.Set(&memcache.Item{
Key: rs.sid, Key: s.sid,
Value: b, Value: data,
Expiration: int32(rs.maxlifetime), Expiration: int32(s.maxlifetime),
}) })
} }
// redis session provider // Flush deletes all session data.
func (s *MemcacheSessionStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = make(map[interface{}]interface{})
return nil
}
// MemProvider represents a memcache session provider implementation.
type MemProvider struct { type MemProvider struct {
maxlifetime int64 maxlifetime int64
conninfo []string conninfo []string
@ -100,25 +98,25 @@ type MemProvider struct {
password string password string
} }
// init redis session // Init initializes memory session provider.
// savepath like // connStrs can be multiple connection strings separate by ;
// e.g. 127.0.0.1:9090 // e.g. 127.0.0.1:9090
func (rp *MemProvider) SessionInit(maxlifetime int64, savePath string) error { func (p *MemProvider) Init(maxlifetime int64, connStrs string) error {
rp.maxlifetime = maxlifetime p.maxlifetime = maxlifetime
rp.conninfo = strings.Split(savePath, ";") p.conninfo = strings.Split(connStrs, ";")
client = memcache.New(rp.conninfo...) client = memcache.New(p.conninfo...)
return nil return nil
} }
func (rp *MemProvider) connectInit() error { func (p *MemProvider) connectInit() error {
client = memcache.New(rp.conninfo...) client = memcache.New(p.conninfo...)
return nil return nil
} }
// read redis session by sid // Read returns raw session store by session ID.
func (rp *MemProvider) SessionRead(sid string) (session.RawStore, error) { func (p *MemProvider) Read(sid string) (session.RawStore, error) {
if client == nil { if client == nil {
if err := rp.connectInit(); err != nil { if err := p.connectInit(); err != nil {
return nil, err return nil, err
} }
} }
@ -138,14 +136,14 @@ func (rp *MemProvider) SessionRead(sid string) (session.RawStore, error) {
} }
} }
rs := &MemcacheSessionStore{sid: sid, values: kv, maxlifetime: rp.maxlifetime} rs := &MemcacheSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
return rs, nil return rs, nil
} }
// check redis session exist by sid // Exist returns true if session with given ID exists.
func (rp *MemProvider) SessionExist(sid string) bool { func (p *MemProvider) Exist(sid string) bool {
if client == nil { if client == nil {
if err := rp.connectInit(); err != nil { if err := p.connectInit(); err != nil {
return false return false
} }
} }
@ -157,10 +155,21 @@ func (rp *MemProvider) SessionExist(sid string) bool {
} }
} }
// generate new sid for redis session // Destory deletes a session by session ID.
func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.RawStore, error) { func (p *MemProvider) Destory(sid string) error {
if client == nil { if client == nil {
if err := rp.connectInit(); err != nil { if err := p.connectInit(); err != nil {
return err
}
}
return client.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 return nil, err
} }
} }
@ -172,13 +181,13 @@ func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.RawStore,
// the existed value will be 0 // the existed value will be 0
item.Key = sid item.Key = sid
item.Value = []byte("") item.Value = []byte("")
item.Expiration = int32(rp.maxlifetime) item.Expiration = int32(p.maxlifetime)
client.Set(item) client.Set(item)
} else { } else {
client.Delete(oldsid) client.Delete(oldsid)
item.Key = sid item.Key = sid
item.Value = item.Value item.Value = item.Value
item.Expiration = int32(rp.maxlifetime) item.Expiration = int32(p.maxlifetime)
client.Set(item) client.Set(item)
contain = item.Value contain = item.Value
} }
@ -194,31 +203,19 @@ func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.RawStore,
} }
} }
rs := &MemcacheSessionStore{sid: sid, values: kv, maxlifetime: rp.maxlifetime} rs := &MemcacheSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
return rs, nil return rs, nil
} }
// delete redis session by id // Count counts and returns number of sessions.
func (rp *MemProvider) SessionDestroy(sid string) error { func (p *MemProvider) Count() int {
if client == nil { // FIXME
if err := rp.connectInit(); err != nil {
return err
}
}
return client.Delete(sid)
}
// Impelment method, no used.
func (rp *MemProvider) SessionGC() {
return
}
// @todo
func (rp *MemProvider) SessionAll() int {
return 0 return 0
} }
// GC calls GC to clean expired sessions.
func (p *MemProvider) GC() {}
func init() { func init() {
session.Register("memcache", mempder) session.Register("memcache", &MemProvider{})
} }

View File

@ -15,17 +15,8 @@
package session package session
// mysql session support need create table as sql:
// CREATE TABLE `session` (
// `session_key` char(64) NOT NULL,
// session_data` blob,
// `session_expiry` int(11) unsigned NOT NULL,
// PRIMARY KEY (`session_key`)
// ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
import ( import (
"database/sql" "database/sql"
"net/http"
"sync" "sync"
"time" "time"
@ -34,96 +25,90 @@ import (
"github.com/macaron-contrib/session" "github.com/macaron-contrib/session"
) )
var mysqlpder = &MysqlProvider{} // MysqlSessionStore represents a mysql session store implementation.
// mysql session store
type MysqlSessionStore struct { type MysqlSessionStore struct {
c *sql.DB c *sql.DB
sid string sid string
lock sync.RWMutex lock sync.RWMutex
values map[interface{}]interface{} data map[interface{}]interface{}
} }
// set value in mysql session. // Set sets value to given key in session.
// it is temp value in map. func (s *MysqlSessionStore) Set(key, val interface{}) error {
func (st *MysqlSessionStore) Set(key, value interface{}) error { s.lock.Lock()
st.lock.Lock() defer s.lock.Unlock()
defer st.lock.Unlock()
st.values[key] = value s.data[key] = val
return nil return nil
} }
// get value from mysql session // Get gets value by given key in session.
func (st *MysqlSessionStore) Get(key interface{}) interface{} { func (s *MysqlSessionStore) Get(key interface{}) interface{} {
st.lock.RLock() s.lock.RLock()
defer st.lock.RUnlock() defer s.lock.RUnlock()
if v, ok := st.values[key]; ok {
return v return s.data[key]
} else {
return nil
}
} }
// delete value in mysql session // Delete delete a key from session.
func (st *MysqlSessionStore) Delete(key interface{}) error { func (s *MysqlSessionStore) Delete(key interface{}) error {
st.lock.Lock() s.lock.Lock()
defer st.lock.Unlock() defer s.lock.Unlock()
delete(st.values, key)
delete(s.data, key)
return nil return nil
} }
// clear all values in mysql session // ID returns current session ID.
func (st *MysqlSessionStore) Flush() error { func (s *MysqlSessionStore) ID() string {
st.lock.Lock() return s.sid
defer st.lock.Unlock()
st.values = make(map[interface{}]interface{})
return nil
} }
// get session id of this mysql session store // Release releases resource and save data to provider.
func (st *MysqlSessionStore) SessionID() string { func (s *MysqlSessionStore) Release() error {
return st.sid defer s.c.Close()
} data, err := session.EncodeGob(s.data)
// save mysql session values to database.
// must call this method to save values to database.
func (st *MysqlSessionStore) SessionRelease(w http.ResponseWriter) {
defer st.c.Close()
b, err := session.EncodeGob(st.values)
if err != nil { if err != nil {
return return err
} }
st.c.Exec("UPDATE session set `session_data`=?, `session_expiry`=? where session_key=?", _, err = s.c.Exec("UPDATE session set `session_data`=?, `session_expiry`=? where session_key=?",
b, time.Now().Unix(), st.sid) data, time.Now().Unix(), s.sid)
return err
} }
// mysql session provider // Flush deletes all session data.
func (s *MysqlSessionStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = make(map[interface{}]interface{})
return nil
}
// MysqlProvider represents a mysql session provider implementation.
type MysqlProvider struct { type MysqlProvider struct {
maxlifetime int64 maxlifetime int64
savePath string connStr string
} }
// connect to mysql func (p *MysqlProvider) connectInit() *sql.DB {
func (mp *MysqlProvider) connectInit() *sql.DB { db, e := sql.Open("mysql", p.connStr)
db, e := sql.Open("mysql", mp.savePath)
if e != nil { if e != nil {
return nil return nil
} }
return db return db
} }
// init mysql session. // Init initializes memory session provider.
// savepath is the connection string of mysql. func (p *MysqlProvider) Init(maxlifetime int64, connStr string) error {
func (mp *MysqlProvider) SessionInit(maxlifetime int64, savePath string) error { p.maxlifetime = maxlifetime
mp.maxlifetime = maxlifetime p.connStr = connStr
mp.savePath = savePath
return nil return nil
} }
// get mysql session by sid // Read returns raw session store by session ID.
func (mp *MysqlProvider) SessionRead(sid string) (session.RawStore, error) { func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
c := mp.connectInit() c := p.connectInit()
row := c.QueryRow("select session_data from session where session_key=?", sid) row := c.QueryRow("select session_data from session where session_key=?", sid)
var sessiondata []byte var sessiondata []byte
err := row.Scan(&sessiondata) err := row.Scan(&sessiondata)
@ -140,14 +125,15 @@ func (mp *MysqlProvider) SessionRead(sid string) (session.RawStore, error) {
return nil, err return nil, err
} }
} }
rs := &MysqlSessionStore{c: c, sid: sid, values: kv} rs := &MysqlSessionStore{c: c, sid: sid, data: kv}
return rs, nil return rs, nil
} }
// check mysql session exist // Exist returns true if session with given ID exists.
func (mp *MysqlProvider) SessionExist(sid string) bool { func (p *MysqlProvider) Exist(sid string) bool {
c := mp.connectInit() c := p.connectInit()
defer c.Close() defer c.Close()
row := c.QueryRow("select session_data from session where session_key=?", sid) row := c.QueryRow("select session_data from session where session_key=?", sid)
var sessiondata []byte var sessiondata []byte
err := row.Scan(&sessiondata) err := row.Scan(&sessiondata)
@ -158,9 +144,18 @@ func (mp *MysqlProvider) SessionExist(sid string) bool {
} }
} }
// generate new sid for mysql session // Destory deletes a session by session ID.
func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (session.RawStore, error) { func (p *MysqlProvider) Destory(sid string) (err error) {
c := mp.connectInit() c := p.connectInit()
if _, err = c.Exec("DELETE FROM session where session_key=?", sid); err != nil {
return err
}
return c.Close()
}
// 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) row := c.QueryRow("select session_data from session where session_key=?", oldsid)
var sessiondata []byte var sessiondata []byte
err := row.Scan(&sessiondata) err := row.Scan(&sessiondata)
@ -177,30 +172,15 @@ func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (session.RawStore
return nil, err return nil, err
} }
} }
rs := &MysqlSessionStore{c: c, sid: sid, values: kv} rs := &MysqlSessionStore{c: c, sid: sid, data: kv}
return rs, nil return rs, nil
} }
// delete mysql session by sid // Count counts and returns number of sessions.
func (mp *MysqlProvider) SessionDestroy(sid string) error { func (p *MysqlProvider) Count() int {
c := mp.connectInit() c := p.connectInit()
c.Exec("DELETE FROM session where session_key=?", sid)
c.Close()
return nil
}
// delete expired values in mysql session
func (mp *MysqlProvider) SessionGC() {
c := mp.connectInit()
c.Exec("DELETE from session where session_expiry < ?", time.Now().Unix()-mp.maxlifetime)
c.Close()
return
}
// count values in mysql session
func (mp *MysqlProvider) SessionAll() int {
c := mp.connectInit()
defer c.Close() defer c.Close()
var total int var total int
err := c.QueryRow("SELECT count(*) as num from session").Scan(&total) err := c.QueryRow("SELECT count(*) as num from session").Scan(&total)
if err != nil { if err != nil {
@ -209,6 +189,13 @@ func (mp *MysqlProvider) SessionAll() int {
return total return total
} }
func init() { // GC calls GC to clean expired sessions.
session.Register("mysql", mysqlpder) func (mp *MysqlProvider) GC() {
c := mp.connectInit()
c.Exec("DELETE from session where session_expiry < ?", time.Now().Unix()-mp.maxlifetime)
c.Close()
}
func init() {
session.Register("mysql", &MysqlProvider{})
} }

View File

@ -15,38 +15,8 @@
package session package session
/*
beego session provider for postgresql
-------------------------------------
depends on github.com/lib/pq:
go install github.com/lib/pq
needs this table in your database:
CREATE TABLE session (
session_key char(64) NOT NULL,
session_data bytea,
session_expiry timestamp NOT NULL,
CONSTRAINT session_key PRIMARY KEY(session_key)
);
will be activated with these settings in app.conf:
SessionOn = true
SessionProvider = postgresql
SessionSavePath = "user=a password=b dbname=c sslmode=disable"
SessionName = session
*/
import ( import (
"database/sql" "database/sql"
"net/http"
"sync" "sync"
"time" "time"
@ -55,96 +25,93 @@ import (
"github.com/macaron-contrib/session" "github.com/macaron-contrib/session"
) )
var postgresqlpder = &PostgresqlProvider{} // PostgresqlSessionStore represents a postgresql session store implementation.
// postgresql session store
type PostgresqlSessionStore struct { type PostgresqlSessionStore struct {
c *sql.DB c *sql.DB
sid string sid string
lock sync.RWMutex lock sync.RWMutex
values map[interface{}]interface{} data map[interface{}]interface{}
} }
// set value in postgresql session. // Set sets value to given key in session.
// it is temp value in map. func (s *PostgresqlSessionStore) Set(key, value interface{}) error {
func (st *PostgresqlSessionStore) Set(key, value interface{}) error { s.lock.Lock()
st.lock.Lock() defer s.lock.Unlock()
defer st.lock.Unlock()
st.values[key] = value s.data[key] = value
return nil return nil
} }
// get value from postgresql session // Get gets value by given key in session.
func (st *PostgresqlSessionStore) Get(key interface{}) interface{} { func (s *PostgresqlSessionStore) Get(key interface{}) interface{} {
st.lock.RLock() s.lock.RLock()
defer st.lock.RUnlock() defer s.lock.RUnlock()
if v, ok := st.values[key]; ok {
return v return s.data[key]
} else {
return nil
}
} }
// delete value in postgresql session // Delete delete a key from session.
func (st *PostgresqlSessionStore) Delete(key interface{}) error { func (s *PostgresqlSessionStore) Delete(key interface{}) error {
st.lock.Lock() s.lock.Lock()
defer st.lock.Unlock() defer s.lock.Unlock()
delete(st.values, key)
delete(s.data, key)
return nil return nil
} }
// clear all values in postgresql session // ID returns current session ID.
func (st *PostgresqlSessionStore) Flush() error { func (s *PostgresqlSessionStore) ID() string {
st.lock.Lock() return s.sid
defer st.lock.Unlock()
st.values = make(map[interface{}]interface{})
return nil
}
// get session id of this postgresql session store
func (st *PostgresqlSessionStore) SessionID() string {
return st.sid
} }
// save postgresql session values to database. // save postgresql session values to database.
// must call this method to save values to database. // must call this method to save values to database.
func (st *PostgresqlSessionStore) SessionRelease(w http.ResponseWriter) { func (s *PostgresqlSessionStore) Release() error {
defer st.c.Close() defer s.c.Close()
b, err := session.EncodeGob(st.values)
data, err := session.EncodeGob(s.data)
if err != nil { if err != nil {
return return err
}
st.c.Exec("UPDATE session set session_data=$1, session_expiry=$2 where session_key=$3",
b, time.Now().Format(time.RFC3339), st.sid)
} }
// postgresql session provider _, 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 { type PostgresqlProvider struct {
maxlifetime int64 maxlifetime int64
savePath string connStr string
} }
// connect to postgresql func (p *PostgresqlProvider) connectInit() *sql.DB {
func (mp *PostgresqlProvider) connectInit() *sql.DB { db, e := sql.Open("postgres", p.connStr)
db, e := sql.Open("postgres", mp.savePath)
if e != nil { if e != nil {
return nil return nil
} }
return db return db
} }
// init postgresql session. // Init initializes memory session provider.
// savepath is the connection string of postgresql. func (p *PostgresqlProvider) Init(maxlifetime int64, connStr string) error {
func (mp *PostgresqlProvider) SessionInit(maxlifetime int64, savePath string) error { p.maxlifetime = maxlifetime
mp.maxlifetime = maxlifetime p.connStr = connStr
mp.savePath = savePath
return nil return nil
} }
// get postgresql session by sid // Read returns raw session store by session ID.
func (mp *PostgresqlProvider) SessionRead(sid string) (session.RawStore, error) { func (p *PostgresqlProvider) Read(sid string) (session.RawStore, error) {
c := mp.connectInit() c := p.connectInit()
row := c.QueryRow("select session_data from session where session_key=$1", sid) row := c.QueryRow("select session_data from session where session_key=$1", sid)
var sessiondata []byte var sessiondata []byte
err := row.Scan(&sessiondata) err := row.Scan(&sessiondata)
@ -168,13 +135,13 @@ func (mp *PostgresqlProvider) SessionRead(sid string) (session.RawStore, error)
return nil, err return nil, err
} }
} }
rs := &PostgresqlSessionStore{c: c, sid: sid, values: kv} rs := &PostgresqlSessionStore{c: c, sid: sid, data: kv}
return rs, nil return rs, nil
} }
// check postgresql session exist // Exist returns true if session with given ID exists.
func (mp *PostgresqlProvider) SessionExist(sid string) bool { func (p *PostgresqlProvider) Exist(sid string) bool {
c := mp.connectInit() c := p.connectInit()
defer c.Close() defer c.Close()
row := c.QueryRow("select session_data from session where session_key=$1", sid) row := c.QueryRow("select session_data from session where session_key=$1", sid)
var sessiondata []byte var sessiondata []byte
@ -187,9 +154,18 @@ func (mp *PostgresqlProvider) SessionExist(sid string) bool {
} }
} }
// generate new sid for postgresql session // Destory deletes a session by session ID.
func (mp *PostgresqlProvider) SessionRegenerate(oldsid, sid string) (session.RawStore, error) { func (p *PostgresqlProvider) Destory(sid string) (err error) {
c := mp.connectInit() 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) row := c.QueryRow("select session_data from session where session_key=$1", oldsid)
var sessiondata []byte var sessiondata []byte
err := row.Scan(&sessiondata) err := row.Scan(&sessiondata)
@ -207,29 +183,13 @@ func (mp *PostgresqlProvider) SessionRegenerate(oldsid, sid string) (session.Raw
return nil, err return nil, err
} }
} }
rs := &PostgresqlSessionStore{c: c, sid: sid, values: kv} rs := &PostgresqlSessionStore{c: c, sid: sid, data: kv}
return rs, nil return rs, nil
} }
// delete postgresql session by sid // Count counts and returns number of sessions.
func (mp *PostgresqlProvider) SessionDestroy(sid string) error { func (p *PostgresqlProvider) Count() int {
c := mp.connectInit() c := p.connectInit()
c.Exec("DELETE FROM session where session_key=$1", sid)
c.Close()
return nil
}
// delete expired values in postgresql session
func (mp *PostgresqlProvider) SessionGC() {
c := mp.connectInit()
c.Exec("DELETE from session where EXTRACT(EPOCH FROM (current_timestamp - session_expiry)) > $1", mp.maxlifetime)
c.Close()
return
}
// count values in postgresql session
func (mp *PostgresqlProvider) SessionAll() int {
c := mp.connectInit()
defer c.Close() defer c.Close()
var total int var total int
err := c.QueryRow("SELECT count(*) as num from session").Scan(&total) err := c.QueryRow("SELECT count(*) as num from session").Scan(&total)
@ -239,6 +199,13 @@ func (mp *PostgresqlProvider) SessionAll() int {
return total return total
} }
func init() { // GC calls GC to clean expired sessions.
session.Register("postgresql", postgresqlpder) 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{})
} }

View File

@ -16,7 +16,6 @@
package session package session
import ( import (
"net/http"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -26,126 +25,125 @@ import (
"github.com/macaron-contrib/session" "github.com/macaron-contrib/session"
) )
var redispder = &RedisProvider{}
// redis max pool size // redis max pool size
var MAX_POOL_SIZE = 100 var MAX_POOL_SIZE = 100
var redisPool chan redis.Conn var redisPool chan redis.Conn
// redis session store // RedisSessionStore represents a redis session store implementation.
type RedisSessionStore struct { type RedisSessionStore struct {
p *redis.Pool p *redis.Pool
sid string sid string
lock sync.RWMutex lock sync.RWMutex
values map[interface{}]interface{} data map[interface{}]interface{}
maxlifetime int64 maxlifetime int64
} }
// set value in redis session // Set sets value to given key in session.
func (rs *RedisSessionStore) Set(key, value interface{}) error { func (s *RedisSessionStore) Set(key, val interface{}) error {
rs.lock.Lock() s.lock.Lock()
defer rs.lock.Unlock() defer s.lock.Unlock()
rs.values[key] = value
s.data[key] = val
return nil return nil
} }
// get value in redis session // Get gets value by given key in session.
func (rs *RedisSessionStore) Get(key interface{}) interface{} { func (s *RedisSessionStore) Get(key interface{}) interface{} {
rs.lock.RLock() s.lock.RLock()
defer rs.lock.RUnlock() defer s.lock.RUnlock()
if v, ok := rs.values[key]; ok {
return v return s.data[key]
} else {
return nil
}
} }
// delete value in redis session // Delete delete a key from session.
func (rs *RedisSessionStore) Delete(key interface{}) error { func (s *RedisSessionStore) Delete(key interface{}) error {
rs.lock.Lock() s.lock.Lock()
defer rs.lock.Unlock() defer s.lock.Unlock()
delete(rs.values, key)
delete(s.data, key)
return nil return nil
} }
// clear all values in redis session // ID returns current session ID.
func (rs *RedisSessionStore) Flush() error { func (s *RedisSessionStore) ID() string {
rs.lock.Lock() return s.sid
defer rs.lock.Unlock()
rs.values = make(map[interface{}]interface{})
return nil
} }
// get redis session id // Release releases resource and save data to provider.
func (rs *RedisSessionStore) SessionID() string { func (s *RedisSessionStore) Release() error {
return rs.sid c := s.p.Get()
}
// save session values to redis
func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) {
c := rs.p.Get()
defer c.Close() defer c.Close()
b, err := session.EncodeGob(rs.values) data, err := session.EncodeGob(s.data)
if err != nil { if err != nil {
return return err
} }
c.Do("SETEX", rs.sid, rs.maxlifetime, string(b)) _, err = c.Do("SETEX", s.sid, s.maxlifetime, string(data))
return err
} }
// redis session provider // Flush deletes all session data.
func (s *RedisSessionStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = make(map[interface{}]interface{})
return nil
}
// RedisProvider represents a redis session provider implementation.
type RedisProvider struct { type RedisProvider struct {
maxlifetime int64 maxlifetime int64
savePath string connAddr string
poolsize int poolsize int
password string password string
poollist *redis.Pool poollist *redis.Pool
} }
// init redis session // Init initializes memory session provider.
// savepath like redis server addr,pool size,password // connStr: <redis server addr>,<pool size>,<password>
// e.g. 127.0.0.1:6379,100,astaxie // e.g. 127.0.0.1:6379,100,macaron
func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { func (p *RedisProvider) Init(maxlifetime int64, connStr string) error {
rp.maxlifetime = maxlifetime p.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",") configs := strings.Split(connStr, ",")
if len(configs) > 0 { if len(configs) > 0 {
rp.savePath = configs[0] p.connAddr = configs[0]
} }
if len(configs) > 1 { if len(configs) > 1 {
poolsize, err := strconv.Atoi(configs[1]) poolsize, err := strconv.Atoi(configs[1])
if err != nil || poolsize <= 0 { if err != nil || poolsize <= 0 {
rp.poolsize = MAX_POOL_SIZE p.poolsize = MAX_POOL_SIZE
} else { } else {
rp.poolsize = poolsize p.poolsize = poolsize
} }
} else { } else {
rp.poolsize = MAX_POOL_SIZE p.poolsize = MAX_POOL_SIZE
} }
if len(configs) > 2 { if len(configs) > 2 {
rp.password = configs[2] p.password = configs[2]
} }
rp.poollist = redis.NewPool(func() (redis.Conn, error) { p.poollist = redis.NewPool(func() (redis.Conn, error) {
c, err := redis.Dial("tcp", rp.savePath) c, err := redis.Dial("tcp", p.connAddr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rp.password != "" { if p.password != "" {
if _, err := c.Do("AUTH", rp.password); err != nil { if _, err := c.Do("AUTH", p.password); err != nil {
c.Close() c.Close()
return nil, err return nil, err
} }
} }
return c, err return c, err
}, rp.poolsize) }, p.poolsize)
return rp.poollist.Get().Err() return p.poollist.Get().Err()
} }
// read redis session by sid // Read returns raw session store by session ID.
func (rp *RedisProvider) SessionRead(sid string) (session.RawStore, error) { func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
c := rp.poollist.Get() c := p.poollist.Get()
defer c.Close() defer c.Close()
kvs, err := redis.String(c.Do("GET", sid)) kvs, err := redis.String(c.Do("GET", sid))
@ -159,13 +157,13 @@ func (rp *RedisProvider) SessionRead(sid string) (session.RawStore, error) {
} }
} }
rs := &RedisSessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime} rs := &RedisSessionStore{p: p.poollist, sid: sid, data: kv, maxlifetime: p.maxlifetime}
return rs, nil return rs, nil
} }
// check redis session exist by sid // Exist returns true if session with given ID exists.
func (rp *RedisProvider) SessionExist(sid string) bool { func (p *RedisProvider) Exist(sid string) bool {
c := rp.poollist.Get() c := p.poollist.Get()
defer c.Close() defer c.Close()
if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 { if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 {
@ -175,19 +173,28 @@ func (rp *RedisProvider) SessionExist(sid string) bool {
} }
} }
// generate new sid for redis session // Destory deletes a session by session ID.
func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (session.RawStore, error) { func (p *RedisProvider) Destory(sid string) error {
c := rp.poollist.Get() c := p.poollist.Get()
defer c.Close()
_, err := c.Do("DEL", sid)
return 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() defer c.Close()
if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 { if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 {
// oldsid doesn't exists, set the new sid directly // oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error // ignore error here, since if it return error
// the existed value will be 0 // the existed value will be 0
c.Do("SET", sid, "", "EX", rp.maxlifetime) c.Do("SET", sid, "", "EX", p.maxlifetime)
} else { } else {
c.Do("RENAME", oldsid, sid) c.Do("RENAME", oldsid, sid)
c.Do("EXPIRE", sid, rp.maxlifetime) c.Do("EXPIRE", sid, p.maxlifetime)
} }
kvs, err := redis.String(c.Do("GET", sid)) kvs, err := redis.String(c.Do("GET", sid))
@ -201,29 +208,19 @@ func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (session.RawStore
} }
} }
rs := &RedisSessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime} rs := &RedisSessionStore{p: p.poollist, sid: sid, data: kv, maxlifetime: p.maxlifetime}
return rs, nil return rs, nil
} }
// delete redis session by id // Count counts and returns number of sessions.
func (rp *RedisProvider) SessionDestroy(sid string) error { func (p *RedisProvider) Count() int {
c := rp.poollist.Get() // FIXME
defer c.Close()
c.Do("DEL", sid)
return nil
}
// Impelment method, no used.
func (rp *RedisProvider) SessionGC() {
return
}
// @todo
func (rp *RedisProvider) SessionAll() int {
return 0 return 0
} }
// GC calls GC to clean expired sessions.
func (_ *RedisProvider) GC() {}
func init() { func init() {
session.Register("redis", redispder) session.Register("redis", &RedisProvider{})
} }

View File

@ -1,186 +0,0 @@
// Copyright 2013 Beego Authors
//
// 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 (
"crypto/aes"
"crypto/cipher"
"encoding/json"
"net/http"
"net/url"
"sync"
)
var cookiepder = &CookieProvider{}
// Cookie SessionStore
type CookieSessionStore struct {
sid string
values map[interface{}]interface{} // session data
lock sync.RWMutex
}
// Set value to cookie session.
// the value are encoded as gob with hash block string.
func (st *CookieSessionStore) Set(key, value interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
st.values[key] = value
return nil
}
// Get value from cookie session
func (st *CookieSessionStore) Get(key interface{}) interface{} {
st.lock.RLock()
defer st.lock.RUnlock()
if v, ok := st.values[key]; ok {
return v
} else {
return nil
}
}
// Delete value in cookie session
func (st *CookieSessionStore) Delete(key interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
delete(st.values, key)
return nil
}
// Clean all values in cookie session
func (st *CookieSessionStore) Flush() error {
st.lock.Lock()
defer st.lock.Unlock()
st.values = make(map[interface{}]interface{})
return nil
}
// Return id of this cookie session
func (st *CookieSessionStore) SessionID() string {
return st.sid
}
// Write cookie session to http response cookie
func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) {
str, err := encodeCookie(cookiepder.block,
cookiepder.config.SecurityKey,
cookiepder.config.SecurityName,
st.values)
if err != nil {
return
}
cookie := &http.Cookie{Name: cookiepder.config.CookieName,
Value: url.QueryEscape(str),
Path: cookiepder.config.CookiePath,
HttpOnly: true,
Secure: cookiepder.config.Secure,
MaxAge: cookiepder.config.Maxage}
http.SetCookie(w, cookie)
return
}
type cookieConfig struct {
SecurityKey string `json:"securityKey"`
BlockKey string `json:"blockKey"`
SecurityName string `json:"securityName"`
CookieName string `json:"cookieName"`
CookiePath string `json:"cookiePath"`
Secure bool `json:"secure"`
Maxage int `json:"maxage"`
}
// Cookie session provider
type CookieProvider struct {
maxlifetime int64
config *cookieConfig
block cipher.Block
}
// Init cookie session provider with max lifetime and config json.
// maxlifetime is ignored.
// json config:
// securityKey - hash string
// blockKey - gob encode hash string. it's saved as aes crypto.
// securityName - recognized name in encoded cookie string
// cookieName - cookie name
// maxage - cookie max life time.
func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error {
pder.config = &cookieConfig{}
err := json.Unmarshal([]byte(config), pder.config)
if err != nil {
return err
}
if pder.config.BlockKey == "" {
pder.config.BlockKey = string(generateRandomKey(16))
}
if pder.config.SecurityName == "" {
pder.config.SecurityName = string(generateRandomKey(20))
}
pder.block, err = aes.NewCipher([]byte(pder.config.BlockKey))
if err != nil {
return err
}
pder.maxlifetime = maxlifetime
return nil
}
// Get SessionStore in cooke.
// decode cooke string to map and put into SessionStore with sid.
func (pder *CookieProvider) SessionRead(sid string) (RawStore, error) {
maps, _ := decodeCookie(pder.block,
pder.config.SecurityKey,
pder.config.SecurityName,
sid, pder.maxlifetime)
if maps == nil {
maps = make(map[interface{}]interface{})
}
rs := &CookieSessionStore{sid: sid, values: maps}
return rs, nil
}
// Cookie session is always existed
func (pder *CookieProvider) SessionExist(sid string) bool {
return true
}
// Implement method, no used.
func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (RawStore, error) {
return nil, nil
}
// Implement method, no used.
func (pder *CookieProvider) SessionDestroy(sid string) error {
return nil
}
// Implement method, no used.
func (pder *CookieProvider) SessionGC() {
return
}
// Implement method, return 0.
func (pder *CookieProvider) SessionAll() int {
return 0
}
// Implement method, no used.
func (pder *CookieProvider) SessionUpdate(sid string) error {
return nil
}
func init() {
Register("cookie", cookiepder)
}

View File

@ -1,60 +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 (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestCookie(t *testing.T) {
config := &Config{
CookieName: "gosessionid",
Gclifetime: 3600,
ProviderConfig: "{\"cookieName\":\"gosessionid\",\"securityKey\":\"macaroncookiehashkey\"}",
}
globalSessions, err := NewManager("cookie", config)
if err != nil {
t.Fatal("init cookie session err", err)
}
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
sess, err := globalSessions.SessionStart(w, r)
if err != nil {
t.Fatal("start session,", err)
}
err = sess.Set("username", "astaxie")
if err != nil {
t.Fatal("set error,", err)
}
if username := sess.Get("username"); username != "astaxie" {
t.Fatal("get username error")
}
sess.SessionRelease(w)
if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" {
t.Fatal("setcookie error")
} else {
parts := strings.Split(strings.TrimSpace(cookiestr), ";")
for k, v := range parts {
nameval := strings.Split(v, "=")
if k == 0 && nameval[0] != "gosessionid" {
t.Fatal("error")
}
}
}
}

View File

@ -1,288 +0,0 @@
// Copyright 2013 Beego Authors
//
// 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 (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"sync"
"time"
)
var (
filepder = &FileProvider{}
gcmaxlifetime int64
)
// File session store
type FileSessionStore struct {
f *os.File
sid string
lock sync.RWMutex
values map[interface{}]interface{}
}
// Set value to file session
func (fs *FileSessionStore) Set(key, value interface{}) error {
fs.lock.Lock()
defer fs.lock.Unlock()
fs.values[key] = value
return nil
}
// Get value from file session
func (fs *FileSessionStore) Get(key interface{}) interface{} {
fs.lock.RLock()
defer fs.lock.RUnlock()
if v, ok := fs.values[key]; ok {
return v
} else {
return nil
}
}
// Delete value in file session by given key
func (fs *FileSessionStore) Delete(key interface{}) error {
fs.lock.Lock()
defer fs.lock.Unlock()
delete(fs.values, key)
return nil
}
// Clean all values in file session
func (fs *FileSessionStore) Flush() error {
fs.lock.Lock()
defer fs.lock.Unlock()
fs.values = make(map[interface{}]interface{})
return nil
}
// Get file session store id
func (fs *FileSessionStore) SessionID() string {
return fs.sid
}
// Write file session to local file with Gob string
func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
b, err := EncodeGob(fs.values)
if err != nil {
return
}
_, err = os.Stat(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid))
var f *os.File
if err == nil {
f, err = os.OpenFile(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid), os.O_RDWR, 0777)
} else if os.IsNotExist(err) {
f, err = os.Create(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid))
} else {
return
}
f.Truncate(0)
f.Seek(0, 0)
f.Write(b)
f.Close()
}
// File session provider
type FileProvider struct {
lock sync.RWMutex
maxlifetime int64
savePath string
}
// Init file session provider.
// savePath sets the session files path.
func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error {
fp.maxlifetime = maxlifetime
fp.savePath = savePath
return nil
}
// Read file session by sid.
// if file is not exist, create it.
// the file path is generated from sid string.
func (fp *FileProvider) SessionRead(sid string) (RawStore, error) {
filepder.lock.Lock()
defer filepder.lock.Unlock()
err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777)
if err != nil {
println(err.Error())
}
_, err = os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
var f *os.File
if err == nil {
f, err = os.OpenFile(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), os.O_RDWR, 0777)
} else if os.IsNotExist(err) {
f, err = os.Create(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
} else {
return nil, err
}
os.Chtimes(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), time.Now(), time.Now())
var kv map[interface{}]interface{}
b, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
if len(b) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = DecodeGob(b)
if err != nil {
return nil, err
}
}
f.Close()
ss := &FileSessionStore{sid: sid, values: kv}
return ss, nil
}
// Check file session exist.
// it checkes the file named from sid exist or not.
func (fp *FileProvider) SessionExist(sid string) bool {
filepder.lock.Lock()
defer filepder.lock.Unlock()
_, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
if err == nil {
return true
} else {
return false
}
}
// Remove all files in this save path
func (fp *FileProvider) SessionDestroy(sid string) error {
filepder.lock.Lock()
defer filepder.lock.Unlock()
os.Remove(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
return nil
}
// Recycle files in save path
func (fp *FileProvider) SessionGC() {
filepder.lock.Lock()
defer filepder.lock.Unlock()
gcmaxlifetime = fp.maxlifetime
filepath.Walk(fp.savePath, gcpath)
}
// Get active file session number.
// it walks save path to count files.
func (fp *FileProvider) SessionAll() int {
a := &activeSession{}
err := filepath.Walk(fp.savePath, func(path string, f os.FileInfo, err error) error {
return a.visit(path, f, err)
})
if err != nil {
fmt.Printf("filepath.Walk() returned %v\n", err)
return 0
}
return a.total
}
// Generate new sid for file session.
// it delete old file and create new file named from new sid.
func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (RawStore, error) {
filepder.lock.Lock()
defer filepder.lock.Unlock()
err := os.MkdirAll(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1])), 0777)
if err != nil {
println(err.Error())
}
err = os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777)
if err != nil {
println(err.Error())
}
_, err = os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
var newf *os.File
if err == nil {
return nil, errors.New("newsid exist")
} else if os.IsNotExist(err) {
newf, err = os.Create(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
}
_, err = os.Stat(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1]), oldsid))
var f *os.File
if err == nil {
f, err = os.OpenFile(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1]), oldsid), os.O_RDWR, 0777)
io.Copy(newf, f)
} else if os.IsNotExist(err) {
newf, err = os.Create(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
} else {
return nil, err
}
f.Close()
os.Remove(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1])))
os.Chtimes(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), time.Now(), time.Now())
var kv map[interface{}]interface{}
b, err := ioutil.ReadAll(newf)
if err != nil {
return nil, err
}
if len(b) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = DecodeGob(b)
if err != nil {
return nil, err
}
}
ss := &FileSessionStore{sid: sid, values: kv}
return ss, nil
}
// remove file in save path if expired
func gcpath(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if (info.ModTime().Unix() + gcmaxlifetime) < time.Now().Unix() {
os.Remove(path)
}
return nil
}
type activeSession struct {
total int
}
func (self *activeSession) visit(paths string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
self.total = self.total + 1
return nil
}
func init() {
Register("file", filepder)
}

View File

@ -1,199 +0,0 @@
// Copyright 2013 Beego Authors
//
// 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 (
"container/list"
"net/http"
"sync"
"time"
)
var mempder = &MemProvider{list: list.New(), sessions: make(map[string]*list.Element)}
// memory session store.
// it saved sessions in a map in memory.
type MemSessionStore struct {
sid string //session id
timeAccessed time.Time //last access time
value map[interface{}]interface{} //session store
lock sync.RWMutex
}
// set value to memory session
func (st *MemSessionStore) Set(key, value interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
st.value[key] = value
return nil
}
// get value from memory session by key
func (st *MemSessionStore) Get(key interface{}) interface{} {
st.lock.RLock()
defer st.lock.RUnlock()
if v, ok := st.value[key]; ok {
return v
} else {
return nil
}
}
// delete in memory session by key
func (st *MemSessionStore) Delete(key interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
delete(st.value, key)
return nil
}
// clear all values in memory session
func (st *MemSessionStore) Flush() error {
st.lock.Lock()
defer st.lock.Unlock()
st.value = make(map[interface{}]interface{})
return nil
}
// get this id of memory session store
func (st *MemSessionStore) SessionID() string {
return st.sid
}
// Implement method, no used.
func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) {
}
type MemProvider struct {
lock sync.RWMutex // locker
sessions map[string]*list.Element // map in memory
list *list.List // for gc
maxlifetime int64
savePath string
}
// init memory session
func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error {
pder.maxlifetime = maxlifetime
pder.savePath = savePath
return nil
}
// get memory session store by sid
func (pder *MemProvider) SessionRead(sid string) (RawStore, error) {
pder.lock.RLock()
if element, ok := pder.sessions[sid]; ok {
go pder.SessionUpdate(sid)
pder.lock.RUnlock()
return element.Value.(*MemSessionStore), nil
} else {
pder.lock.RUnlock()
pder.lock.Lock()
newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})}
element := pder.list.PushBack(newsess)
pder.sessions[sid] = element
pder.lock.Unlock()
return newsess, nil
}
}
// check session store exist in memory session by sid
func (pder *MemProvider) SessionExist(sid string) bool {
pder.lock.RLock()
defer pder.lock.RUnlock()
if _, ok := pder.sessions[sid]; ok {
return true
} else {
return false
}
}
// generate new sid for session store in memory session
func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (RawStore, error) {
pder.lock.RLock()
if element, ok := pder.sessions[oldsid]; ok {
go pder.SessionUpdate(oldsid)
pder.lock.RUnlock()
pder.lock.Lock()
element.Value.(*MemSessionStore).sid = sid
pder.sessions[sid] = element
delete(pder.sessions, oldsid)
pder.lock.Unlock()
return element.Value.(*MemSessionStore), nil
} else {
pder.lock.RUnlock()
pder.lock.Lock()
newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})}
element := pder.list.PushBack(newsess)
pder.sessions[sid] = element
pder.lock.Unlock()
return newsess, nil
}
}
// delete session store in memory session by id
func (pder *MemProvider) SessionDestroy(sid string) error {
pder.lock.Lock()
defer pder.lock.Unlock()
if element, ok := pder.sessions[sid]; ok {
delete(pder.sessions, sid)
pder.list.Remove(element)
return nil
}
return nil
}
// clean expired session stores in memory session
func (pder *MemProvider) SessionGC() {
pder.lock.RLock()
for {
element := pder.list.Back()
if element == nil {
break
}
if (element.Value.(*MemSessionStore).timeAccessed.Unix() + pder.maxlifetime) < time.Now().Unix() {
pder.lock.RUnlock()
pder.lock.Lock()
pder.list.Remove(element)
delete(pder.sessions, element.Value.(*MemSessionStore).sid)
pder.lock.Unlock()
pder.lock.RLock()
} else {
break
}
}
pder.lock.RUnlock()
}
// get count number of memory session
func (pder *MemProvider) SessionAll() int {
return pder.list.Len()
}
// expand time of session store by id in memory session
func (pder *MemProvider) SessionUpdate(sid string) error {
pder.lock.Lock()
defer pder.lock.Unlock()
if element, ok := pder.sessions[sid]; ok {
element.Value.(*MemSessionStore).timeAccessed = time.Now()
pder.list.MoveToFront(element)
return nil
}
return nil
}
func init() {
Register("memory", mempder)
}

View File

@ -1,59 +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 (
"net/http"
"net/http/httptest"
"strings"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestMem(t *testing.T) {
Convey("Memory provider", t, func() {
config := &Config{
CookieName: "gosessionid",
Gclifetime: 10,
}
globalSessions, err := NewManager("memory", config)
So(err, ShouldBeNil)
go globalSessions.GC()
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
sess, err := globalSessions.SessionStart(resp, req)
if err != nil {
t.Fatal("start session,", err)
}
defer sess.SessionRelease(resp)
So(sess.Set("username", "Unknwon"), ShouldBeNil)
So(sess.Get("username"), ShouldEqual, "Unknwon")
cookiestr := resp.Header().Get("Set-Cookie")
So(cookiestr, ShouldNotBeEmpty)
parts := strings.Split(strings.TrimSpace(cookiestr), ";")
for _, v := range parts {
nameval := strings.Split(v, "=")
So(nameval[0], ShouldEqual, "gosessionid")
break
}
})
}

View File

@ -1,132 +0,0 @@
// Copyright 2013 Beego Authors
//
// 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 (
"crypto/aes"
"encoding/json"
"testing"
)
func Test_gob(t *testing.T) {
a := make(map[interface{}]interface{})
a["username"] = "astaxie"
a[12] = 234
a["user"] = User{"asta", "xie"}
b, err := EncodeGob(a)
if err != nil {
t.Error(err)
}
c, err := DecodeGob(b)
if err != nil {
t.Error(err)
}
if len(c) == 0 {
t.Error("decodeGob empty")
}
if c["username"] != "astaxie" {
t.Error("decode string error")
}
if c[12] != 234 {
t.Error("decode int error")
}
if c["user"].(User).Username != "asta" {
t.Error("decode struct error")
}
}
type User struct {
Username string
NickName string
}
func TestGenerate(t *testing.T) {
str := generateRandomKey(20)
if len(str) != 20 {
t.Fatal("generate length is not equal to 20")
}
}
func TestCookieEncodeDecode(t *testing.T) {
hashKey := "testhashKey"
blockkey := generateRandomKey(16)
block, err := aes.NewCipher(blockkey)
if err != nil {
t.Fatal("NewCipher:", err)
}
securityName := string(generateRandomKey(20))
val := make(map[interface{}]interface{})
val["name"] = "astaxie"
val["gender"] = "male"
str, err := encodeCookie(block, hashKey, securityName, val)
if err != nil {
t.Fatal("encodeCookie:", err)
}
dst := make(map[interface{}]interface{})
dst, err = decodeCookie(block, hashKey, securityName, str, 3600)
if err != nil {
t.Fatal("decodeCookie", err)
}
if dst["name"] != "astaxie" {
t.Fatal("dst get map error")
}
if dst["gender"] != "male" {
t.Fatal("dst get map error")
}
}
func TestParseConfig(t *testing.T) {
s := `{"cookieName":"gosessionid","gclifetime":3600}`
cf := new(Config)
cf.EnableSetCookie = true
err := json.Unmarshal([]byte(s), cf)
if err != nil {
t.Fatal("parse json error,", err)
}
if cf.CookieName != "gosessionid" {
t.Fatal("parseconfig get cookiename error")
}
if cf.Gclifetime != 3600 {
t.Fatal("parseconfig get gclifetime error")
}
cc := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}`
cf2 := new(Config)
cf2.EnableSetCookie = true
err = json.Unmarshal([]byte(cc), cf2)
if err != nil {
t.Fatal("parse json error,", err)
}
if cf2.CookieName != "gosessionid" {
t.Fatal("parseconfig get cookiename error")
}
if cf2.Gclifetime != 3600 {
t.Fatal("parseconfig get gclifetime error")
}
if cf2.EnableSetCookie != false {
t.Fatal("parseconfig get enableSetCookie error")
}
cconfig := new(cookieConfig)
err = json.Unmarshal([]byte(cf2.ProviderConfig), cconfig)
if err != nil {
t.Fatal("parse ProviderConfig err,", err)
}
if cconfig.CookieName != "gosessionid" {
t.Fatal("ProviderConfig get cookieName error")
}
if cconfig.SecurityKey != "beegocookiehashkey" {
t.Fatal("ProviderConfig get securityKey error")
}
}

View File

@ -1,231 +0,0 @@
// Copyright 2013 Beego Authors
//
// 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 (
"bytes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/subtle"
"encoding/base64"
"encoding/gob"
"errors"
"fmt"
"io"
r "math/rand"
"strconv"
"time"
)
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{})
}
// RandomCreateBytes generate random []byte by specify chars.
func RandomCreateBytes(n int, alphabets ...byte) []byte {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
var randby bool
if num, err := rand.Read(bytes); num != n || err != nil {
r.Seed(time.Now().UnixNano())
randby = true
}
for i, b := range bytes {
if len(alphabets) == 0 {
if randby {
bytes[i] = alphanum[r.Intn(len(alphanum))]
} else {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
} else {
if randby {
bytes[i] = alphabets[r.Intn(len(alphabets))]
} else {
bytes[i] = alphabets[b%byte(len(alphabets))]
}
}
}
return bytes
}
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
}
func DecodeGob(encoded []byte) (map[interface{}]interface{}, 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
}
// generateRandomKey creates a random key with the given strength.
func generateRandomKey(strength int) []byte {
k := make([]byte, strength)
if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil {
return RandomCreateBytes(strength)
}
return k
}
// Encryption -----------------------------------------------------------------
// encrypt encrypts a value using the given block in counter mode.
//
// A random initialization vector (http://goo.gl/zF67k) with the length of the
// block size is prepended to the resulting ciphertext.
func encrypt(block cipher.Block, value []byte) ([]byte, error) {
iv := generateRandomKey(block.BlockSize())
if iv == nil {
return nil, errors.New("encrypt: failed to generate random iv")
}
// Encrypt it.
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(value, value)
// Return iv + ciphertext.
return append(iv, value...), nil
}
// decrypt decrypts a value using the given block in counter mode.
//
// The value to be decrypted must be prepended by a initialization vector
// (http://goo.gl/zF67k) with the length of the block size.
func decrypt(block cipher.Block, value []byte) ([]byte, error) {
size := block.BlockSize()
if len(value) > size {
// Extract iv.
iv := value[:size]
// Extract ciphertext.
value = value[size:]
// Decrypt it.
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(value, value)
return value, nil
}
return nil, errors.New("decrypt: the value could not be decrypted")
}
func encodeCookie(block cipher.Block, hashKey, name string, value map[interface{}]interface{}) (string, error) {
var err error
var b []byte
// 1. EncodeGob.
if b, err = EncodeGob(value); err != nil {
return "", err
}
// 2. Encrypt (optional).
if b, err = encrypt(block, b); err != nil {
return "", err
}
b = encode(b)
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
b = []byte(fmt.Sprintf("%s|%d|%s|", name, time.Now().UTC().Unix(), b))
h := hmac.New(sha1.New, []byte(hashKey))
h.Write(b)
sig := h.Sum(nil)
// Append mac, remove name.
b = append(b, sig...)[len(name)+1:]
// 4. Encode to base64.
b = encode(b)
// Done.
return string(b), nil
}
func decodeCookie(block cipher.Block, hashKey, name, value string, gcmaxlifetime int64) (map[interface{}]interface{}, error) {
// 1. Decode from base64.
b, err := decode([]byte(value))
if err != nil {
return nil, err
}
// 2. Verify MAC. Value is "date|value|mac".
parts := bytes.SplitN(b, []byte("|"), 3)
if len(parts) != 3 {
return nil, errors.New("Decode: invalid value %v")
}
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])]...)
h := hmac.New(sha1.New, []byte(hashKey))
h.Write(b)
sig := h.Sum(nil)
if len(sig) != len(parts[2]) || subtle.ConstantTimeCompare(sig, parts[2]) != 1 {
return nil, errors.New("Decode: the value is not valid")
}
// 3. Verify date ranges.
var t1 int64
if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
return nil, errors.New("Decode: invalid timestamp")
}
t2 := time.Now().UTC().Unix()
if t1 > t2 {
return nil, errors.New("Decode: timestamp is too new")
}
if t1 < t2-gcmaxlifetime {
return nil, errors.New("Decode: expired timestamp")
}
// 4. Decrypt (optional).
b, err = decode(parts[1])
if err != nil {
return nil, err
}
if b, err = decrypt(block, b); err != nil {
return nil, err
}
// 5. DecodeGob.
if dst, err := DecodeGob(b); err != nil {
return nil, err
} else {
return dst, nil
}
}
// Encoding -------------------------------------------------------------------
// encode encodes a value using base64.
func encode(value []byte) []byte {
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
base64.URLEncoding.Encode(encoded, value)
return encoded
}
// decode decodes a cookie using base64.
func decode(value []byte) ([]byte, error) {
decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
b, err := base64.URLEncoding.Decode(decoded, value)
if err != nil {
return nil, err
}
return decoded[:b], nil
}

View File

@ -16,10 +16,9 @@
// Package session a middleware that provides the session manager of Macaron. // Package session a middleware that provides the session manager of Macaron.
package session package session
// NOTE: last sync fc6b9ce on Nov 4, 2014. // NOTE: last sync 000033e on Nov 4, 2014.
import ( import (
"crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/http" "net/http"
@ -29,30 +28,74 @@ import (
"github.com/Unknwon/macaron" "github.com/Unknwon/macaron"
) )
const _VERSION = "0.1.1"
func Version() string { func Version() string {
return "0.0.5" return _VERSION
} }
// RawStore is the interface that operates the session data.
type RawStore interface { type RawStore interface {
Set(key, value interface{}) error //set session value // Set sets value to given key in session.
Get(key interface{}) interface{} //get session value Set(key, value interface{}) error
Delete(key interface{}) error //delete session value // Get gets value by given key in session.
SessionID() string //back current sessionID Get(key interface{}) interface{}
SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data // Delete delete a key from session.
Flush() error //delete all data Delete(key interface{}) error
// ID returns current session ID.
ID() string
// Release releases session resource and save data to provider.
Release() error
// Flush deletes all session data.
Flush() error
} }
// Store contains all data for one session process with specific id. // Store is the interface that contains all data for one session process with specific ID.
type Store interface { type Store interface {
RawStore RawStore
GetActiveSession() int // Read returns raw session store by session ID.
Read(sid string) (RawStore, error)
// Destory deletes a session.
Destory(*macaron.Context) error
// RegenerateId regenerates a session store from old session ID to new one.
RegenerateId(*macaron.Context) (RawStore, error)
// Count counts and returns number of sessions.
Count() int
// GC calls GC to clean expired sessions.
GC()
} }
type store struct {
RawStore
*Manager
}
var _ Store = &store{}
// Options represents a struct for specifying configuration options for the session middleware.
type Options struct { type Options struct {
// Name of provider. Default is memory. // Name of provider. Default is "memory".
Provider string Provider string
// Provider configuration string. // Provider configuration, it's corresponding to provider.
Config ProviderConfig string
// Cookie name to save session ID. Default is "MacaronSession".
CookieName string
// Cookie path to store. Default is "/".
CookiePath string
// GC interval time in seconds. Default is 3600.
Gclifetime int64
// Max life time in seconds. Default is whatever GC interval time is.
Maxlifetime int64
// Use HTTPS only. Default is false.
Secure bool
// Cookie life time. Default is 0.
CookieLifeTime int
// Cookie domain name. Default is empty.
Domain string
// Session ID length. Default is 16.
IDLength int
// Configuration section name. Default is "session".
Section string
} }
func prepareOptions(options []Options) Options { func prepareOptions(options []Options) Options {
@ -60,32 +103,266 @@ func prepareOptions(options []Options) Options {
if len(options) > 0 { if len(options) > 0 {
opt = options[0] opt = options[0]
} }
if len(opt.Section) == 0 {
// Defaults opt.Section = "session"
if len(opt.Provider) == 0 { }
opt.Provider = "memory" sec := macaron.Config().Section(opt.Section)
if len(opt.Provider) == 0 {
opt.Provider = sec.Key("PROVIDER").MustString("memory")
}
if len(opt.ProviderConfig) == 0 && opt.Provider == "file" {
opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions")
} }
opt.EnableSetCookie = true
if len(opt.CookieName) == 0 { if len(opt.CookieName) == 0 {
opt.CookieName = "MacaronSession" opt.CookieName = sec.Key("COOKIE_NAME").MustString("MacaronSession")
} }
if len(opt.CookiePath) == 0 { if len(opt.CookiePath) == 0 {
opt.CookiePath = "/" opt.CookiePath = sec.Key("COOKIE_PATH").MustString("/")
} }
if opt.Gclifetime == 0 { if opt.Gclifetime == 0 {
opt.Gclifetime = 3600 opt.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(3600)
} }
if opt.Maxlifetime == 0 { if opt.Maxlifetime == 0 {
opt.Maxlifetime = opt.Gclifetime opt.Maxlifetime = sec.Key("MAX_LIFE_TIME").MustInt64(opt.Gclifetime)
} }
if !opt.Secure {
if opt.SessionIdLength == 0 { opt.Secure = sec.Key("SECURE").MustBool()
opt.SessionIdLength = 16 }
if opt.CookieLifeTime == 0 {
opt.CookieLifeTime = sec.Key("COOKIE_LIFE_TIME").MustInt()
}
if len(opt.Domain) == 0 {
opt.Domain = sec.Key("DOMAIN").String()
}
if opt.IDLength == 0 {
opt.IDLength = sec.Key("ID_LENGTH").MustInt(16)
} }
return opt return opt
} }
// Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain.
// An single variadic session.Options struct can be optionally provided to configure.
func Sessioner(options ...Options) macaron.Handler {
opt := prepareOptions(options)
manager, err := NewManager(opt.Provider, opt)
if err != nil {
panic(err)
}
go manager.startGC()
return func(ctx *macaron.Context) {
sess, err := manager.Start(ctx)
if err != nil {
panic("session: " + err.Error())
}
// Get flash.
vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash"))
if len(vals) > 0 {
f := &Flash{Values: vals}
f.ErrorMsg = f.Get("error")
f.SuccessMsg = f.Get("success")
f.InfoMsg = f.Get("info")
f.WarningMsg = f.Get("warning")
ctx.Data["Flash"] = f
ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath)
}
f := &Flash{ctx, url.Values{}, "", "", "", ""}
ctx.Resp.Before(func(macaron.ResponseWriter) {
if flash := f.Encode(); len(flash) > 0 {
ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath)
}
})
ctx.Map(f)
s := store{
RawStore: sess,
Manager: manager,
}
ctx.MapTo(s, (*Store)(nil))
ctx.Next()
if sess.Release() != nil {
panic("session: " + err.Error())
}
}
}
// Provider is the interface that provides session manipulations.
type Provider interface {
// Init initializes session provider.
Init(gclifetime int64, config string) error
// Read returns raw session store by session ID.
Read(sid string) (RawStore, error)
// Exist returns true if session with given ID exists.
Exist(sid string) bool
// Destory deletes a session by session ID.
Destory(sid string) error
// Regenerate regenerates a session store from old session ID to new one.
Regenerate(oldsid, sid string) (RawStore, error)
// Count counts and returns number of sessions.
Count() int
// GC calls GC to clean expired sessions.
GC()
}
var providers = make(map[string]Provider)
// Register registers a provider.
func Register(name string, provider Provider) {
if provider == nil {
panic("session: cannot register provider with nil value")
}
if _, dup := providers[name]; dup {
panic(fmt.Errorf("session: cannot register provider '%s' twice", name))
}
providers[name] = provider
}
// _____
// / \ _____ ____ _____ ____ ___________
// / \ / \\__ \ / \\__ \ / ___\_/ __ \_ __ \
// / Y \/ __ \| | \/ __ \_/ /_/ > ___/| | \/
// \____|__ (____ /___| (____ /\___ / \___ >__|
// \/ \/ \/ \//_____/ \/
// Manager represents a struct that contains session provider and its configuration.
type Manager struct {
provider Provider
opt Options
}
// NewManager creates and returns a new session manager by given provider name and configuration.
// It panics when given provider isn't registered.
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 &Manager{p, opt}, nil
}
// 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))
}
// Start starts a session by generating new one
// or retrieve existence one by reading session ID from HTTP request if it's valid.
func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) {
sid := ctx.GetCookie(m.opt.CookieName)
if len(sid) > 0 && m.provider.Exist(sid) {
return m.provider.Read(sid)
}
sid = m.sessionId()
sess, err := m.provider.Read(sid)
if err != nil {
return nil, err
}
cookie := &http.Cookie{
Name: m.opt.CookieName,
Value: sid,
Path: m.opt.CookiePath,
HttpOnly: true,
Secure: m.opt.Secure,
Domain: m.opt.Domain,
}
if m.opt.CookieLifeTime >= 0 {
cookie.MaxAge = m.opt.CookieLifeTime
}
http.SetCookie(ctx.Resp, cookie)
ctx.Req.AddCookie(cookie)
return sess, nil
}
// Read returns raw session store by session ID.
func (m *Manager) Read(sid string) (RawStore, error) {
return m.provider.Read(sid)
}
// Destory deletes a session by given ID.
func (m *Manager) Destory(ctx *macaron.Context) error {
sid := ctx.GetCookie(m.opt.CookieName)
if len(sid) == 0 {
return nil
}
if err := m.provider.Destory(sid); err != nil {
return err
}
cookie := &http.Cookie{
Name: m.opt.CookieName,
Path: m.opt.CookiePath,
HttpOnly: true,
Expires: time.Now(),
MaxAge: -1,
}
http.SetCookie(ctx.Resp, cookie)
return nil
}
// RegenerateId regenerates a session store from old session ID to new one.
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
}
}
ck := &http.Cookie{
Name: m.opt.CookieName,
Value: sid,
Path: m.opt.CookiePath,
HttpOnly: true,
Secure: m.opt.Secure,
Domain: m.opt.Domain,
}
if m.opt.CookieLifeTime >= 0 {
ck.MaxAge = m.opt.CookieLifeTime
}
http.SetCookie(ctx.Resp, ck)
ctx.Req.AddCookie(ck)
return sess, nil
}
// Count counts and returns number of sessions.
func (m *Manager) Count() int {
return m.provider.Count()
}
// GC starts GC job in a certain period.
func (m *Manager) GC() {
m.provider.GC()
}
// startGC starts GC job in a certain period.
func (m *Manager) startGC() {
m.GC()
time.AfterFunc(time.Duration(m.opt.Gclifetime)*time.Second, func() { m.startGC() })
}
// SetSecure indicates whether to set cookie with HTTPS or not.
func (m *Manager) SetSecure(secure bool) {
m.opt.Secure = secure
}
// ___________.____ _____ _________ ___ ___ // ___________.____ _____ _________ ___ ___
// \_ _____/| | / _ \ / _____// | \ // \_ _____/| | / _ \ / _____// | \
// | __) | | / /_\ \ \_____ \/ ~ \ // | __) | | / /_\ \ \_____ \/ ~ \
@ -132,289 +409,3 @@ func (f *Flash) Success(msg string, current ...bool) {
f.SuccessMsg = msg f.SuccessMsg = msg
f.set("success", msg, current...) f.set("success", msg, current...)
} }
type store struct {
RawStore
*Manager
}
// Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain.
// An single variadic session.Options struct can be optionally provided to configure.
func Sessioner(options ...Options) macaron.Handler {
opt := prepareOptions(options)
manager, err := NewManager(opt.Provider, &opt.Config)
if err != nil {
panic(err)
}
go manager.GC()
return func(ctx *macaron.Context) {
// FIXME: should I panic for error?
sess, _ := manager.SessionStart(ctx.Resp, ctx.Req.Request)
// Get flash.
vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash"))
if len(vals) > 0 {
f := &Flash{Values: vals}
f.ErrorMsg = f.Get("error")
f.SuccessMsg = f.Get("success")
f.InfoMsg = f.Get("info")
f.WarningMsg = f.Get("warning")
ctx.Data["Flash"] = f
ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath)
}
f := &Flash{ctx, url.Values{}, "", "", "", ""}
ctx.Resp.Before(func(macaron.ResponseWriter) {
sess.SessionRelease(ctx.Resp)
if flash := f.Encode(); len(flash) > 0 {
ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath)
}
})
ctx.Map(f)
s := store{
RawStore: sess,
Manager: manager,
}
ctx.MapTo(s, (*Store)(nil))
}
}
// Provider contains global session methods and saved SessionStores.
// it can operate a SessionStore by its id.
type Provider interface {
SessionInit(gclifetime int64, config string) error
SessionRead(sid string) (RawStore, error)
SessionExist(sid string) bool
SessionRegenerate(oldsid, sid string) (RawStore, error)
SessionDestroy(sid string) error
SessionAll() int //get all active session
SessionGC()
}
var provides = make(map[string]Provider)
// Register makes a session provide available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, provide Provider) {
if provide == nil {
panic("session: Register provide is nil")
}
if _, dup := provides[name]; dup {
panic("session: Register called twice for provider " + name)
}
provides[name] = provide
}
type Config struct {
CookieName string `json:"cookieName"`
CookiePath string `json:"cookiePath"`
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
Gclifetime int64 `json:"gclifetime"`
Maxlifetime int64 `json:"maxLifetime"`
Secure bool `json:"secure"`
CookieLifeTime int `json:"cookieLifeTime"`
ProviderConfig string `json:"providerConfig"`
Domain string `json:"domain"`
SessionIdLength int64 `json:"sessionIdLength"`
}
// Manager contains Provider and its configuration.
type Manager struct {
provider Provider
config *Config
}
// Create new Manager with provider name and json config string.
// provider name:
// 1. cookie
// 2. file
// 3. memory
// 4. redis
// 5. mysql
// json config:
// 1. is https default false
// 2. hashfunc default sha1
// 3. hashkey default beegosessionkey
// 4. maxage default is none
func NewManager(provideName string, config *Config) (*Manager, error) {
provider, ok := provides[provideName]
if !ok {
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
}
config.EnableSetCookie = true
if config.Maxlifetime == 0 {
config.Maxlifetime = config.Gclifetime
}
if err := provider.SessionInit(config.Maxlifetime, config.ProviderConfig); err != nil {
return nil, err
}
return &Manager{
provider: provider,
config: config,
}, nil
}
// Start session. generate or read the session id from http request.
// if session id exists, return SessionStore with this id.
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session RawStore, _ error) {
cookie, err := r.Cookie(manager.config.CookieName)
if err != nil || len(cookie.Value) == 0 {
sid, err := manager.sessionId(r)
if err != nil {
return nil, err
}
session, err = manager.provider.SessionRead(sid)
if err != nil {
return nil, err
}
cookie = &http.Cookie{Name: manager.config.CookieName,
Value: url.QueryEscape(sid),
Path: manager.config.CookiePath,
HttpOnly: true,
Secure: manager.config.Secure,
Domain: manager.config.Domain,
}
if manager.config.CookieLifeTime >= 0 {
cookie.MaxAge = manager.config.CookieLifeTime
}
if manager.config.EnableSetCookie {
http.SetCookie(w, cookie)
}
r.AddCookie(cookie)
} else {
sid, err := url.QueryUnescape(cookie.Value)
if err != nil {
return nil, err
}
if manager.provider.SessionExist(sid) {
session, err = manager.provider.SessionRead(sid)
if err != nil {
return nil, err
}
} else {
sid, err = manager.sessionId(r)
if err != nil {
return nil, err
}
session, err = manager.provider.SessionRead(sid)
if err != nil {
return nil, err
}
cookie = &http.Cookie{Name: manager.config.CookieName,
Value: url.QueryEscape(sid),
Path: manager.config.CookiePath,
HttpOnly: true,
Secure: manager.config.Secure,
Domain: manager.config.Domain,
}
if manager.config.CookieLifeTime >= 0 {
cookie.MaxAge = manager.config.CookieLifeTime
}
if manager.config.EnableSetCookie {
http.SetCookie(w, cookie)
}
r.AddCookie(cookie)
}
}
return session, nil
}
// Destroy session by its id in http request cookie.
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(manager.config.CookieName)
if err != nil || cookie.Value == "" {
return
} else {
manager.provider.SessionDestroy(cookie.Value)
expiration := time.Now()
cookie := http.Cookie{Name: manager.config.CookieName,
Path: manager.config.CookiePath,
HttpOnly: true,
Expires: expiration,
MaxAge: -1}
http.SetCookie(w, &cookie)
}
}
// Get SessionStore by its id.
func (manager *Manager) GetSessionStore(sid string) (sessions RawStore, err error) {
sessions, err = manager.provider.SessionRead(sid)
return
}
// Start session gc process.
// it can do gc in times after gc lifetime.
func (manager *Manager) GC() {
manager.provider.SessionGC()
time.AfterFunc(time.Duration(manager.config.Gclifetime)*time.Second, func() { manager.GC() })
}
// Regenerate a session id for this SessionStore who's id is saving in http request.
func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Request) (session RawStore) {
sid, err := manager.sessionId(r)
if err != nil {
return nil
}
cookie, err := r.Cookie(manager.config.CookieName)
if err != nil && cookie.Value == "" {
// delete old cookie
session, err = manager.provider.SessionRead(sid)
if err != nil {
return nil
}
cookie = &http.Cookie{Name: manager.config.CookieName,
Value: url.QueryEscape(sid),
Path: manager.config.CookiePath,
HttpOnly: true,
Secure: manager.config.Secure,
Domain: manager.config.Domain,
}
} else {
oldsid, err := url.QueryUnescape(cookie.Value)
if err != nil {
return nil
}
session, err = manager.provider.SessionRegenerate(oldsid, sid)
if err != nil {
return nil
}
cookie.Value = url.QueryEscape(sid)
cookie.HttpOnly = true
cookie.Path = "/"
}
if manager.config.CookieLifeTime >= 0 {
cookie.MaxAge = manager.config.CookieLifeTime
}
http.SetCookie(w, cookie)
r.AddCookie(cookie)
return session
}
// Get all active sessions count number.
func (manager *Manager) GetActiveSession() int {
return manager.provider.SessionAll()
}
// Set cookie with https.
func (manager *Manager) SetSecure(secure bool) {
manager.config.Secure = secure
}
// generate session id with rand string, unix nano time, remote addr by hash function.
func (manager *Manager) sessionId(r *http.Request) (string, error) {
b := make([]byte, manager.config.SessionIdLength)
n, err := rand.Read(b)
if n != len(b) || err != nil {
return "", fmt.Errorf("fail to read from the system CSPRNG.")
}
return hex.EncodeToString(b), nil
}

Binary file not shown.

View File

@ -37,10 +37,7 @@ func newMacaron() *macaron.Macaron {
mapStatic(m, "public/app", "app") mapStatic(m, "public/app", "app")
mapStatic(m, "public/img", "img") mapStatic(m, "public/img", "img")
m.Use(session.Sessioner(session.Options{ m.Use(session.Sessioner(setting.SessionOptions))
Provider: setting.SessionProvider,
Config: *setting.SessionConfig,
}))
m.Use(macaron.Renderer(macaron.RenderOptions{ m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "views"), Directory: path.Join(setting.StaticRootPath, "views"),

View File

@ -54,8 +54,7 @@ var (
StaticRootPath string StaticRootPath string
// Session settings. // Session settings.
SessionProvider string SessionOptions session.Options
SessionConfig *session.Config
// Global setting objects. // Global setting objects.
Cfg *goconfig.ConfigFile Cfg *goconfig.ConfigFile
@ -149,19 +148,18 @@ func NewConfigContext() {
} }
func initSessionService() { func initSessionService() {
SessionProvider = Cfg.MustValueRange("session", "provider", "memory", []string{"memory", "file"})
SessionConfig = new(session.Config) SessionOptions = session.Options{}
SessionConfig.ProviderConfig = strings.Trim(Cfg.MustValue("session", "provider_config"), "\" ") SessionOptions.Provider = Cfg.MustValueRange("session", "provider", "memory", []string{"memory", "file"})
SessionConfig.CookieName = Cfg.MustValue("session", "cookie_name", "grafana_pro_sess") SessionOptions.ProviderConfig = strings.Trim(Cfg.MustValue("session", "provider_config"), "\" ")
SessionConfig.CookiePath = AppSubUrl SessionOptions.CookieName = Cfg.MustValue("session", "cookie_name", "grafana_pro_sess")
SessionConfig.Secure = Cfg.MustBool("session", "cookie_secure") SessionOptions.CookiePath = AppSubUrl
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "enable_set_cookie", true) SessionOptions.Secure = Cfg.MustBool("session", "cookie_secure")
SessionConfig.Gclifetime = Cfg.MustInt64("session", "gc_interval_time", 86400) SessionOptions.Gclifetime = Cfg.MustInt64("session", "gc_interval_time", 86400)
SessionConfig.Maxlifetime = Cfg.MustInt64("session", "session_life_time", 86400) SessionOptions.Maxlifetime = Cfg.MustInt64("session", "session_life_time", 86400)
if SessionProvider == "file" { if SessionOptions.Provider == "file" {
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm) os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
} }
log.Info("Session Service Enabled") log.Info("Session Service Enabled")