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 } 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) { defer fs.f.Close() b, err := EncodeGob(fs.values) if err != nil { return } fs.f.Truncate(0) fs.f.Seek(0, 0) fs.f.Write(b) } // 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) (SessionStore, 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() f, err = os.OpenFile(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), os.O_WRONLY|os.O_CREATE, 0777) ss := &FileSessionStore{f: f, 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) (SessionStore, 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 } } newf, err = os.OpenFile(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), os.O_WRONLY|os.O_CREATE, 0777) ss := &FileSessionStore{f: newf, 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) }