在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ GO/ 6.2 Go如何使用session
7 文本處理
3 Web基礎
14 擴展Web框架
10.4 小結
2.2 Go基礎
2.8 總結
6.1 session和cookie
5.5 使用beedb庫進行ORM開發(fā)
8.3 REST
13.6 小結
5.4 使用PostgreSQL數據庫
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調試
7.7 小結
1 GO環(huán)境配置
14.5 多語言支持
7.1 XML處理
1.5 總結
13 如何設計一個Web框架
14.3 表單及驗證支持
12 部署與維護
10 國際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數據庫操作
6.5 小結
9.4 避免SQL注入
12.1 應用日志
4.2 驗證表單的輸入
10.1 設置默認地區(qū)
1.3 Go 命令
9.6 加密和解密數據
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測試用例
8 Web服務
12.3 應用部署
5.7 小結
12.5 小結
11 錯誤處理,調試和測試
9.2 確保輸入過濾
14.2 Session支持
6.4 預防session劫持
12.4 備份和恢復
8.1 Socket編程
13.1 項目規(guī)劃
13.4 日志和配置設計
7.6 字符串處理
13.2 自定義路由器設計
6.3 session存儲
3.4 Go的http包詳解
8.2 WebSocket
10.3 國際化站點
7.5 文件操作
7.4 模板處理
9.1 預防CSRF攻擊
13.3 controller設計
2.6 interface
14.4 用戶認證
2.3 流程和函數
附錄A 參考資料
11.1 錯誤處理
9.5 存儲密碼
9.3 避免XSS攻擊
12.2 網站錯誤處理
6 session和數據存儲
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向對象
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結
13.5 實現博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語言基礎
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結
4.3 預防跨站腳本
5.3 使用SQLite數據庫
14.7 小結
3.2 Go搭建一個Web服務器
2.7 并發(fā)
5 訪問數據庫
4 表單
3.5 小結
1.4 Go開發(fā)工具
11.4 小結
9 安全與加密
5.2 使用MySQL數據庫
4.6 小結
8.4 RPC

6.2 Go如何使用session

通過上一小節(jié)的介紹,我們知道session是在服務器端實現的一種用戶和服務器之間認證的解決方案,目前Go標準包沒有為session提供任何支持,這小節(jié)我們將會自己動手來實現go版本的session管理和創(chuàng)建。

session創(chuàng)建過程

session的基本原理是由服務器為每個會話維護一份信息數據,客戶端和服務端依靠一個全局唯一的標識來訪問這份數據,以達到交互的目的。當用戶訪問Web應用時,服務端程序會隨需要創(chuàng)建session,這個過程可以概括為三個步驟:

  • 生成全局唯一標識符(sessionid);
  • 開辟數據存儲空間。一般會在內存中創(chuàng)建相應的數據結構,但這種情況下,系統一旦掉電,所有的會話數據就會丟失,如果是電子商務類網站,這將造成嚴重的后果。所以為了解決這類問題,你可以將會話數據寫到文件里或存儲在數據庫中,當然這樣會增加I/O開銷,但是它可以實現某種程度的session持久化,也更有利于session的共享;
  • 將session的全局唯一標示符發(fā)送給客戶端。

以上三個步驟中,最關鍵的是如何發(fā)送這個session的唯一標識這一步上。考慮到HTTP協議的定義,數據無非可以放到請求行、頭域或Body里,所以一般來說會有兩種常用的方式:cookie和URL重寫。

  1. Cookie 服務端通過設置Set-cookie頭就可以將session的標識符傳送到客戶端,而客戶端此后的每一次請求都會帶上這個標識符,另外一般包含session信息的cookie會將失效時間設置為0(會話cookie),即瀏覽器進程有效時間。至于瀏覽器怎么處理這個0,每個瀏覽器都有自己的方案,但差別都不會太大(一般體現在新建瀏覽器窗口的時候);
  2. URL重寫 所謂URL重寫,就是在返回給用戶的頁面里的所有的URL后面追加session標識符,這樣用戶在收到響應之后,無論點擊響應頁面里的哪個鏈接或提交表單,都會自動帶上session標識符,從而就實現了會話的保持。雖然這種做法比較麻煩,但是,如果客戶端禁用了cookie的話,此種方案將會是首選。

