go web programming --session Management Mechanism Design and Implementation

Go native language does not implement session management mechanism, so if you are using native web programming language Go, we need to design and implement session management mechanisms themselves, this article will be described in detail, and implement a simple session management mechanism.
session information can be used such as memory, file or database storage manner, taking into account the compatibility of different storage methods, session management mechanism we designed should be able to easily switch between different storage methods. Therefore, session management mechanism can be divided into two parts: session manager and session memory, session manager is responsible for co-operation portion of the plurality of storage, e.g., cookie is read and set, generate the session ID, as well as some common required parameter settings and so on. session management structure may be provided as follows:

// session manager 
of the type struct {SessionManager 
	cookieName String // name of the cookie 
	cookieExpire int // cookie expiration time (unit: minutes, 0 means that the session the cookie) 
	sessionExpire int64 // session expiration time (unit: minutes) 
	gcDuration int // garbage collection operation mechanism interval (unit: min) 
	Provider SessionProvider memory // the session 
}

Its creation methods are:

//创建session管理器
func NewManager(cookieName string, cookieExpire int, sessionExpire int64, gcDuration int, provider SessionProvider) *SessionManager {
	return &SessionManager{
		cookieName:    cookieName,
		cookieExpire:  cookieExpire,
		sessionExpire: sessionExpire,
		gcDuration:    gcDuration,
		provider:      provider,
	}
}

While the session corresponding to a specific memory storage is simply responsible for data read and write operations according to the session ID of the session, this part of different ways depending on the memory, but the method signature is consistent, the interface can define the type:

// session memory 
type interface SessionProvider { 
	Create (String sessionId, Data Map [String] interface {}) // Create error session 
	GET (sessionId, String Key) (String, error) // read the session key 
	getAll (sessionId string ) (map [string] string, error) // read all session keys to 
	set (sessionId, key string, value interface {}) error // set session key 
	destroy (sessionId string) error // destruction session 
	GC ( expire int64) error // garbage collection: delete expired the session 
}

The method defined next session manager generates the session ID, the header information may be generated upon request, here is an example:

//生成session ID
func (sm *SessionManager) createSessionId(req *http.Request) string {
	addr := req.RemoteAddr
	userAgent := req.Header.Get("User-Agent")
	rand.Seed(time.Now().UnixNano())
	n := rand.Intn(10000)
	str := addr + "_" + userAgent + "_" + strconv.Itoa(n)
	h := md5.New()
	h.Write([]byte(str))
	cipherStr := h.Sum(nil)
	return hex.EncodeToString(cipherStr)
}

Various methods of session manager will need to read cookie, get session ID:

//读cookie,获取session ID
func (sm *SessionManager) getSessionId(req *http.Request) (string, error) {
	c, err := req.Cookie(sm.cookieName)
	if err != nil {
		return "", errors.New("Reading cookie failed: " + err.Error())
	}
	if len(c.Value) == 0 { //尚未设置cookie
		return "", errors.New("Cookie does not exists: " + sm.cookieName)
	}
	return c.Value, nil
}

最后就是session管理器创建session、读取session、写入session、销毁session、session GC等方法的定义,这些方法比较简单,只是调用session存储器对应的方法即可:

//创建session
func (sm *SessionManager) Create(writer *http.ResponseWriter, req *http.Request, data map[string]interface{}) error {
	sessionId, _ := sm.getSessionId(req)
	if len(sessionId) > 0 {
		data, _ := sm.provider.getAll(sessionId)
		if data != nil { //已有session,无需创建
			return nil
		}
	}
	sessionId = sm.createSessionId(req)
	if len(sessionId) == 0 {
		return errors.New("Length of sessionId is 0")
	}
	err := sm.provider.create(sessionId, data)
	if err != nil {
		return err
	}
	if sm.cookieExpire == 0 { //会话cookie
		http.SetCookie(*writer, &http.Cookie{
			Name:     sm.cookieName,
			Value:    sessionId,
			Path:     "/", //一定要设置为根目录,才能在所有页面生效
			HttpOnly: true,
		})
	} else { //持久cookie
		expire, _ := time.ParseDuration(strconv.Itoa(sm.cookieExpire) + "m")
		http.SetCookie(*writer, &http.Cookie{
			Name:     sm.cookieName,
			Value:    sessionId,
			Path:     "/", //一定要设置为根目录,才能在所有页面生效
			Expires:  time.Now().Add(expire),
			HttpOnly: true,
		})
	}
	return nil
}

