溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go?gorilla/sessions庫安裝使用的方法

發(fā)布時間:2022-08-13 10:05:23 來源:億速云 閱讀:150 作者:iii 欄目:開發(fā)技術(shù)

這篇“Go gorilla/sessions庫安裝使用的方法”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Go gorilla/sessions庫安裝使用的方法”文章吧。

簡介

gorilla/sessions是 gorilla web 開發(fā)工具包中管理 session 的庫。它提供了基于 cookie 和本地文件系統(tǒng)的 session。同時預(yù)留擴(kuò)展接口,可以使用其它的后端存儲 session 數(shù)據(jù)。

本文先介紹sessions提供的兩種 session 存儲方式,然后通過第三方擴(kuò)展介紹在多個 Web 服務(wù)器實例間如何保持登錄狀態(tài)。

快速使用

本文代碼使用 Go Modules。

創(chuàng)建目錄并初始化:

$ mkdir gorilla/sessions && cd gorilla/sessions
$ go mod init github.com/darjun/go-daily-lib/gorilla/sessions

安裝gorilla/sessions庫:

$ go get -u github.com/valyala/gorilla/sessions

現(xiàn)在我們實現(xiàn)在服務(wù)器端通過 session 存儲一些信息的功能:

package main
import (
  "fmt"
  "github.com/gorilla/mux"
  "github.com/gorilla/sessions"
  "log"
  "net/http"
  "os"
)
var (
  store = sessions.NewFilesystemStore("./", securecookie.GenerateRandomKey(32), securecookie.GenerateRandomKey(32))
)
func set(w http.ResponseWriter, r *http.Request) {
  session, _ := store.Get(r, "user")
  session.Values["name"] = "dj"
  session.Values["age"] = 18
  err := sessions.Save(r, w)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }
  fmt.Fprintln(w, "Hello World")
}
func read(w http.ResponseWriter, r *http.Request) {
  session, _ := store.Get(r, "user")
  fmt.Fprintf(w, "name:%s age:%d\n", session.Values["name"], session.Values["age"])
}
func main() {
  r := mux.NewRouter()
  r.HandleFunc("/set", set)
  r.HandleFunc("/read", read)
  log.Fatal(http.ListenAndServe(":8080", r))
}

整個程序邏輯比較清晰,分別在/set/read路徑下掛上設(shè)置和讀取的處理函數(shù)。重點是變量store。我們調(diào)用session.NewFilesystemStore()方法創(chuàng)建了一個*sessions.FilesystemStore類型的對象,它會將我們的 session 內(nèi)容存儲到文件系統(tǒng)(即本地磁盤上)。我們需要給NewFilesytemStore()方法傳入至少 2 個參數(shù),第一個參數(shù)指定 session 存儲的本地磁盤路徑。后續(xù)參數(shù)依次指定hashKeyblockKey(可省略),前者用于驗證,后者用于加密,我們可以使用securecookie生成足夠隨機(jī)的 key,詳情見前一篇介紹securecookie的文章。

sessions為所有的 session 存儲抽象了一個接口Store

type Store interface {
  Get(r *http.Request, name string) (*Session, error)
  New(r *http.Request, name string) (*Session, error)
  Save(r *http.Request, w http.ResponseWriter, s *Session) error
}

實現(xiàn)這個接口可以自定義我們存儲 session 的位置和格式。

set處理函數(shù)中,我們調(diào)用store.Get(r, "user")獲取名為user的 session,如果 session 不存在,則創(chuàng)建一個新的。sessions庫支持為同一個用戶創(chuàng)建多個 session,store.Get()方法的第二個參數(shù)指定名字。獲取到的*Session結(jié)構(gòu)如下:

type Session struct {
  ID string
  Values  map[interface{}]interface{}
  Options *Options
  IsNew   bool
  store   Store
  name    string
}

數(shù)據(jù)直接存放在Session.Values字段中,這是一個類型為map[interface{}]interface{}的字段,幾乎能保存任何類型的數(shù)據(jù)(之所以我這里要說幾乎,因為還要考慮序列化到存儲的限制,有些數(shù)據(jù)類型無法序列化為字節(jié)流保存,如chan)。