Go實現session管理

通過上面session創(chuàng)建過程的講解,讀者應該對session有了一個大體的認識,但是具體到動態(tài)頁面技術里面,又是怎么實現session的呢?下面我們將結合session的生命周期(lifecycle),來實現go語言版本的session管理。

session管理設計

我們知道session管理涉及到如下幾個因素

  • 全局session管理器
  • 保證sessionid 的全局唯一性
  • 為每個客戶關聯一個session
  • session 的存儲(可以存儲到內存、文件、數據庫等)
  • session 過期處理

接下來我將講解一下我關于session管理的整個設計思路以及相應的go代碼示例:

Session管理器

定義一個全局的session管理器

type Manager struct {
    cookieName  string     //private cookiename
    lock        sync.Mutex // protects session
    provider    Provider
    maxlifetime int64
}

func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
    provider, ok := provides[provideName]
    if !ok {
        return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
    }
    return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
}

Go實現整個的流程應該也是這樣的,在main包中創(chuàng)建一個全局的session管理器

var globalSessions *session.Manager
//然后在init函數中初始化
func init() {
    globalSessions, _ = NewManager("memory","gosessionid",3600)
}

我們知道session是保存在服務器端的數據,它可以以任何的方式存儲,比如存儲在內存、數據庫或者文件中。因此我們抽象出一個Provider接口,用以表征session管理器底層存儲結構。

type Provider interface {
    SessionInit(sid string) (Session, error)
    SessionRead(sid string) (Session, error)
    SessionDestroy(sid string) error
    SessionGC(maxLifeTime int64)
}
  • SessionInit函數實現Session的初始化,操作成功則返回此新的Session變量
  • SessionRead函數返回sid所代表的Session變量,如果不存在,那么將以sid為參數調用SessionInit函數創(chuàng)建并返回一個新的Session變量
  • SessionDestroy函數用來銷毀sid對應的Session變量
  • SessionGC根據maxLifeTime來刪除過期的數據

那么Session接口需要實現什么樣的功能呢?有過Web開發(fā)經驗的讀者知道,對Session的處理基本就 設置值、讀取值、刪除值以及獲取當前sessionID這四個操作,所以我們的Session接口也就實現這四個操作。

type Session 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
}

以上設計思路來源于database/sql/driver,先定義好接口,然后具體的存儲session的結構實現相應的接口并注冊后,相應功能這樣就可以使用了,以下是用來隨需注冊存儲session的結構的Register函數的實現。

var provides = make(map[string]Provider)

// Register makes a session provide available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, provider Provider) {
    if provider == nil {
        panic("session: Register provide is nil")
    }
    if _, dup := provides[name]; dup {
        panic("session: Register called twice for provide " + name)
    }
    provides[name] = provider
}

全局唯一的Session ID

Session ID是用來識別訪問Web應用的每一個用戶,因此必須保證它是全局唯一的(GUID),下面代碼展示了如何滿足這一需求:

func (manager *Manager) sessionId() string {
    b := make([]byte, 32)
    if _, err := io.ReadFull(rand.Reader, b); err != nil {
        return ""
    }
    return base64.URLEncoding.EncodeToString(b)
}

session創(chuàng)建

我們需要為每個來訪用戶分配或獲取與他相關連的Session,以便后面根據Session信息來驗證操作。SessionStart這個函數就是用來檢測是否已經有某個Session與當前來訪用戶發(fā)生了關聯,如果沒有則創(chuàng)建之。

