代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/25-session
一、Provider与Session的具体实现
上一节我们介绍了Session
管理器的实现原理,定义了存储session
的接口,这小节我们将示例一个基于内存的session
存储接口的实现,其他的存储方式,读者可以自行参考示例来实现,内存的实现请看下面的例子代码,逻辑并不复杂,直接看代码中注释即可
Provider与Session的具体实现均在如下代码中
package memory
import (
"container/list"
"golang-trick/25-session/session"
"sync"
"time"
)
var pder = &Provider{list: list.New()}
func init() {
pder.sessions = make(map[string]*list.Element, 0)
session.Register("memory", pder)
}
// 实现了session包中的Session接口,具备增删改查等操作
type SessionStore struct {
sid string //session id唯一标识
timeAccessed time.Time //最后访问时间,用于GC时判断是否距离当前时间超过了manager.maxlifetime
value map[interface{}]interface{} //session里面存储的数据
}
func (st *SessionStore) Set(key, value interface{}) error {
st.value[key] = value
// 更新当前sid对应的session的最后访问时间,并移动到链表头部,GC时,从尾部开始判断每个session是否过期
pder.SessionUpdate(st.sid)
return nil
}
func (st *SessionStore) Get(key interface{}) interface{} {
pder.SessionUpdate(st.sid)
if v, ok := st.value[key]; ok {
return v
} else {
return nil
}
return nil
}
func (st *SessionStore) Delete(key interface{}) error {
delete(st.value, key)
pder.SessionUpdate(st.sid)
return nil
}
func (st *SessionStore) SessionID() string {
return st.sid
}
// 实现了session包中的Provider接口
type Provider struct {
lock sync.Mutex //用来锁
sessions map[string]*list.Element //用来存储在内存,如果是存储在redis,则这里可能是redisClient
list *list.List //用来做gc,如果是存储在redis,则这个可以不要,而是通过redis自带的过期时间使得key过期即可
}
func (pder *Provider) SessionInit(sid string) (session.Session, error) {
pder.lock.Lock()
defer pder.lock.Unlock()
v := make(map[interface{}]interface{}, 0)
// 实际使用时,v中应该放入相关数据,如用户名,用户id等,这些则应该由SessionInit方法的参数传入,而不能只有一个sid作为参数
newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
element := pder.list.PushBack(newsess)
pder.sessions[sid] = element
return newsess, nil
}
func (pder *Provider) SessionRead(sid string) (session.Session, error) {
if element, ok := pder.sessions[sid]; ok {
return element.Value.(*SessionStore), nil
} else {
sess, err := pder.SessionInit(sid)
return sess, err
}
return nil, nil
}
// 重置服务端sid的session,就是从provider中删除相应数据
// 重置客户端sid的session,就是响应请求时,设置cookie头的内容为空
func (pder *Provider) SessionDestroy(sid string) error {
if element, ok := pder.sessions[sid]; ok {
delete(pder.sessions, sid)
pder.list.Remove(element)
return nil
}
return nil
}
// 从尾部开始判断每个session是否过期
func (pder *Provider) SessionGC(maxlifetime int64) {
pder.lock.Lock()
defer pder.lock.Unlock()
for {
element := pder.list.Back()
if element == nil {
break
}
if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
pder.list.Remove(element)
delete(pder.sessions, element.Value.(*SessionStore).sid)
} else {
break
}
}
}
// 更新当前sid对应的session的最后访问时间,并移动到链表头部,GC时,从尾部开始判断每个session是否过期
func (pder *Provider) SessionUpdate(sid string) error {
pder.lock.Lock()
defer pder.lock.Unlock()
if element, ok := pder.sessions[sid]; ok {
element.Value.(*SessionStore).timeAccessed = time.Now()
pder.list.MoveToFront(element)
return nil
}
return nil
}
二、如何使用我们自己写的session
上面这个代码实现了一个内存存储的session
机制。通过init
函数注册到session
管理器中。这样就可以方便的调用了。我们如何来调用该引擎呢?请看下面的代码
import (
_ "golang-trick/25-session/memory" // memory 包的引入方式用下划线,只需执行 memory的init方法即可。
"golang-trick/25-session/session"
)
当import
的时候已经执行了memory
函数里面的init
函数,这样就已经注册到session
管理器中,我们就可以使用了,通过如下方式就可以初始化一个session
管理器,,后续直接使用globalSessions
即可,我们将原来放在manager.go文件中的globalSessions 相关代码放到main.go中:
package main
import (
"fmt"
_ "golang-trick/25-session/memory" // memory 包的引入方式用下划线,只需执行 memory的init方法即可。
"golang-trick/25-session/session"
"log"
"net/http"
)
var globalSessions *session.Manager
func init() {
var err error
globalSessions, err = session.NewManager("memory", "goSessionid", 3600)
if err != nil {
fmt.Println(err)
return
}
go globalSessions.GC()
fmt.Println("fd")
}
func sayHelloHandler(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("goSessionid")
if err == nil {
fmt.Println(cookie.Value)
}
}
func login(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
val := sess.Get("username")
if val != nil {
fmt.Println(val)
} else {
sess.Set("username", "jerry")
fmt.Println("set session")
}
}
func loginOut(w http.ResponseWriter, r *http.Request) {
//销毁
globalSessions.SessionDestroy(w, r)
fmt.Println("session destroy")
}
func main() {
http.HandleFunc("/", sayHelloHandler) // 设置访问路由
http.HandleFunc("/login", login)
http.HandleFunc("/logout", loginOut) //销毁
log.Fatal(http.ListenAndServe(":8080", nil))
}
注:memory 包的引入方式用下划线,只需执行 memory的init方法即可。
三、启动服务测试
在浏览器中输入http://localhost:8080/login
,可以看到控制台输出
也可以从浏览器中看到cookie
详情
访问http://localhost:8080/
,控制台输出
在浏览器中输入http://localhost:8080/logout
,可以看到控制台输出
浏览器再次查看cookie
,可以看到name
为goSessionid
的cookie
被删除了
附:直接使用已有的github.com/gorilla/sessions
包
package main
import (
"fmt"
"net/http"
"github.com/gorilla/sessions"
)
var store = sessions.NewCookieStore([]byte("secret-key"))
func main() {
http.HandleFunc("/", homeHandler)
http.HandleFunc("/login", loginHandler)
http.HandleFunc("/logout", logoutHandler)
fmt.Println("Server started on http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
// Check if the user is authenticated
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Display the user's name
name := session.Values["name"].(string)
fmt.Fprintf(w, "Welcome, %s!", name)
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
// Set user authentication status and name in the session
session.Values["authenticated"] = true
session.Values["name"] = "John Doe"
// Save the session
session.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func logoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
// Revoke user authentication status and clear session data
session.Values["authenticated"] = false
session.Values["name"] = ""
// Save the session
session.Save(r, w)
http.Redirect(w, r, "/login", http.StatusSeeOther)
}