set處理函數(shù)中,我們直接操作Values字段,最后我們調(diào)用store.Save(r, w, session)將 session數(shù)據(jù)保存到對應(yīng)的存儲中。

get處理函數(shù)中,同樣地我們先調(diào)用store.Get(r, "user")獲取*Session對象,然后讀取里面的nameage值。

運行:

$ go run main.go

首先訪問localhost:8080/set,通過瀏覽器的開發(fā)者工具Application頁簽查看 cookie:

Go?gorilla/sessions庫安裝使用的方法

我們發(fā)現(xiàn) session 的名字會作為 cookie 名發(fā)送到客戶端,session ID 被保存為 cookie 的值。

然后我們訪問localhost:8080/read,讀取到 session 保存的數(shù)據(jù):

Go?gorilla/sessions庫安裝使用的方法

另前面說過FilesystemStore數(shù)據(jù)是存儲在本地硬盤上的,在運行程序的本地目錄我們看到有以 session 開頭的文件,文件名 session 后面的部分就是 session ID:

Go?gorilla/sessions庫安裝使用的方法

cookie 存儲

除了默認(rèn)的將本地文件系統(tǒng)作為存儲外,sessions還支持將 cookie 作為存儲,也就是將 session 的數(shù)據(jù)直接通過 cookie 在客戶端和服務(wù)器之間傳輸。cookie 存儲的創(chuàng)建方式與文件系統(tǒng)存儲的創(chuàng)建方式類似:

var store = sessions.NewCookieStore(securecookie.GenerateRandomKey(32), securecookie.GenerateRandomKey(32))

sessions.NewCookieStore()方法的第一個參數(shù)為 hashKey 用于驗證,第二個參數(shù)為 blockKey 用于加密,與sessions.NewFilesystemStore()一樣。

其他部分的代碼完全不用修改,運行程序的結(jié)果與上面的一致。session 數(shù)據(jù)保存在 cookie 中,隨每次請求由客戶端傳給服務(wù)器。這種方式其實就是之前文章中介紹的 cookie 用法。

記錄登錄狀態(tài)

之前我們介紹gorilla/mux時介紹過使用 cookie 保存登錄狀態(tài)。當(dāng)時將用戶名和密碼經(jīng)過簡單的 Base64 編碼后就直接存放在 cookie 中了,基本處于“裸露”狀態(tài)。只要有意,很容易就能竊取用戶名和密碼。現(xiàn)在我們將用戶關(guān)鍵信息存儲在 session 中,cookie 中只存儲一個 session ID。

首先,我們設(shè)計 3 個頁面,登錄頁面,主頁面,授權(quán)才能訪問的 secret 頁面。登錄頁面只需要用戶名&密碼的輸入框和登錄按鈕即可:

// login.tpl
<form action="/login" method="post">
  <label>Username:</label>
  <input name="username"><br>
  <label>Password:</label>
  <input name="password" type="password"><br>
  <button type="submit">登錄</button>
</form>

登錄請求根據(jù)方法不同需要執(zhí)行不同的操作,GET 方法表示請求登錄的頁面,POST 方法表示執(zhí)行登錄操作。我們使用handlers.MethodHandler這個中間件來處理同一個路徑的不同方法的請求:

r.Handle("/login", handlers.MethodHandler{
  "GET":  http.HandlerFunc(Login),
  "POST": http.HandlerFunc(DoLogin),
})

Login處理函數(shù)很簡單,只是展示頁面:

func Login(w http.ResponseWriter, r *http.Request) {
  ptTemplate.ExecuteTemplate(w, "login.tpl", nil)
}

這里我使用 Go 標(biāo)準(zhǔn)庫html/template模版庫來加載和管理各個頁面的模板:

var (
  ptTemplate *template.Template
)
func init() {
  template.Must(template.New("").ParseGlob("./tpls/*.tpl"))
}