func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
    manager.lock.Lock()
    defer manager.lock.Unlock()
    cookie, err := r.Cookie(manager.cookieName)
    if err != nil || cookie.Value == "" {
        sid := manager.sessionId()
        session, _ = manager.provider.SessionInit(sid)
        cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)}
        http.SetCookie(w, &cookie)
    } else {
        sid, _ := url.QueryUnescape(cookie.Value)
        session, _ = manager.provider.SessionRead(sid)
    }
    return
}

我們用前面login操作來演示session的運用:

func login(w http.ResponseWriter, r *http.Request) {
    sess := globalSessions.SessionStart(w, r)
    r.ParseForm()
    if r.Method == "GET" {
        t, _ := template.ParseFiles("login.gtpl")
        w.Header().Set("Content-Type", "text/html")
        t.Execute(w, sess.Get("username"))
    } else {
        sess.Set("username", r.Form["username"])
        http.Redirect(w, r, "/", 302)
    }
}

操作值:設置、讀取和刪除

SessionStart函數返回的是一個滿足Session接口的變量,那么我們該如何用他來對session數據進行操作呢?

上面的例子中的代碼session.Get("uid")已經展示了基本的讀取數據的操作,現在我們再來看一下詳細的操作:

func count(w http.ResponseWriter, r *http.Request) {
    sess := globalSessions.SessionStart(w, r)
    createtime := sess.Get("createtime")
    if createtime == nil {
        sess.Set("createtime", time.Now().Unix())
    } else if (createtime.(int64) + 360) < (time.Now().Unix()) {
        globalSessions.SessionDestroy(w, r)
        sess = globalSessions.SessionStart(w, r)
    }
    ct := sess.Get("countnum")
    if ct == nil {
        sess.Set("countnum", 1)
    } else {
        sess.Set("countnum", (ct.(int) + 1))
    }
    t, _ := template.ParseFiles("count.gtpl")
    w.Header().Set("Content-Type", "text/html")
    t.Execute(w, sess.Get("countnum"))
}

通過上面的例子可以看到,Session的操作和操作key/value數據庫類似:Set、Get、Delete等操作

因為Session有過期的概念,所以我們定義了GC操作,當訪問過期時間滿足GC的觸發(fā)條件后將會引起GC,但是當我們進行了任意一個session操作,都會對Session實體進行更新,都會觸發(fā)對最后訪問時間的修改,這樣當GC的時候就不會誤刪除還在使用的Session實體。

session重置

我們知道,Web應用中有用戶退出這個操作,那么當用戶退出應用的時候,我們需要對該用戶的session數據進行銷毀操作,上面的代碼已經演示了如何使用session重置操作,下面這個函數就是實現了這個功能:

//Destroy sessionid
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
    cookie, err := r.Cookie(manager.cookieName)
    if err != nil || cookie.Value == "" {
        return
    } else {
        manager.lock.Lock()
        defer manager.lock.Unlock()
        manager.provider.SessionDestroy(cookie.Value)
        expiration := time.Now()
        cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
        http.SetCookie(w, &cookie)
    }
}

session銷毀

我們來看一下Session管理器如何來管理銷毀,只要我們在Main啟動的時候啟動:

func init() {
    go globalSessions.GC()
}

func (manager *Manager) GC() {
    manager.lock.Lock()
    defer manager.lock.Unlock()
    manager.provider.SessionGC(manager.maxlifetime)
    time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() })
}

我們可以看到GC充分利用了time包中的定時器功能,當超時maxLifeTime之后調用GC函數,這樣就可以保證maxLifeTime時間內的session都是可用的,類似的方案也可以用于統計在線用戶數之類的。

總結

至此 我們實現了一個用來在Web應用中全局管理Session的SessionManager,定義了用來提供Session存儲實現Provider的接口,下一小節(jié),我們將會通過接口定義來實現一些Provider,供大家參考學習。

上一篇:11.4 小結下一篇:9.3 避免XSS攻擊