//获取session键值
func (sm *SessionManager) Get(writer *http.ResponseWriter, req *http.Request, key string) (string, error) {
	sessionId, _ := sm.getSessionId(req)
	if len(sessionId) == 0 {
		return "", errors.New("Length of sessionId is 0")
	}
	return sm.provider.get(sessionId, key)
}

//读取session所有键值对
func (sm *SessionManager) GetAll(writer *http.ResponseWriter, req *http.Request) (map[string]string, error) {
	sessionId, _ := sm.getSessionId(req)
	if len(sessionId) == 0 {
		return nil, errors.New("Length of sessionId is 0")
	}
	return sm.provider.getAll(sessionId)
}

//设置session键值
func (sm *SessionManager) Set(writer *http.ResponseWriter, req *http.Request, key string, value interface{}) error {
	sessionId, _ := sm.getSessionId(req)
	if len(sessionId) == 0 {
		return errors.New("Length of sessionId is 0")
	}
	return sm.provider.set(sessionId, key, value)
}

//销毁session
func (sm *SessionManager) Destroy(req *http.Request) error {
	sessionId, _ := sm.getSessionId(req)
	if len(sessionId) == 0 {
		return errors.New("Length of sessionId is 0")
	}
	return sm.provider.destroy(sessionId)
}

//垃圾回收:删除过期session
func (sm *SessionManager) Gc() error {
	err := sm.provider.gc(sm.sessionExpire)
	duration, _ := time.ParseDuration(strconv.Itoa(sm.gcDuration) + "m")
	time.AfterFunc(duration, func() { sm.Gc() }) //设置下次运行时间
	return err
}

至此,我们已经实现session管理器!

接下来,不管使用什么方式存储session信息,只要实现SessionProvider接口,关心session数据的读写操作即可。
这里,我们实现一个文件session存储器,除了session文件保存路径,为了并发安全,每个session文件还需要对应一个读写锁,所以其结构可设计为:

//文件session存储器
type FileProvider struct {
	savePath string                   //session文件保存路径
	muxMap   map[string]*sync.RWMutex //session文件锁
}

对应的创建方法:

//创建文件session存储器对象
func NewFileProvider(savePath string) *FileProvider {
	return &FileProvider{
		savePath: savePath,
		muxMap:   make(map[string]*sync.RWMutex),
	}
}

所有方法都需要根据session ID得到文件路径,可定义共用方法:

//返回session文件名称
func (fp FileProvider) filename(sessionId string) string {
	return fp.savePath + "/" + sessionId
}

写入session文件时,数据只能是字符串,而存入session的却不一定是字符串,所以需要一个将其他数据类型转换为字符串的共用方法:

//将数据类型转换为字符串
func (fp FileProvider) toString(value interface{}) (string, error) {
	var str string
	vType := reflect.TypeOf(value)
	switch vType.Name() {
	case "int":
		i, _ := value.(int)
		str = strconv.Itoa(i)
	case "string":
		str, _ = value.(string)
	case "int64":
		i, _ := value.(int64)
		str = strconv.FormatInt(i, 10)
	default:
		return "", errors.New("Unsupported type: " + vType.Name())
	}
	return str, nil
}

文件session存储器的create、get、getAll、set等四个方法,本质上都是对session文件进行读写操作,可以将读和写抽取出来成为两个共用方法:

//创建/重写session文件
func (fp FileProvider) write(sessionId string, data map[string]interface{}, newFile bool) error {
	_, exist := fp.muxMap[sessionId]
	if !exist { //内存中没有锁,先建锁
		fp.muxMap[sessionId] = new(sync.RWMutex)
	}
	fp.muxMap[sessionId].Lock()
	defer func() {
		fp.muxMap[sessionId].Unlock()
	}()
	fname := fp.filename(sessionId)
	_, err := os.Stat(fname)
	var f *os.File
	if newFile {
		if err == nil { //若session文件存在,则先删除
			os.Remove(fname)
		}
		f, err = os.Create(fname)
		if err != nil {
			return errors.New("Creating session file failed: " + err.Error())
		}
	} else {
		if err != nil { //session文件不存在
			return errors.New("Session file does not exists: " + fname)
		}
		f, err = os.OpenFile(fname, os.O_RDWR|os.O_TRUNC, 0644)
		if err != nil {
			return errors.New("Opening session file failed: " + err.Error())
		}
	}
	defer func() {
		os.Chtimes(fname, time.Now(), time.Now()) //更新文件最后访问时间
		f.Close()
	}()
	for key, value := range data {
		str, err := fp.toString(value)
		if err != nil {
			return err
		}
		_, err = fmt.Fprintln(f, key+":"+str)
		if err != nil {
			return errors.New("Setting session key value failed: " + err.Error())
		}
	}
	return nil
}