DoLogin處理函數(shù),需要驗證登錄請求,然后創(chuàng)建User對象,保存在 session 中,接著重定向到主頁面:

func DoLogin(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  username := r.Form.Get("username")
  password := r.Form.Get("password")
  if username != "darjun" || password != "handsome" {
    http.Redirect(w, r, "/login", http.StatusFound)
    return
  }
  SaveSessionUser(w, r, &User{Username: username})
  http.Redirect(w, r, "/", http.StatusFound)
}

下面是主頁面的處理,我們可以從 session 中取出保存的User對象,根據(jù)是否有User對象顯示不同的頁面:

// home.tpl
{% if . %}
<p>Hi, {% .Username %}</p><br>
<a href="/secret" rel="external nofollow" >Goto secret?</a>
{% else %}
<p>Hi, stranger</p><br>
<a href="/login" rel="external nofollow" >Goto login?</a>
{% end %}

HomeHandler代碼如下:

func HomeHandler(w http.ResponseWriter, r *http.Request) {
  u := GetSessionUser(r)
  ptTemplate.ExecuteTemplate(w, "home.tpl", u)
}

最后是 secret 頁面:

// secret.tpl
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Inventore a cumque sunt pariatur nihil doloremque tempore,
consectetur ipsum sapiente id excepturi enim velit,
quis nisi esse doloribus aliquid. Incidunt, dolore.
</p>
<p>You have visited this page {% .Count %} times.</p>

顯示訪問了該頁面多少次。

SecretHandler如下:

func SecretHandler(w http.ResponseWriter, r *http.Request) {
  u := GetSessionUser(r)
  if u == nil {
    http.Redirect(w, r, "/login", http.StatusFound)
    return
  }
  u.Count++
  SaveSessionUser(w, r, u)
  ptTemplate.ExecuteTemplate(w, "secret.tpl", u)
}

如果沒有 session,則重定向到登錄頁面。反之顯示該頁面。這里每次成功訪問 secret 頁面,都會增加計數(shù)器,保存在 session 中。

上面代碼中需要注意一點,由于 session 內(nèi)容的序列化使用了標(biāo)準(zhǔn)庫中的encoding/gob,所以不支持直接序列化結(jié)構(gòu)體,我封裝了兩個函數(shù),將User對象序列化為 JSON,然后保存到 session 中和從 session 中取出字符串反序列化為User對象:

func GetSessionUser(r *http.Request) *User {
  session, _ := store.Get(r, "user")
  s, ok := session.Values["user"]
  if !ok {
    return nil
  }
  u := &User{}
  json.Unmarshal([]byte(s.(string)), u)
  return u
}
func SaveSessionUser(w http.ResponseWriter, r *http.Request, u *User) {
  session, _ := store.Get(r, "user")
  data, _ := json.Marshal(u)
  session.Values["user"] = string(data)
  store.Save(r, w, session)
}

現(xiàn)在運行我們的程序,首先訪問localhost:8080,由于沒有登錄,顯示歡迎陌生人,去登錄:

Go?gorilla/sessions庫安裝使用的方法

點擊去登錄,跳轉(zhuǎn)到登錄界面,輸入用戶名和密碼:

Go?gorilla/sessions庫安裝使用的方法

點擊登錄,跳轉(zhuǎn)到主頁,這時由于記錄了登錄狀態(tài),會顯示歡迎 darjun:

Go?gorilla/sessions庫安裝使用的方法

點擊去隱秘鏈接:

Go?gorilla/sessions庫安裝使用的方法

不停刷新頁面,發(fā)現(xiàn)訪問次數(shù)一直累加。

如果未登錄時,直接訪問localhost:8080/secret,會直接重定向到登錄界面。

上面程序有一個缺點,程序重啟啟動后,就需要重新登錄。因為每次啟動我們都重新隨機(jī) hashKey 和 blockKey,只需要固定這兩個值即可實現(xiàn)重啟也能保存登錄狀態(tài)。

