1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-25 17:00:54 +00:00

Merge pull request #490 from fuxiaohei/develop

add comments for session and toolbox package
This commit is contained in:
astaxie 2014-02-04 06:25:18 -08:00
commit 74804bc586
11 changed files with 213 additions and 67 deletions

View File

@ -11,12 +11,15 @@ import (
var cookiepder = &CookieProvider{} var cookiepder = &CookieProvider{}
// Cookie SessionStore
type CookieSessionStore struct { type CookieSessionStore struct {
sid string sid string
values map[interface{}]interface{} // session data values map[interface{}]interface{} // session data
lock sync.RWMutex 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 { func (st *CookieSessionStore) Set(key, value interface{}) error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -24,6 +27,7 @@ func (st *CookieSessionStore) Set(key, value interface{}) error {
return nil return nil
} }
// Get value from cookie session
func (st *CookieSessionStore) Get(key interface{}) interface{} { func (st *CookieSessionStore) Get(key interface{}) interface{} {
st.lock.RLock() st.lock.RLock()
defer st.lock.RUnlock() defer st.lock.RUnlock()
@ -35,6 +39,7 @@ func (st *CookieSessionStore) Get(key interface{}) interface{} {
return nil return nil
} }
// Delete value in cookie session
func (st *CookieSessionStore) Delete(key interface{}) error { func (st *CookieSessionStore) Delete(key interface{}) error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -42,6 +47,7 @@ func (st *CookieSessionStore) Delete(key interface{}) error {
return nil return nil
} }
// Clean all values in cookie session
func (st *CookieSessionStore) Flush() error { func (st *CookieSessionStore) Flush() error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -49,10 +55,12 @@ func (st *CookieSessionStore) Flush() error {
return nil return nil
} }
// Return id of this cookie session
func (st *CookieSessionStore) SessionID() string { func (st *CookieSessionStore) SessionID() string {
return st.sid return st.sid
} }
// Write cookie session to http response cookie
func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) { func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) {
str, err := encodeCookie(cookiepder.block, str, err := encodeCookie(cookiepder.block,
cookiepder.config.SecurityKey, cookiepder.config.SecurityKey,
@ -79,12 +87,21 @@ type cookieConfig struct {
Maxage int `json:"maxage"` Maxage int `json:"maxage"`
} }
// Cookie session provider
type CookieProvider struct { type CookieProvider struct {
maxlifetime int64 maxlifetime int64
config *cookieConfig config *cookieConfig
block cipher.Block 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 { func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error {
pder.config = &cookieConfig{} pder.config = &cookieConfig{}
err := json.Unmarshal([]byte(config), pder.config) err := json.Unmarshal([]byte(config), pder.config)
@ -104,6 +121,8 @@ func (pder *CookieProvider) SessionInit(maxlifetime int64, config string) error
return nil return nil
} }
// Get SessionStore in cooke.
// decode cooke string to map and put into SessionStore with sid.
func (pder *CookieProvider) SessionRead(sid string) (SessionStore, error) { func (pder *CookieProvider) SessionRead(sid string) (SessionStore, error) {
maps, _ := decodeCookie(pder.block, maps, _ := decodeCookie(pder.block,
pder.config.SecurityKey, pder.config.SecurityKey,
@ -116,26 +135,32 @@ func (pder *CookieProvider) SessionRead(sid string) (SessionStore, error) {
return rs, nil return rs, nil
} }
// Cookie session is always existed
func (pder *CookieProvider) SessionExist(sid string) bool { func (pder *CookieProvider) SessionExist(sid string) bool {
return true return true
} }
// Implement method, no used.
func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { func (pder *CookieProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) {
return nil, nil return nil, nil
} }
// Implement method, no used.
func (pder *CookieProvider) SessionDestroy(sid string) error { func (pder *CookieProvider) SessionDestroy(sid string) error {
return nil return nil
} }
// Implement method, no used.
func (pder *CookieProvider) SessionGC() { func (pder *CookieProvider) SessionGC() {
return return
} }
// Implement method, return 0.
func (pder *CookieProvider) SessionAll() int { func (pder *CookieProvider) SessionAll() int {
return 0 return 0
} }
// Implement method, no used.
func (pder *CookieProvider) SessionUpdate(sid string) error { func (pder *CookieProvider) SessionUpdate(sid string) error {
return nil return nil
} }

View File

@ -18,6 +18,7 @@ var (
gcmaxlifetime int64 gcmaxlifetime int64
) )
// File session store
type FileSessionStore struct { type FileSessionStore struct {
f *os.File f *os.File
sid string sid string
@ -25,6 +26,7 @@ type FileSessionStore struct {
values map[interface{}]interface{} values map[interface{}]interface{}
} }
// Set value to file session
func (fs *FileSessionStore) Set(key, value interface{}) error { func (fs *FileSessionStore) Set(key, value interface{}) error {
fs.lock.Lock() fs.lock.Lock()
defer fs.lock.Unlock() defer fs.lock.Unlock()
@ -32,6 +34,7 @@ func (fs *FileSessionStore) Set(key, value interface{}) error {
return nil return nil
} }
// Get value from file session
func (fs *FileSessionStore) Get(key interface{}) interface{} { func (fs *FileSessionStore) Get(key interface{}) interface{} {
fs.lock.RLock() fs.lock.RLock()
defer fs.lock.RUnlock() defer fs.lock.RUnlock()
@ -43,6 +46,7 @@ func (fs *FileSessionStore) Get(key interface{}) interface{} {
return nil return nil
} }
// Delete value in file session by given key
func (fs *FileSessionStore) Delete(key interface{}) error { func (fs *FileSessionStore) Delete(key interface{}) error {
fs.lock.Lock() fs.lock.Lock()
defer fs.lock.Unlock() defer fs.lock.Unlock()
@ -50,6 +54,7 @@ func (fs *FileSessionStore) Delete(key interface{}) error {
return nil return nil
} }
// Clean all values in file session
func (fs *FileSessionStore) Flush() error { func (fs *FileSessionStore) Flush() error {
fs.lock.Lock() fs.lock.Lock()
defer fs.lock.Unlock() defer fs.lock.Unlock()
@ -57,10 +62,12 @@ func (fs *FileSessionStore) Flush() error {
return nil return nil
} }
// Get file session store id
func (fs *FileSessionStore) SessionID() string { func (fs *FileSessionStore) SessionID() string {
return fs.sid return fs.sid
} }
// Write file session to local file with Gob string
func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
defer fs.f.Close() defer fs.f.Close()
b, err := encodeGob(fs.values) b, err := encodeGob(fs.values)
@ -72,17 +79,23 @@ func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
fs.f.Write(b) fs.f.Write(b)
} }
// File session provider
type FileProvider struct { type FileProvider struct {
maxlifetime int64 maxlifetime int64
savePath string savePath string
} }
// Init file session provider.
// savePath sets the session files path.
func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error {
fp.maxlifetime = maxlifetime fp.maxlifetime = maxlifetime
fp.savePath = savePath fp.savePath = savePath
return nil 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) (SessionStore, error) { func (fp *FileProvider) SessionRead(sid string) (SessionStore, error) {
err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777) err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777)
if err != nil { if err != nil {
@ -117,6 +130,8 @@ func (fp *FileProvider) SessionRead(sid string) (SessionStore, error) {
return ss, nil return ss, nil
} }
// Check file session exist.
// it checkes the file named from sid exist or not.
func (fp *FileProvider) SessionExist(sid string) bool { func (fp *FileProvider) SessionExist(sid string) bool {
_, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid)) _, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
if err == nil { if err == nil {
@ -126,16 +141,20 @@ func (fp *FileProvider) SessionExist(sid string) bool {
} }
} }
// Remove all files in this save path
func (fp *FileProvider) SessionDestroy(sid string) error { func (fp *FileProvider) SessionDestroy(sid string) error {
os.Remove(path.Join(fp.savePath)) os.Remove(path.Join(fp.savePath))
return nil return nil
} }
// Recycle files in save path
func (fp *FileProvider) SessionGC() { func (fp *FileProvider) SessionGC() {
gcmaxlifetime = fp.maxlifetime gcmaxlifetime = fp.maxlifetime
filepath.Walk(fp.savePath, gcpath) filepath.Walk(fp.savePath, gcpath)
} }
// Get active file session number.
// it walks save path to count files.
func (fp *FileProvider) SessionAll() int { func (fp *FileProvider) SessionAll() int {
a := &activeSession{} a := &activeSession{}
err := filepath.Walk(fp.savePath, func(path string, f os.FileInfo, err error) error { err := filepath.Walk(fp.savePath, func(path string, f os.FileInfo, err error) error {
@ -148,6 +167,8 @@ func (fp *FileProvider) SessionAll() int {
return a.total 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) (SessionStore, error) { func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) {
err := os.MkdirAll(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1])), 0777) err := os.MkdirAll(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1])), 0777)
if err != nil { if err != nil {
@ -197,6 +218,7 @@ func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (SessionStore, err
return ss, nil return ss, nil
} }
// remove file in save path if expired
func gcpath(path string, info os.FileInfo, err error) error { func gcpath(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err

View File

@ -9,6 +9,8 @@ import (
var mempder = &MemProvider{list: list.New(), sessions: make(map[string]*list.Element)} 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 { type MemSessionStore struct {
sid string //session id sid string //session id
timeAccessed time.Time //last access time timeAccessed time.Time //last access time
@ -16,6 +18,7 @@ type MemSessionStore struct {
lock sync.RWMutex lock sync.RWMutex
} }
// set value to memory session
func (st *MemSessionStore) Set(key, value interface{}) error { func (st *MemSessionStore) Set(key, value interface{}) error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -23,6 +26,7 @@ func (st *MemSessionStore) Set(key, value interface{}) error {
return nil return nil
} }
// get value from memory session by key
func (st *MemSessionStore) Get(key interface{}) interface{} { func (st *MemSessionStore) Get(key interface{}) interface{} {
st.lock.RLock() st.lock.RLock()
defer st.lock.RUnlock() defer st.lock.RUnlock()
@ -34,6 +38,7 @@ func (st *MemSessionStore) Get(key interface{}) interface{} {
return nil return nil
} }
// delete in memory session by key
func (st *MemSessionStore) Delete(key interface{}) error { func (st *MemSessionStore) Delete(key interface{}) error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -41,6 +46,7 @@ func (st *MemSessionStore) Delete(key interface{}) error {
return nil return nil
} }
// clear all values in memory session
func (st *MemSessionStore) Flush() error { func (st *MemSessionStore) Flush() error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -48,27 +54,31 @@ func (st *MemSessionStore) Flush() error {
return nil return nil
} }
// get this id of memory session store
func (st *MemSessionStore) SessionID() string { func (st *MemSessionStore) SessionID() string {
return st.sid return st.sid
} }
// Implement method, no used.
func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) { func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) {
} }
type MemProvider struct { type MemProvider struct {
lock sync.RWMutex //用来锁 lock sync.RWMutex // locker
sessions map[string]*list.Element //用来存储在内存 sessions map[string]*list.Element // map in memory
list *list.List //用来做gc list *list.List // for gc
maxlifetime int64 maxlifetime int64
savePath string savePath string
} }
// init memory session
func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error { func (pder *MemProvider) SessionInit(maxlifetime int64, savePath string) error {
pder.maxlifetime = maxlifetime pder.maxlifetime = maxlifetime
pder.savePath = savePath pder.savePath = savePath
return nil return nil
} }
// get memory session store by sid
func (pder *MemProvider) SessionRead(sid string) (SessionStore, error) { func (pder *MemProvider) SessionRead(sid string) (SessionStore, error) {
pder.lock.RLock() pder.lock.RLock()
if element, ok := pder.sessions[sid]; ok { if element, ok := pder.sessions[sid]; ok {
@ -87,6 +97,7 @@ func (pder *MemProvider) SessionRead(sid string) (SessionStore, error) {
return nil, nil return nil, nil
} }
// check session store exist in memory session by sid
func (pder *MemProvider) SessionExist(sid string) bool { func (pder *MemProvider) SessionExist(sid string) bool {
pder.lock.RLock() pder.lock.RLock()
defer pder.lock.RUnlock() defer pder.lock.RUnlock()
@ -97,6 +108,7 @@ func (pder *MemProvider) SessionExist(sid string) bool {
} }
} }
// generate new sid for session store in memory session
func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) {
pder.lock.RLock() pder.lock.RLock()
if element, ok := pder.sessions[oldsid]; ok { if element, ok := pder.sessions[oldsid]; ok {
@ -120,6 +132,7 @@ func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er
return nil, nil return nil, nil
} }
// delete session store in memory session by id
func (pder *MemProvider) SessionDestroy(sid string) error { func (pder *MemProvider) SessionDestroy(sid string) error {
pder.lock.Lock() pder.lock.Lock()
defer pder.lock.Unlock() defer pder.lock.Unlock()
@ -131,6 +144,7 @@ func (pder *MemProvider) SessionDestroy(sid string) error {
return nil return nil
} }
// clean expired session stores in memory session
func (pder *MemProvider) SessionGC() { func (pder *MemProvider) SessionGC() {
pder.lock.RLock() pder.lock.RLock()
for { for {
@ -152,10 +166,12 @@ func (pder *MemProvider) SessionGC() {
pder.lock.RUnlock() pder.lock.RUnlock()
} }
// get count number of memory session
func (pder *MemProvider) SessionAll() int { func (pder *MemProvider) SessionAll() int {
return pder.list.Len() return pder.list.Len()
} }
// expand time of session store by id in memory session
func (pder *MemProvider) SessionUpdate(sid string) error { func (pder *MemProvider) SessionUpdate(sid string) error {
pder.lock.Lock() pder.lock.Lock()
defer pder.lock.Unlock() defer pder.lock.Unlock()

View File

@ -1,8 +1,9 @@
package session package session
// mysql session support need create table as sql:
// CREATE TABLE `session` ( // CREATE TABLE `session` (
// `session_key` char(64) NOT NULL, // `session_key` char(64) NOT NULL,
// `session_data` blob, // session_data` blob,
// `session_expiry` int(11) unsigned NOT NULL, // `session_expiry` int(11) unsigned NOT NULL,
// PRIMARY KEY (`session_key`) // PRIMARY KEY (`session_key`)
// ) ENGINE=MyISAM DEFAULT CHARSET=utf8; // ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
@ -18,6 +19,7 @@ import (
var mysqlpder = &MysqlProvider{} var mysqlpder = &MysqlProvider{}
// mysql session store
type MysqlSessionStore struct { type MysqlSessionStore struct {
c *sql.DB c *sql.DB
sid string sid string
@ -25,6 +27,8 @@ type MysqlSessionStore struct {
values map[interface{}]interface{} values map[interface{}]interface{}
} }
// set value in mysql session.
// it is temp value in map.
func (st *MysqlSessionStore) Set(key, value interface{}) error { func (st *MysqlSessionStore) Set(key, value interface{}) error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -32,6 +36,7 @@ func (st *MysqlSessionStore) Set(key, value interface{}) error {
return nil return nil
} }
// get value from mysql session
func (st *MysqlSessionStore) Get(key interface{}) interface{} { func (st *MysqlSessionStore) Get(key interface{}) interface{} {
st.lock.RLock() st.lock.RLock()
defer st.lock.RUnlock() defer st.lock.RUnlock()
@ -43,6 +48,7 @@ func (st *MysqlSessionStore) Get(key interface{}) interface{} {
return nil return nil
} }
// delete value in mysql session
func (st *MysqlSessionStore) Delete(key interface{}) error { func (st *MysqlSessionStore) Delete(key interface{}) error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -50,6 +56,7 @@ func (st *MysqlSessionStore) Delete(key interface{}) error {
return nil return nil
} }
// clear all values in mysql session
func (st *MysqlSessionStore) Flush() error { func (st *MysqlSessionStore) Flush() error {
st.lock.Lock() st.lock.Lock()
defer st.lock.Unlock() defer st.lock.Unlock()
@ -57,10 +64,13 @@ func (st *MysqlSessionStore) Flush() error {
return nil return nil
} }
// get session id of this mysql session store
func (st *MysqlSessionStore) SessionID() string { func (st *MysqlSessionStore) SessionID() string {
return st.sid return st.sid
} }
// save mysql session values to database.
// must call this method to save values to database.
func (st *MysqlSessionStore) SessionRelease(w http.ResponseWriter) { func (st *MysqlSessionStore) SessionRelease(w http.ResponseWriter) {
defer st.c.Close() defer st.c.Close()
b, err := encodeGob(st.values) b, err := encodeGob(st.values)
@ -72,11 +82,13 @@ func (st *MysqlSessionStore) SessionRelease(w http.ResponseWriter) {
} }
// mysql session provider
type MysqlProvider struct { type MysqlProvider struct {
maxlifetime int64 maxlifetime int64
savePath string savePath string
} }
// connect to mysql
func (mp *MysqlProvider) connectInit() *sql.DB { func (mp *MysqlProvider) connectInit() *sql.DB {
db, e := sql.Open("mysql", mp.savePath) db, e := sql.Open("mysql", mp.savePath)
if e != nil { if e != nil {
@ -85,12 +97,15 @@ func (mp *MysqlProvider) connectInit() *sql.DB {
return db return db
} }
// init mysql session.
// savepath is the connection string of mysql.
func (mp *MysqlProvider) SessionInit(maxlifetime int64, savePath string) error { func (mp *MysqlProvider) SessionInit(maxlifetime int64, savePath string) error {
mp.maxlifetime = maxlifetime mp.maxlifetime = maxlifetime
mp.savePath = savePath mp.savePath = savePath
return nil return nil
} }
// get mysql session by sid
func (mp *MysqlProvider) SessionRead(sid string) (SessionStore, error) { func (mp *MysqlProvider) SessionRead(sid string) (SessionStore, error) {
c := mp.connectInit() c := mp.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)
@ -113,6 +128,7 @@ func (mp *MysqlProvider) SessionRead(sid string) (SessionStore, error) {
return rs, nil return rs, nil
} }
// check mysql session exist
func (mp *MysqlProvider) SessionExist(sid string) bool { func (mp *MysqlProvider) SessionExist(sid string) bool {
c := mp.connectInit() c := mp.connectInit()
defer c.Close() defer c.Close()
@ -126,6 +142,7 @@ func (mp *MysqlProvider) SessionExist(sid string) bool {
} }
} }
// generate new sid for mysql session
func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) {
c := mp.connectInit() c := mp.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)
@ -148,6 +165,7 @@ func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er
return rs, nil return rs, nil
} }
// delete mysql session by sid
func (mp *MysqlProvider) SessionDestroy(sid string) error { func (mp *MysqlProvider) SessionDestroy(sid string) error {
c := mp.connectInit() c := mp.connectInit()
c.Exec("DELETE FROM session where session_key=?", sid) c.Exec("DELETE FROM session where session_key=?", sid)
@ -155,6 +173,7 @@ func (mp *MysqlProvider) SessionDestroy(sid string) error {
return nil return nil
} }
// delete expired values in mysql session
func (mp *MysqlProvider) SessionGC() { func (mp *MysqlProvider) SessionGC() {
c := mp.connectInit() c := mp.connectInit()
c.Exec("DELETE from session where session_expiry < ?", time.Now().Unix()-mp.maxlifetime) c.Exec("DELETE from session where session_expiry < ?", time.Now().Unix()-mp.maxlifetime)
@ -162,6 +181,7 @@ func (mp *MysqlProvider) SessionGC() {
return return
} }
// count values in mysql session
func (mp *MysqlProvider) SessionAll() int { func (mp *MysqlProvider) SessionAll() int {
c := mp.connectInit() c := mp.connectInit()
defer c.Close() defer c.Close()

View File

@ -11,10 +11,12 @@ import (
var redispder = &RedisProvider{} var redispder = &RedisProvider{}
// 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
type RedisSessionStore struct { type RedisSessionStore struct {
p *redis.Pool p *redis.Pool
sid string sid string
@ -23,6 +25,7 @@ type RedisSessionStore struct {
maxlifetime int64 maxlifetime int64
} }
// set value in redis session
func (rs *RedisSessionStore) Set(key, value interface{}) error { func (rs *RedisSessionStore) Set(key, value interface{}) error {
rs.lock.Lock() rs.lock.Lock()
defer rs.lock.Unlock() defer rs.lock.Unlock()
@ -30,6 +33,7 @@ func (rs *RedisSessionStore) Set(key, value interface{}) error {
return nil return nil
} }
// get value in redis session
func (rs *RedisSessionStore) Get(key interface{}) interface{} { func (rs *RedisSessionStore) Get(key interface{}) interface{} {
rs.lock.RLock() rs.lock.RLock()
defer rs.lock.RUnlock() defer rs.lock.RUnlock()
@ -41,6 +45,7 @@ func (rs *RedisSessionStore) Get(key interface{}) interface{} {
return nil return nil
} }
// delete value in redis session
func (rs *RedisSessionStore) Delete(key interface{}) error { func (rs *RedisSessionStore) Delete(key interface{}) error {
rs.lock.Lock() rs.lock.Lock()
defer rs.lock.Unlock() defer rs.lock.Unlock()
@ -48,6 +53,7 @@ func (rs *RedisSessionStore) Delete(key interface{}) error {
return nil return nil
} }
// clear all values in redis session
func (rs *RedisSessionStore) Flush() error { func (rs *RedisSessionStore) Flush() error {
rs.lock.Lock() rs.lock.Lock()
defer rs.lock.Unlock() defer rs.lock.Unlock()
@ -55,10 +61,12 @@ func (rs *RedisSessionStore) Flush() error {
return nil return nil
} }
// get redis session id
func (rs *RedisSessionStore) SessionID() string { func (rs *RedisSessionStore) SessionID() string {
return rs.sid return rs.sid
} }
// save session values to redis
func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) { func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) {
c := rs.p.Get() c := rs.p.Get()
defer c.Close() defer c.Close()
@ -77,6 +85,7 @@ func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) {
c.Do("SET", rs.sid, string(b), "EX", rs.maxlifetime) c.Do("SET", rs.sid, string(b), "EX", rs.maxlifetime)
} }
// redis session provider
type RedisProvider struct { type RedisProvider struct {
maxlifetime int64 maxlifetime int64
savePath string savePath string
@ -85,8 +94,9 @@ type RedisProvider struct {
poollist *redis.Pool poollist *redis.Pool
} }
// init redis session
// savepath like redis server addr,pool size,password // savepath like redis server addr,pool size,password
//127.0.0.1:6379,100,astaxie // e.g. 127.0.0.1:6379,100,astaxie
func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",") configs := strings.Split(savePath, ",")
@ -122,6 +132,7 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
return nil return nil
} }
// read redis session by sid
func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) { func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) {
c := rp.poollist.Get() c := rp.poollist.Get()
defer c.Close() defer c.Close()
@ -141,6 +152,7 @@ func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) {
return rs, nil return rs, nil
} }
// check redis session exist by sid
func (rp *RedisProvider) SessionExist(sid string) bool { func (rp *RedisProvider) SessionExist(sid string) bool {
c := rp.poollist.Get() c := rp.poollist.Get()
defer c.Close() defer c.Close()
@ -152,6 +164,7 @@ func (rp *RedisProvider) SessionExist(sid string) bool {
} }
} }
// generate new sid for redis session
func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) {
c := rp.poollist.Get() c := rp.poollist.Get()
defer c.Close() defer c.Close()
@ -181,6 +194,7 @@ func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er
return rs, nil return rs, nil
} }
// delete redis session by id
func (rp *RedisProvider) SessionDestroy(sid string) error { func (rp *RedisProvider) SessionDestroy(sid string) error {
c := rp.poollist.Get() c := rp.poollist.Get()
defer c.Close() defer c.Close()
@ -189,6 +203,7 @@ func (rp *RedisProvider) SessionDestroy(sid string) error {
return nil return nil
} }
// Impelment method, no used.
func (rp *RedisProvider) SessionGC() { func (rp *RedisProvider) SessionGC() {
return return
} }

View File

@ -14,6 +14,7 @@ import (
"time" "time"
) )
// SessionStore contains all data for one session process with specific id.
type SessionStore interface { type SessionStore interface {
Set(key, value interface{}) error //set session value Set(key, value interface{}) error //set session value
Get(key interface{}) interface{} //get session value Get(key interface{}) interface{} //get session value
@ -23,6 +24,8 @@ type SessionStore interface {
Flush() error //delete all data Flush() error //delete all data
} }
// Provider contains global session methods and saved SessionStores.
// it can operate a SessionStore by its id.
type Provider interface { type Provider interface {
SessionInit(gclifetime int64, config string) error SessionInit(gclifetime int64, config string) error
SessionRead(sid string) (SessionStore, error) SessionRead(sid string) (SessionStore, error)
@ -61,12 +64,20 @@ type managerConfig struct {
ProviderConfig string `json:"providerConfig"` ProviderConfig string `json:"providerConfig"`
} }
// Manager contains Provider and its configuration.
type Manager struct { type Manager struct {
provider Provider provider Provider
config *managerConfig config *managerConfig
} }
//options // 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 // 1. is https default false
// 2. hashfunc default sha1 // 2. hashfunc default sha1
// 3. hashkey default beegosessionkey // 3. hashkey default beegosessionkey
@ -102,7 +113,8 @@ func NewManager(provideName, config string) (*Manager, error) {
}, nil }, nil
} }
//get Session // 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 SessionStore) { func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session SessionStore) {
cookie, err := r.Cookie(manager.config.CookieName) cookie, err := r.Cookie(manager.config.CookieName)
if err != nil || cookie.Value == "" { if err != nil || cookie.Value == "" {
@ -144,7 +156,7 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
return return
} }
//Destroy sessionid // Destroy session by its id in http request cookie.
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(manager.config.CookieName) cookie, err := r.Cookie(manager.config.CookieName)
if err != nil || cookie.Value == "" { if err != nil || cookie.Value == "" {
@ -161,16 +173,20 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
} }
} }
// Get SessionStore by its id.
func (manager *Manager) GetProvider(sid string) (sessions SessionStore, err error) { func (manager *Manager) GetProvider(sid string) (sessions SessionStore, err error) {
sessions, err = manager.provider.SessionRead(sid) sessions, err = manager.provider.SessionRead(sid)
return return
} }
// Start session gc process.
// it can do gc in times after gc lifetime.
func (manager *Manager) GC() { func (manager *Manager) GC() {
manager.provider.SessionGC() manager.provider.SessionGC()
time.AfterFunc(time.Duration(manager.config.Gclifetime)*time.Second, func() { manager.GC() }) 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 SessionStore) { func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Request) (session SessionStore) {
sid := manager.sessionId(r) sid := manager.sessionId(r)
cookie, err := r.Cookie(manager.config.CookieName) cookie, err := r.Cookie(manager.config.CookieName)
@ -198,20 +214,23 @@ func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Reque
return return
} }
// Get all active sessions count number.
func (manager *Manager) GetActiveSession() int { func (manager *Manager) GetActiveSession() int {
return manager.provider.SessionAll() return manager.provider.SessionAll()
} }
// Set hash function for generating session id.
func (manager *Manager) SetHashFunc(hasfunc, hashkey string) { func (manager *Manager) SetHashFunc(hasfunc, hashkey string) {
manager.config.SessionIDHashFunc = hasfunc manager.config.SessionIDHashFunc = hasfunc
manager.config.SessionIDHashKey = hashkey manager.config.SessionIDHashKey = hashkey
} }
// Set cookie with https.
func (manager *Manager) SetSecure(secure bool) { func (manager *Manager) SetSecure(secure bool) {
manager.config.Secure = secure manager.config.Secure = secure
} }
//remote_addr cruunixnano randdata // generate session id with rand string, unix nano time, remote addr by hash function.
func (manager *Manager) sessionId(r *http.Request) (sid string) { func (manager *Manager) sessionId(r *http.Request) (sid string) {
bs := make([]byte, 24) bs := make([]byte, 24)
if _, err := io.ReadFull(rand.Reader, bs); err != nil { if _, err := io.ReadFull(rand.Reader, bs); err != nil {

View File

@ -29,16 +29,13 @@ type pointerInfo struct {
used []int used []int
} }
//
// print the data in console // print the data in console
//
func Display(data ...interface{}) { func Display(data ...interface{}) {
display(true, data...) display(true, data...)
} }
//
// return string // return data print string
//
func GetDisplayString(data ...interface{}) string { func GetDisplayString(data ...interface{}) string {
return display(false, data...) return display(false, data...)
} }
@ -67,9 +64,7 @@ func display(displayed bool, data ...interface{}) string {
return buf.String() return buf.String()
} }
// // return data dump and format bytes
// return fomateinfo
//
func fomateinfo(headlen int, data ...interface{}) []byte { func fomateinfo(headlen int, data ...interface{}) []byte {
var buf = new(bytes.Buffer) var buf = new(bytes.Buffer)
@ -108,6 +103,7 @@ func fomateinfo(headlen int, data ...interface{}) []byte {
return buf.Bytes() return buf.Bytes()
} }
// check data is golang basic type
func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo, interfaces *[]reflect.Value) bool { func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo, interfaces *[]reflect.Value) bool {
switch kind { switch kind {
case reflect.Bool: case reflect.Bool:
@ -158,6 +154,7 @@ func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo,
return false return false
} }
// dump value
func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo, interfaces *[]reflect.Value, structFilter func(string, string) bool, formatOutput bool, indent string, level int) { func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo, interfaces *[]reflect.Value, structFilter func(string, string) bool, formatOutput bool, indent string, level int) {
var t = val.Kind() var t = val.Kind()
@ -367,6 +364,7 @@ func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo,
} }
} }
// dump pointer value
func printPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) { func printPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) {
var anyused = false var anyused = false
var pointerNum = 0 var pointerNum = 0
@ -434,9 +432,7 @@ func printPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) {
} }
} }
// // get stack bytes
// get stack info
//
func stack(skip int, indent string) []byte { func stack(skip int, indent string) []byte {
var buf = new(bytes.Buffer) var buf = new(bytes.Buffer)
@ -455,7 +451,7 @@ func stack(skip int, indent string) []byte {
return buf.Bytes() return buf.Bytes()
} }
// function returns, if possible, the name of the function containing the PC. // return the name of the function containing the PC if possible,
func function(pc uintptr) []byte { func function(pc uintptr) []byte {
fn := runtime.FuncForPC(pc) fn := runtime.FuncForPC(pc)
if fn == nil { if fn == nil {

View File

@ -13,12 +13,15 @@ package toolbox
//AddHealthCheck("database",&DatabaseCheck{}) //AddHealthCheck("database",&DatabaseCheck{})
// health checker map
var AdminCheckList map[string]HealthChecker var AdminCheckList map[string]HealthChecker
// health checker interface
type HealthChecker interface { type HealthChecker interface {
Check() error Check() error
} }
// add health checker with name string
func AddHealthCheck(name string, hc HealthChecker) { func AddHealthCheck(name string, hc HealthChecker) {
AdminCheckList[name] = hc AdminCheckList[name] = hc
} }

View File

@ -19,6 +19,7 @@ func init() {
pid = os.Getpid() pid = os.Getpid()
} }
// parse input command string
func ProcessInput(input string, w io.Writer) { func ProcessInput(input string, w io.Writer) {
switch input { switch input {
case "lookup goroutine": case "lookup goroutine":
@ -44,6 +45,7 @@ func ProcessInput(input string, w io.Writer) {
} }
} }
// record memory profile in pprof
func MemProf() { func MemProf() {
if f, err := os.Create("mem-" + strconv.Itoa(pid) + ".memprof"); err != nil { if f, err := os.Create("mem-" + strconv.Itoa(pid) + ".memprof"); err != nil {
log.Fatal("record memory profile failed: %v", err) log.Fatal("record memory profile failed: %v", err)
@ -54,6 +56,7 @@ func MemProf() {
} }
} }
// start cpu profile monitor
func StartCPUProfile() { func StartCPUProfile() {
f, err := os.Create("cpu-" + strconv.Itoa(pid) + ".pprof") f, err := os.Create("cpu-" + strconv.Itoa(pid) + ".pprof")
if err != nil { if err != nil {
@ -62,10 +65,12 @@ func StartCPUProfile() {
pprof.StartCPUProfile(f) pprof.StartCPUProfile(f)
} }
// stop cpu profile monitor
func StopCPUProfile() { func StopCPUProfile() {
pprof.StopCPUProfile() pprof.StopCPUProfile()
} }
// print gc information to io.Writer
func PrintGCSummary(w io.Writer) { func PrintGCSummary(w io.Writer) {
memStats := &runtime.MemStats{} memStats := &runtime.MemStats{}
runtime.ReadMemStats(memStats) runtime.ReadMemStats(memStats)
@ -114,7 +119,7 @@ func avg(items []time.Duration) time.Duration {
return time.Duration(int64(sum) / int64(len(items))) return time.Duration(int64(sum) / int64(len(items)))
} }
// human readable format // format bytes number friendly
func toH(bytes uint64) string { func toH(bytes uint64) string {
switch { switch {
case bytes < 1024: case bytes < 1024:

View File

@ -7,6 +7,7 @@ import (
"time" "time"
) )
// Statistics struct
type Statistics struct { type Statistics struct {
RequestUrl string RequestUrl string
RequestController string RequestController string
@ -16,12 +17,15 @@ type Statistics struct {
TotalTime time.Duration TotalTime time.Duration
} }
// UrlMap contains several statistics struct to log different data
type UrlMap struct { type UrlMap struct {
lock sync.RWMutex lock sync.RWMutex
LengthLimit int //limit the urlmap's length if it's equal to 0 there's no limit LengthLimit int //limit the urlmap's length if it's equal to 0 there's no limit
urlmap map[string]map[string]*Statistics urlmap map[string]map[string]*Statistics
} }
// add statistics task.
// it needs request method, request url, request controller and statistics time duration
func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController string, requesttime time.Duration) { func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController string, requesttime time.Duration) {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
@ -65,6 +69,7 @@ func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController stri
} }
} }
// put url statistics result in io.Writer
func (m *UrlMap) GetMap(rw io.Writer) { func (m *UrlMap) GetMap(rw io.Writer) {
m.lock.RLock() m.lock.RLock()
defer m.lock.RUnlock() defer m.lock.RUnlock()
@ -78,6 +83,7 @@ func (m *UrlMap) GetMap(rw io.Writer) {
} }
} }
// global statistics data map
var StatisticsMap *UrlMap var StatisticsMap *UrlMap
func init() { func init() {

View File

@ -53,6 +53,7 @@ const (
starBit = 1 << 63 starBit = 1 << 63
) )
// time taks schedule
type Schedule struct { type Schedule struct {
Second uint64 Second uint64
Minute uint64 Minute uint64
@ -62,8 +63,10 @@ type Schedule struct {
Week uint64 Week uint64
} }
// task func type
type TaskFunc func() error type TaskFunc func() error
// task interface
type Tasker interface { type Tasker interface {
GetStatus() string GetStatus() string
Run() error Run() error
@ -73,21 +76,24 @@ type Tasker interface {
GetPrev() time.Time GetPrev() time.Time
} }
// task error
type taskerr struct { type taskerr struct {
t time.Time t time.Time
errinfo string errinfo string
} }
// task struct
type Task struct { type Task struct {
Taskname string Taskname string
Spec *Schedule Spec *Schedule
DoFunc TaskFunc DoFunc TaskFunc
Prev time.Time Prev time.Time
Next time.Time Next time.Time
Errlist []*taskerr //errtime:errinfo Errlist []*taskerr // like errtime:errinfo
ErrLimit int //max length for the errlist 0 stand for there' no limit ErrLimit int // max length for the errlist, 0 stand for no limit
} }
// add new task with name, time and func
func NewTask(tname string, spec string, f TaskFunc) *Task { func NewTask(tname string, spec string, f TaskFunc) *Task {
task := &Task{ task := &Task{
@ -99,6 +105,7 @@ func NewTask(tname string, spec string, f TaskFunc) *Task {
return task return task
} }
// get current task status
func (tk *Task) GetStatus() string { func (tk *Task) GetStatus() string {
var str string var str string
for _, v := range tk.Errlist { for _, v := range tk.Errlist {
@ -107,6 +114,7 @@ func (tk *Task) GetStatus() string {
return str return str
} }
// run task
func (tk *Task) Run() error { func (tk *Task) Run() error {
err := tk.DoFunc() err := tk.DoFunc()
if err != nil { if err != nil {
@ -117,53 +125,58 @@ func (tk *Task) Run() error {
return err return err
} }
// set next time for this task
func (tk *Task) SetNext(now time.Time) { func (tk *Task) SetNext(now time.Time) {
tk.Next = tk.Spec.Next(now) tk.Next = tk.Spec.Next(now)
} }
// get the next call time of this task
func (tk *Task) GetNext() time.Time { func (tk *Task) GetNext() time.Time {
return tk.Next return tk.Next
} }
// set prev time of this task
func (tk *Task) SetPrev(now time.Time) { func (tk *Task) SetPrev(now time.Time) {
tk.Prev = now tk.Prev = now
} }
// get prev time of this task
func (tk *Task) GetPrev() time.Time { func (tk *Task) GetPrev() time.Time {
return tk.Prev return tk.Prev
} }
//前6个字段分别表示 // six columns mean
// 秒钟0-59 // second0-59
// 分钟0-59 // minute0-59
// 小时1-23 // hour1-23
// 日期1-31 // day1-31
// 月份1-12 // month1-12
// 星期0-60表示周日 // week0-60 means Sunday
//还可以用一些特殊符号 // some signals
// * 表示任何时刻 // * any time
// , 表示分割如第三段里2,4表示2点和4点执行 // ,  separate signal
//   表示一个段,如第三端里: 1-5就表示1到5点 //   duration
// /n : 表示每个n的单位执行一次如第三段里*/1, 就表示每隔1个小时执行一次命令。也可以写成1-23/1. // /n : do as n times of time duration
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
// 0/30 * * * * * 每30秒 执行 // 0/30 * * * * * every 30s
// 0 43 21 * * * 21:43 执行 // 0 43 21 * * * 21:43
// 0 15 05 * * *    05:15 执行 // 0 15 05 * * *    05:15
// 0 0 17 * * * 17:00 执行 // 0 0 17 * * * 17:00
// 0 0 17 * * 1 每周一的 17:00 执行 // 0 0 17 * * 1 17:00 in every Monday
// 0 0,10 17 * * 0,2,3 每周日,周二,周三的 17:00和 17:10 执行 // 0 0,10 17 * * 0,2,3 17:00 and 17:10 in every Sunday, Tuesday and Wednesday
// 0 0-10 17 1 * * 毎月1日从 17:00到7:10 毎隔1分钟 执行 // 0 0-10 17 1 * * 17:00 to 17:10 in 1 min duration each time on the first day of month
// 0 0 0 1,15 * 1 毎月1日和 15日和 一日的 0:00 执行 // 0 0 0 1,15 * 1 0:00 on the 1st day and 15th day of month
// 0 42 4 1 * *     毎月1日的 4:42分 执行 // 0 42 4 1 * *     4:42 on the 1st day of month
// 0 0 21 * * 1-6   周一到周六 21:00 执行 // 0 0 21 * * 1-6   21:00 from Monday to Saturday
// 0 0,10,20,30,40,50 * * * *  每隔10分 执行 // 0 0,10,20,30,40,50 * * * *  every 10 min duration
// 0 */10 * * * *        每隔10分 执行 // 0 */10 * * * *        every 10 min duration
// 0 * 1 * * *         从1:0到1:59 每隔1分钟 执行 // 0 * 1 * * *         1:00 to 1:59 in 1 min duration each time
// 0 0 1 * * *         1:00 执行 // 0 0 1 * * *         1:00
// 0 0 */1 * * *        毎时0分 每隔1小时 执行 // 0 0 */1 * * *        0 min of hour in 1 hour duration
// 0 0 * * * *         毎时0分 每隔1小时 执行 // 0 0 * * * *         0 min of hour in 1 hour duration
// 0 2 8-20/3 * * *       8:02,11:02,14:02,17:02,20:02 执行 // 0 2 8-20/3 * * *       8:02, 11:02, 14:02, 17:02, 20:02
// 0 30 5 1,15 * *       1日 和 15日的 5:30 执行 // 0 30 5 1,15 * *       5:30 on the 1st day and 15th day of month
func (t *Task) SetCron(spec string) { func (t *Task) SetCron(spec string) {
t.Spec = t.parse(spec) t.Spec = t.parse(spec)
} }
@ -252,6 +265,7 @@ func (t *Task) parseSpec(spec string) *Schedule {
return nil return nil
} }
// set schedule to next time
func (s *Schedule) Next(t time.Time) time.Time { func (s *Schedule) Next(t time.Time) time.Time {
// Start at the earliest possible time (the upcoming second). // Start at the earliest possible time (the upcoming second).
@ -349,6 +363,7 @@ func dayMatches(s *Schedule, t time.Time) bool {
return domMatch || dowMatch return domMatch || dowMatch
} }
// start all tasks
func StartTask() { func StartTask() {
go run() go run()
} }
@ -388,10 +403,12 @@ func run() {
} }
} }
// start all tasks
func StopTask() { func StopTask() {
stop <- true stop <- true
} }
// add task with name
func AddTask(taskname string, t Tasker) { func AddTask(taskname string, t Tasker) {
AdminTaskList[taskname] = t AdminTaskList[taskname] = t
} }
@ -402,6 +419,7 @@ type MapSorter struct {
Vals []Tasker Vals []Tasker
} }
// create new tasker map
func NewMapSorter(m map[string]Tasker) *MapSorter { func NewMapSorter(m map[string]Tasker) *MapSorter {
ms := &MapSorter{ ms := &MapSorter{
Keys: make([]string, 0, len(m)), Keys: make([]string, 0, len(m)),
@ -414,6 +432,7 @@ func NewMapSorter(m map[string]Tasker) *MapSorter {
return ms return ms
} }
// sort tasker map
func (ms *MapSorter) Sort() { func (ms *MapSorter) Sort() {
sort.Sort(ms) sort.Sort(ms)
} }