//读取session文件
func (fp FileProvider) read(sessionId string) (map[string]string, error) {
	fname := fp.filename(sessionId)
	_, err := os.Stat(fname)
	if err != nil { //session文件不存在
		return nil, errors.New("Session file does not exists: " + fname)
	}
	_, exist := fp.muxMap[sessionId]
	if !exist { //内存中没有锁,先建锁
		fp.muxMap[sessionId] = new(sync.RWMutex)
	}
	fp.muxMap[sessionId].Lock()
	defer func() {
		fp.muxMap[sessionId].Unlock()
	}()
	f, err := os.Open(fname)
	if err != nil {
		return nil, errors.New("Opening session file failed: " + err.Error())
	}
	defer func() {
		os.Chtimes(fname, time.Now(), time.Now()) //更新文件最后访问时间
		f.Close()
	}()
	data := make(map[string]string)
	scaner := bufio.NewScanner(f)
	for scaner.Scan() {
		kv := strings.Split(scaner.Text(), ":")
		if len(kv) != 2 {
			continue
		}
		data[kv[0]] = kv[1]
	}
	if len(data) == 0 {
		return nil, errors.New("No data in session file")
	}
	return data, nil
}

最后,实现SessionProvider接口的6个方法:

//创建session
func (fp FileProvider) create(sessionId string, data map[string]interface{}) error {
	return fp.write(sessionId, data, true)
}

//读取session键值
func (fp FileProvider) get(sessionId, key string) (string, error) {
	data, err := fp.read(sessionId)
	if err != nil {
		return "", err
	}
	value, ok := data[key]
	if !ok {
		return "", errors.New("Session key does not exists: " + key)
	}
	return value, nil
}

//读取session所有键值对
func (fp FileProvider) getAll(sessionId string) (map[string]string, error) {
	return fp.read(sessionId)
}

//设置session键值
func (fp FileProvider) set(sessionId, key string, value interface{}) error {
	data, err := fp.read(sessionId)
	if data == nil {
		return err
	}
	str, err := fp.toString(value)
	if err != nil {
		return err
	}
	data[key] = str
	//数据类型转换
	intData := make(map[string]interface{})
	for k, v := range data {
		intData[k] = v
	}
	return fp.write(sessionId, intData, false)
}

//销毁session:删除session文件
func (fp FileProvider) destroy(sessionId string) error {
	fname := fp.filename(sessionId)
	_, err := os.Stat(fname)
	if err != nil { //session文件不存在
		return errors.New("Session file does not exists: " + fname)
	}
	_, exist := fp.muxMap[sessionId]
	if !exist { //内存中没有锁,先建锁
		fp.muxMap[sessionId] = new(sync.RWMutex)
	}
	fp.muxMap[sessionId].Lock()
	err = os.Remove(fname)
	fp.muxMap[sessionId].Unlock()
	if err != nil {
		return errors.New("Removing session file failed: " + err.Error())
	}
	delete(fp.muxMap, sessionId)
	return nil
}

//垃圾回收:删除过期session文件
func (fp FileProvider) gc(expire int64) error {
	now := time.Now().Unix()
	for sessionId, mux := range fp.muxMap {
		fname := fp.filename(sessionId)
		if len(fname) == 0 {
			continue
		}
		mux.Lock()
		info, err := os.Stat(fname)
		if err != nil {
			mux.Unlock()
			continue
		}
		modTime := info.ModTime().Unix() //文件最后访问时间
		if modTime+expire*60 < now {     //已超出过期时间
			err = os.Remove(fname)
			mux.Unlock()
			if err != nil {
				delete(fp.muxMap, sessionId)
			}
		} else {
			mux.Unlock()
		}
	}
	return nil
}

这样就完成了文件session存储器的实现。
当然我们也可以使用内存或数据库等其他方式进行session数据的存储,只需实现SessionProvider接口,并将其实例化对象赋值给session管理器创建方法的provider参数,即可实现不同存储方式的快速切换。

Guess you like

Origin www.cnblogs.com/wujuntian/p/12070875.html