登錄驗證類的功能非常適合放在中間件中處理,之前的文章已經(jīng)介紹過如何編寫中間件了,這里就不贅述了。

第三方后端存儲

將 session 存儲在本地文件系統(tǒng),不利于水平擴(kuò)展。一般稍微上點規(guī)模的網(wǎng)站,Web 服務(wù)器都會部署很多個實例,請求通過 Nginx 之類的反向代理轉(zhuǎn)發(fā)到一個后端實例處理。不能保證后面的請求與之前的請求在同一個實例中處理,故 session 一般需要存儲在一個公共的地方,例如 redis

sessions提供了擴(kuò)展接口,方便擴(kuò)展使用其他的后端存儲 session 內(nèi)容。目前 GitHub 上已經(jīng)有很多的第三方后端擴(kuò)展了,詳細(xì) list 見sessions庫的 GitHub 首頁:

Go?gorilla/sessions庫安裝使用的方法

我們只介紹基于 redis 的后端存儲,其他的擴(kuò)展感興趣可自行研究。首先安裝擴(kuò)展:

$ go get gopkg.in/boj/redistore.v1

創(chuàng)建一個 redistore 的實例:

store, _ = redistore.NewRediStore(10, "tcp", ":6379", "", []byte("redis-key"))

參數(shù)依次為:

size:最大空閑連接數(shù);

network:連接類型,一般是 TCP;

addr:網(wǎng)絡(luò)地址+端口;

password:redis 的密碼,如果未啟用,填空;

keyPairs:依次是 hashKey 和 blockKey(可省略),不再贅述。

為了驗證,我們開啟多個服務(wù)器,所以將端口通過命令行參數(shù)傳入,使用標(biāo)準(zhǔn)庫flag

port = flag.Int("port", 8080, "port to listen")
func init() {
  flag.Parse()
}
func main() {
  // ...
  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}

為了運行服務(wù)器,我們需要先開啟一個 redis-server。redis 的安裝就不多說了,在 windows 下,建議使用 chocolatey 安裝,chocolatey 類似于 Ubutnu 的 apt-get,Mac 的 brew,非常方便,強烈推薦。

Go?gorilla/sessions庫安裝使用的方法

為了演示反向代理的效果,即通過一個地址可以隨機(jī)訪問部署的多個 Web 服務(wù)器,我們開啟 3 個 Web 服務(wù)器。終端1:

$ go build 
$ ./redis -port 8080

終端2:

$ ./redis -port 8081

終端3:

$ ./redis -port 8082

可以使用nginx做反向代理,安裝 nginx,配置:

upstream mysvr {
  server localhost:8080;
  server localhost:8081;
  server localhost:8082;
}
server {
  listen       80;
  server_name  localhost;
  location / {
    proxy_pass http://mysvr;
  }
}

這里表示將localhost隨機(jī)轉(zhuǎn)發(fā)到mysvr這個組中的 3 個服務(wù)器上,啟動 nginx:

$ nginx -c nginx.conf

萬事俱備,現(xiàn)在使用瀏覽器訪問localhost,通過控制臺日志發(fā)現(xiàn)是 server3 處理了這個請求:

Go?gorilla/sessions庫安裝使用的方法

點擊去登錄,server1 處理了展示頁面的請求:

Go?gorilla/sessions庫安裝使用的方法

點擊登錄,server3 處理了 POST 類型的登錄請求:

Go?gorilla/sessions庫安裝使用的方法

登錄成功之后,重定向到主界面的請求又是 server1 處理的:

Go?gorilla/sessions庫安裝使用的方法

點擊私密鏈接,展示頁面的請求是 server2 處理的:

Go?gorilla/sessions庫安裝使用的方法

雖然每次處理的 server 不同,但是登錄狀態(tài)一直保存著。因為我們使用了 redis 保存 session。

注意,我這里每次都是隨機(jī)一個 server 去處理,你運行的結(jié)果不一定一樣。

以上就是關(guān)于“Go gorilla/sessions庫安裝使用的方法”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI