github.com/astaxie/beego@v1.12.3/session/sess_file.go (about)

     1  // Copyright 2014 beego Author. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package session
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net/http"
    22  	"os"
    23  	"path"
    24  	"path/filepath"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  )
    29  
    30  var (
    31  	filepder      = &FileProvider{}
    32  	gcmaxlifetime int64
    33  )
    34  
    35  // FileSessionStore File session store
    36  type FileSessionStore struct {
    37  	sid    string
    38  	lock   sync.RWMutex
    39  	values map[interface{}]interface{}
    40  }
    41  
    42  // Set value to file session
    43  func (fs *FileSessionStore) Set(key, value interface{}) error {
    44  	fs.lock.Lock()
    45  	defer fs.lock.Unlock()
    46  	fs.values[key] = value
    47  	return nil
    48  }
    49  
    50  // Get value from file session
    51  func (fs *FileSessionStore) Get(key interface{}) interface{} {
    52  	fs.lock.RLock()
    53  	defer fs.lock.RUnlock()
    54  	if v, ok := fs.values[key]; ok {
    55  		return v
    56  	}
    57  	return nil
    58  }
    59  
    60  // Delete value in file session by given key
    61  func (fs *FileSessionStore) Delete(key interface{}) error {
    62  	fs.lock.Lock()
    63  	defer fs.lock.Unlock()
    64  	delete(fs.values, key)
    65  	return nil
    66  }
    67  
    68  // Flush Clean all values in file session
    69  func (fs *FileSessionStore) Flush() error {
    70  	fs.lock.Lock()
    71  	defer fs.lock.Unlock()
    72  	fs.values = make(map[interface{}]interface{})
    73  	return nil
    74  }
    75  
    76  // SessionID Get file session store id
    77  func (fs *FileSessionStore) SessionID() string {
    78  	return fs.sid
    79  }
    80  
    81  // SessionRelease Write file session to local file with Gob string
    82  func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
    83  	filepder.lock.Lock()
    84  	defer filepder.lock.Unlock()
    85  	b, err := EncodeGob(fs.values)
    86  	if err != nil {
    87  		SLogger.Println(err)
    88  		return
    89  	}
    90  	_, err = os.Stat(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid))
    91  	var f *os.File
    92  	if err == nil {
    93  		f, err = os.OpenFile(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid), os.O_RDWR, 0777)
    94  		if err != nil {
    95  			SLogger.Println(err)
    96  			return
    97  		}
    98  	} else if os.IsNotExist(err) {
    99  		f, err = os.Create(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid))
   100  		if err != nil {
   101  			SLogger.Println(err)
   102  			return
   103  		}
   104  	} else {
   105  		return
   106  	}
   107  	f.Truncate(0)
   108  	f.Seek(0, 0)
   109  	f.Write(b)
   110  	f.Close()
   111  }
   112  
   113  // FileProvider File session provider
   114  type FileProvider struct {
   115  	lock        sync.RWMutex
   116  	maxlifetime int64
   117  	savePath    string
   118  }
   119  
   120  // SessionInit Init file session provider.
   121  // savePath sets the session files path.
   122  func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error {
   123  	fp.maxlifetime = maxlifetime
   124  	fp.savePath = savePath
   125  	return nil
   126  }
   127  
   128  // SessionRead Read file session by sid.
   129  // if file is not exist, create it.
   130  // the file path is generated from sid string.
   131  func (fp *FileProvider) SessionRead(sid string) (Store, error) {
   132  	invalidChars := "./"
   133  	if strings.ContainsAny(sid, invalidChars) {
   134  		return nil, errors.New("the sid shouldn't have following characters: " + invalidChars)
   135  	}
   136  	if len(sid) < 2 {
   137  		return nil, errors.New("length of the sid is less than 2")
   138  	}
   139  	filepder.lock.Lock()
   140  	defer filepder.lock.Unlock()
   141  
   142  	err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0755)
   143  	if err != nil {
   144  		SLogger.Println(err.Error())
   145  	}
   146  	_, err = os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
   147  	var f *os.File
   148  	if err == nil {
   149  		f, err = os.OpenFile(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), os.O_RDWR, 0777)
   150  	} else if os.IsNotExist(err) {
   151  		f, err = os.Create(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
   152  	} else {
   153  		return nil, err
   154  	}
   155  
   156  	defer f.Close()
   157  
   158  	os.Chtimes(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), time.Now(), time.Now())
   159  	var kv map[interface{}]interface{}
   160  	b, err := ioutil.ReadAll(f)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	if len(b) == 0 {
   165  		kv = make(map[interface{}]interface{})
   166  	} else {
   167  		kv, err = DecodeGob(b)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  	}
   172  
   173  	ss := &FileSessionStore{sid: sid, values: kv}
   174  	return ss, nil
   175  }
   176  
   177  // SessionExist Check file session exist.
   178  // it checks the file named from sid exist or not.
   179  func (fp *FileProvider) SessionExist(sid string) bool {
   180  	filepder.lock.Lock()
   181  	defer filepder.lock.Unlock()
   182  
   183  	if len(sid) < 2 {
   184  		SLogger.Println("min length of session id is 2", sid)
   185  		return false
   186  	}
   187  
   188  	_, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
   189  	return err == nil
   190  }
   191  
   192  // SessionDestroy Remove all files in this save path
   193  func (fp *FileProvider) SessionDestroy(sid string) error {
   194  	filepder.lock.Lock()
   195  	defer filepder.lock.Unlock()
   196  	os.Remove(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
   197  	return nil
   198  }
   199  
   200  // SessionGC Recycle files in save path
   201  func (fp *FileProvider) SessionGC() {
   202  	filepder.lock.Lock()
   203  	defer filepder.lock.Unlock()
   204  
   205  	gcmaxlifetime = fp.maxlifetime
   206  	filepath.Walk(fp.savePath, gcpath)
   207  }
   208  
   209  // SessionAll Get active file session number.
   210  // it walks save path to count files.
   211  func (fp *FileProvider) SessionAll() int {
   212  	a := &activeSession{}
   213  	err := filepath.Walk(fp.savePath, func(path string, f os.FileInfo, err error) error {
   214  		return a.visit(path, f, err)
   215  	})
   216  	if err != nil {
   217  		SLogger.Printf("filepath.Walk() returned %v\n", err)
   218  		return 0
   219  	}
   220  	return a.total
   221  }
   222  
   223  // SessionRegenerate Generate new sid for file session.
   224  // it delete old file and create new file named from new sid.
   225  func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (Store, error) {
   226  	filepder.lock.Lock()
   227  	defer filepder.lock.Unlock()
   228  
   229  	oldPath := path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1]))
   230  	oldSidFile := path.Join(oldPath, oldsid)
   231  	newPath := path.Join(fp.savePath, string(sid[0]), string(sid[1]))
   232  	newSidFile := path.Join(newPath, sid)
   233  
   234  	// new sid file is exist
   235  	_, err := os.Stat(newSidFile)
   236  	if err == nil {
   237  		return nil, fmt.Errorf("newsid %s exist", newSidFile)
   238  	}
   239  
   240  	err = os.MkdirAll(newPath, 0755)
   241  	if err != nil {
   242  		SLogger.Println(err.Error())
   243  	}
   244  
   245  	// if old sid file exist
   246  	// 1.read and parse file content
   247  	// 2.write content to new sid file
   248  	// 3.remove old sid file, change new sid file atime and ctime
   249  	// 4.return FileSessionStore
   250  	_, err = os.Stat(oldSidFile)
   251  	if err == nil {
   252  		b, err := ioutil.ReadFile(oldSidFile)
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  
   257  		var kv map[interface{}]interface{}
   258  		if len(b) == 0 {
   259  			kv = make(map[interface{}]interface{})
   260  		} else {
   261  			kv, err = DecodeGob(b)
   262  			if err != nil {
   263  				return nil, err
   264  			}
   265  		}
   266  
   267  		ioutil.WriteFile(newSidFile, b, 0777)
   268  		os.Remove(oldSidFile)
   269  		os.Chtimes(newSidFile, time.Now(), time.Now())
   270  		ss := &FileSessionStore{sid: sid, values: kv}
   271  		return ss, nil
   272  	}
   273  
   274  	// if old sid file not exist, just create new sid file and return
   275  	newf, err := os.Create(newSidFile)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  	newf.Close()
   280  	ss := &FileSessionStore{sid: sid, values: make(map[interface{}]interface{})}
   281  	return ss, nil
   282  }
   283  
   284  // remove file in save path if expired
   285  func gcpath(path string, info os.FileInfo, err error) error {
   286  	if err != nil {
   287  		return err
   288  	}
   289  	if info.IsDir() {
   290  		return nil
   291  	}
   292  	if (info.ModTime().Unix() + gcmaxlifetime) < time.Now().Unix() {
   293  		os.Remove(path)
   294  	}
   295  	return nil
   296  }
   297  
   298  type activeSession struct {
   299  	total int
   300  }
   301  
   302  func (as *activeSession) visit(paths string, f os.FileInfo, err error) error {
   303  	if err != nil {
   304  		return err
   305  	}
   306  	if f.IsDir() {
   307  		return nil
   308  	}
   309  	as.total = as.total + 1
   310  	return nil
   311  }
   312  
   313  func init() {
   314  	Register("file", filepder)
   315  }