🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## **简介** 在上一篇文章中介绍了Cookie的内容。在HTTP Request中,可以在Header中携带Cookie给服务端;在HTTP Response中,可以在Header中携带Cookie返回给客户端。但是,Cookie的结构体一般是固定的,携带的信息有限。此时,我们可以在服务端保存客户端的相关信息,然后客户端请求时,只需要携带一个Id给服务端,服务端根据此Id查询客户端相关的信息,这就是Session的基本机制。 这里列出了Session与Cookie的几个区别: * Cookie是通过HTTP协议的请求头中的Cookie字段与响应头中的Set-Cookie字段进行传输的 * Cookie的结构体是固定的,所携带的信息有限 * Cookie由服务端生成,返回给客户端,客户端请求时,再携带给服务端。所以,* Cookie是一种将状态信息存储在客户端的机制。 * Session机制是将状态信息存储在服务端,然后通过Cookie把SessionId传回给客户端 * Session的结构是不固定的,用户可以自已定义,存储的信息量比Cookie要多 * Session的后端存储引擎也比较丰富,有memory、file、redis、mysql等。 ## **示例** 接下来,我们通过一个例子(go语言)来展示一下session的使用。解释一下这段代码的意思: * 在浏览器(客户端)上访问 `x.x.x.x:8082/login?username=xxx` 时,程序(服务端)会新建一个Session,记录登录的用户名,并把SessionId通过Cookie传回给浏览器 * 在浏览器上访问 `x.x.x.x:8082/whoami` 时,浏览器会携带包含SessionId的Cookie给服务端,服务端根据SessionId查询到Session对象,得到用户名,返回给浏览器 * 在浏览器上访问 `x.x.x.x:8082/logout` 时,浏览器会携带包含SessionId的Cookie给服务端,服务端会根据SeesionId删除对应的Session对象,并且告诉浏览器删除对应的Cookie #### **session.go** ``` package main import ( "net/http" "github.com/astaxie/beego/session" ) var globalSessions *session.Manager func init() { sessionConfig := &session.ManagerConfig{ CookieName: "gosessionid", EnableSetCookie: true, Gclifetime: 3600, Maxlifetime: 3600, Secure: false, CookieLifeTime: 0, ProviderConfig: "./tmp", } globalSessions, _ = session.NewManager("memory", sessionConfig) go globalSessions.GC() } func main() { http.HandleFunc("/login", Login) http.HandleFunc("/logout", Logout) http.HandleFunc("/whoami", WhoAmI) http.ListenAndServe(":8082", nil) } func Login(w http.ResponseWriter, r *http.Request) { sess, _ := globalSessions.SessionStart(w, r) // get or generate a Store object, sess is type of Store username := sess.Get("username") // username is type of interface{} if username != nil { usernameInString, _ := username.(string) // convert interface{} to string w.Write([]byte("already logged in as user : " + usernameInString)) } else { queryUsername := r.URL.Query().Get("username") if queryUsername == "" { w.Write([]byte("query parameter missed : username")) globalSessions.SessionDestroy(w, r) return } sess.Set("username", queryUsername) w.Write([]byte("logged in successfully as user : " + queryUsername)) } } func Logout(w http.ResponseWriter, r *http.Request) { sess, _ := globalSessions.SessionStart(w, r) // get session object from request username := sess.Get("username") if username == nil { w.Write([]byte("logout failed : no user logged in")) } globalSessions.SessionDestroy(w, r) usernameInString, _ := username.(string) w.Write([]byte("logout successfully : " + usernameInString)) } func WhoAmI(w http.ResponseWriter, r *http.Request) { sess, _ := globalSessions.SessionStart(w, r) // get session object from request username := sess.Get("username") if username == nil { w.Write([]byte("i don't know who you are : no user logged in")) globalSessions.SessionDestroy(w, r) } else { usernameInString, _ := username.(string) w.Write([]byte("You are : " + usernameInString)) } } ``` #### **实验效果** * 首先登录 首次访问`/login?username=peng`,服务端会返回一个Cookie给浏览器,包含了SessionId的内容 ![](https://img.kancloud.cn/b2/cf/b2cf9e15e153f20a1e41d85684baa330_635x496.png) * 携带上述的Cookie访问 接着,访问页面 `/whoami`,此时浏览器会携带Cookie给服务端,服务端根据Cookie中的SessionId查到出对应的Session,然后返回用户名给浏览器 ![](https://img.kancloud.cn/63/9b/639b527d7ae71e9fa2d9f2c1c700ef52_409x106.png) * 登出系统 访问 `/logout`,浏览器把Cookie携带给服务端后,服务端会删除该SessionId的Session对象,并且告知浏览器出删除这个Cookie ![](https://img.kancloud.cn/a6/c2/a6c256e565643063568efd4e61f1a9af_600x485.png) * 再次访问 /whoami 登出系统后,再次访问`/whoami`,由于浏览器没有携带SessionId相关的Cookie,所以服务端会报不知道客户端是谁 ![](https://img.kancloud.cn/e2/e3/e2e3dd01f8359017692f01d011e3c38f_382x120.png) #### **代码详细解析** * `sess, _ := globalSessions.SessionStart(w, r)` 该语句的意思是从HTTP的请求头中读取承载SessionId的Cookie,然后在服务端查找出对应的Session对象,并且将该Cookie继续保存在HTTP的响应头中(http.RespondWriter);如果请求头中并没有相应的Cookie,则新建一个Session对象,并且把SessionId保存在Cookie中然后放到HTTP的响应头中。 * `sess.Set("username", queryUsername)` `globalSessions.SessionStart(w, r)` 返回的Session对象的结构体与具体的后端存储有关,在上面的代码中我们使用的memory存储引擎。详细的类的定义见下面的内容 #### **代码中各种结构体及函数的源码** * `func NewManager(...)` ``` func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) ``` * `Manager struct` ``` type Manager struct { provider Provider config *ManagerConfig } // getSid retrieves session identifier from HTTP Request. // First try to retrieve id by reading from cookie, session cookie name is configurable, // if not exist, then retrieve id from querying parameters. // // error is not nil when there is anything wrong. // sid is empty when need to generate a new session id // otherwise return an valid session id. func (manager *Manager) getSid(r *http.Request) (string, error) // SessionStart 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 Store, err error) // SessionDestroy Destroy session by its id in http request cookie func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) // GetSessionStore Get SessionStore by its id func (manager *Manager) GetSessionStore(sid string) (sessions Store, err error) // GC Start session gc process. // it can do gc in times after gc lifetime. func (manager *Manager) GC() // SessionRegenerateID 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 Store) // GetActiveSession Get all active sessions count number. func (manager *Manager) GetActiveSession() int // Set cookie with https func (manager *Manager) SetSecure(req *http.Request) bool // inner function func (manager *Manager) sessionID() (string, error) // inner function func (manager *Manager) isSecure(req *http.Request) bool ``` * Store interface ``` // Store contains all data for one session process with specific id. type Store interface { Set(key, value interface{}) error //set session value Get(key interface{}) interface{} //get session value Delete(key interface{}) error //delete session value SessionID() string //back current sessionID SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data Flush() error //delete all data } ``` * MemSessionStore struct ``` type MemSessionStore struct { sid string //session id timeAccessed time.Time //last access time value map[interface{}]interface{} //session store lock sync.RWMutex } // Set value to memory session func (st *MemSessionStore) Set(key, value interface{}) error // Get value from memory session by key func (st *MemSessionStore) Get(key interface{}) interface{} // Delete in memory session by key func (st *MemSessionStore) Delete(key interface{}) error // Flush clear all values in memory session func (st *MemSessionStore) Flush() error // SessionID get this id of memory session store func (st *MemSessionStore) SessionID() string // SessionRelease Implement method, no used. func (st *MemSessionStore) SessionRelease(w http.ResponseWriter) ``` ## **参考** [1] https://beego.me/docs/module/session.md