Gogorilla/sessions库安装使用
来源:脚本之家
时间:2022-12-30 18:43:29 470浏览 收藏
本篇文章给大家分享《Gogorilla/sessions库安装使用》,覆盖了Golang的常见基础知识,其实一个语言的全部知识点一篇文章是不可能说完的,但希望通过这些问题,让读者对自己的掌握程度有一定的认识(B 数),从而弥补自己的不足,更好的掌握它。
简介
上一篇文章《Go 每日一库之 securecookie》中,我们介绍了 cookie。同时提到 cookie 有两个缺点,一是数据不宜过大,二是安全问题。session 是服务器端的存储方案,可以存储大量的数据,而且不需要向客户端传输,从而解决了这两个问题。
但是 session 需要一个能唯一标识用户的 ID,这个 ID 一般存放在 cookie 中发送到客户端保存,随每次请求一起发送到服务器。cookie 和 session 通常配套使用。
gorilla/sessions是 gorilla web 开发工具包中管理 session 的库。它提供了基于 cookie 和本地文件系统的 session。同时预留扩展接口,可以使用其它的后端存储 session 数据。
本文先介绍sessions
提供的两种 session 存储方式,然后通过第三方扩展介绍在多个 Web 服务器实例间如何保持登录状态。
快速使用
本文代码使用 Go Modules。
创建目录并初始化:
$ 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
现在我们实现在服务器端通过 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
路径下挂上设置和读取的处理函数。重点是变量store
。我们调用session.NewFilesystemStore()
方法创建了一个*sessions.FilesystemStore
类型的对象,它会将我们的 session 内容存储到文件系统(即本地磁盘上)。我们需要给NewFilesytemStore()
方法传入至少 2 个参数,第一个参数指定 session 存储的本地磁盘路径。后续参数依次指定hashKey
和blockKey
(可省略),前者用于验证,后者用于加密,我们可以使用securecookie
生成足够随机的 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 }
实现这个接口可以自定义我们存储 session 的位置和格式。
在set
处理函数中,我们调用store.Get(r, "user")
获取名为user
的 session,如果 session 不存在,则创建一个新的。sessions
库支持为同一个用户创建多个 session,store.Get()
方法的第二个参数指定名字。获取到的*Session
结构如下:
type Session struct { ID string Values map[interface{}]interface{} Options *Options IsNew bool store Store name string }
数据直接存放在Session.Values
字段中,这是一个类型为map[interface{}]interface{}
的字段,几乎能保存任何类型的数据(之所以我这里要说几乎,因为还要考虑序列化到存储的限制,有些数据类型无法序列化为字节流保存,如chan
)。
在set
处理函数中,我们直接操作Values
字段,最后我们调用store.Save(r, w, session)
将 session数据保存到对应的存储中。
在get
处理函数中,同样地我们先调用store.Get(r, "user")
获取*Session
对象,然后读取里面的name
和age
值。
运行:
$ go run main.go
首先访问localhost:8080/set
,通过浏览器的开发者工具Application
页签查看 cookie:
我们发现 session 的名字会作为 cookie 名发送到客户端,session ID 被保存为 cookie 的值。
然后我们访问localhost:8080/read
,读取到 session 保存的数据:
另前面说过FilesystemStore
数据是存储在本地硬盘上的,在运行程序的本地目录我们看到有以 session 开头的文件,文件名 session 后面的部分就是 session ID:
cookie 存储
除了默认的将本地文件系统作为存储外,sessions
还支持将 cookie 作为存储,也就是将 session 的数据直接通过 cookie 在客户端和服务器之间传输。cookie 存储的创建方式与文件系统存储的创建方式类似:
var store = sessions.NewCookieStore(securecookie.GenerateRandomKey(32), securecookie.GenerateRandomKey(32))
sessions.NewCookieStore()
方法的第一个参数为 hashKey 用于验证,第二个参数为 blockKey 用于加密,与sessions.NewFilesystemStore()
一样。
其他部分的代码完全不用修改,运行程序的结果与上面的一致。session 数据保存在 cookie 中,随每次请求由客户端传给服务器。这种方式其实就是之前文章中介绍的 cookie 用法。
记录登录状态
之前我们介绍gorilla/mux
时介绍过使用 cookie 保存登录状态。当时将用户名和密码经过简单的 Base64 编码后就直接存放在 cookie 中了,基本处于“裸露”状态。只要有意,很容易就能窃取用户名和密码。现在我们将用户关键信息存储在 session 中,cookie 中只存储一个 session ID。
首先,我们设计 3 个页面,登录页面,主页面,授权才能访问的 secret 页面。登录页面只需要用户名&密码的输入框和登录按钮即可:
// login.tpl
登录请求根据方法不同需要执行不同的操作,GET 方法表示请求登录的页面,POST 方法表示执行登录操作。我们使用handlers.MethodHandler
这个中间件来处理同一个路径的不同方法的请求:
r.Handle("/login", handlers.MethodHandler{ "GET": http.HandlerFunc(Login), "POST": http.HandlerFunc(DoLogin), })
Login
处理函数很简单,只是展示页面:
func Login(w http.ResponseWriter, r *http.Request) { ptTemplate.ExecuteTemplate(w, "login.tpl", nil) }
这里我使用 Go 标准库html/template
模版库来加载和管理各个页面的模板:
var ( ptTemplate *template.Template ) func init() { template.Must(template.New("").ParseGlob("./tpls/*.tpl")) }
DoLogin
处理函数,需要验证登录请求,然后创建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
对象,根据是否有User
对象显示不同的页面:
// home.tpl {% if . %}Hi, {% .Username %}
Goto secret? {% else %}Hi, stranger
Goto login? {% end %}
HomeHandler
代码如下:
func HomeHandler(w http.ResponseWriter, r *http.Request) { u := GetSessionUser(r) ptTemplate.ExecuteTemplate(w, "home.tpl", u) }
最后是 secret 页面:
// secret.tplLorem 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.
You have visited this page {% .Count %} times.
显示访问了该页面多少次。
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 页面,都会增加计数器,保存在 session 中。
上面代码中需要注意一点,由于 session 内容的序列化使用了标准库中的encoding/gob
,所以不支持直接序列化结构体,我封装了两个函数,将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) }
现在运行我们的程序,首先访问localhost:8080
,由于没有登录,显示欢迎陌生人,去登录:
点击去登录,跳转到登录界面,输入用户名和密码:
点击登录,跳转到主页,这时由于记录了登录状态,会显示欢迎 darjun:
点击去隐秘链接:
不停刷新页面,发现访问次数一直累加。
如果未登录时,直接访问localhost:8080/secret
,会直接重定向到登录界面。
上面程序有一个缺点,程序重启启动后,就需要重新登录。因为每次启动我们都重新随机 hashKey 和 blockKey,只需要固定这两个值即可实现重启也能保存登录状态。
登录验证类的功能非常适合放在中间件中处理,之前的文章已经介绍过如何编写中间件了,这里就不赘述了。
第三方后端存储
将 session 存储在本地文件系统,不利于水平扩展。一般稍微上点规模的网站,Web 服务器都会部署很多个实例,请求通过 Nginx 之类的反向代理转发到一个后端实例处理。不能保证后面的请求与之前的请求在同一个实例中处理,故 session 一般需要存储在一个公共的地方,例如 redis。
sessions
提供了扩展接口,方便扩展使用其他的后端存储 session 内容。目前 GitHub 上已经有很多的第三方后端扩展了,详细 list 见sessions
库的 GitHub 首页:
我们只介绍基于 redis 的后端存储,其他的扩展感兴趣可自行研究。首先安装扩展:
$ go get gopkg.in/boj/redistore.v1
创建一个 redistore 的实例:
store, _ = redistore.NewRediStore(10, "tcp", ":6379", "", []byte("redis-key"))
参数依次为:
size
:最大空闲连接数;
network
:连接类型,一般是 TCP;
addr
:网络地址+端口;
password
:redis 的密码,如果未启用,填空;
keyPairs
:依次是 hashKey 和 blockKey(可省略),不再赘述。
为了验证,我们开启多个服务器,所以将端口通过命令行参数传入,使用标准库flag
:
port = flag.Int("port", 8080, "port to listen") func init() { flag.Parse() } func main() { // ... log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) }
为了运行服务器,我们需要先开启一个 redis-server。redis 的安装就不多说了,在 windows 下,建议使用 chocolatey 安装,chocolatey 类似于 Ubutnu 的 apt-get,Mac 的 brew,非常方便,强烈推荐。
为了演示反向代理的效果,即通过一个地址可以随机访问部署的多个 Web 服务器,我们开启 3 个 Web 服务器。终端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
随机转发到mysvr
这个组中的 3 个服务器上,启动 nginx:
$ nginx -c nginx.conf
万事俱备,现在使用浏览器访问localhost
,通过控制台日志发现是 server3 处理了这个请求:
点击去登录,server1 处理了展示页面的请求:
点击登录,server3 处理了 POST 类型的登录请求:
登录成功之后,重定向到主界面的请求又是 server1 处理的:
点击私密链接,展示页面的请求是 server2 处理的:
虽然每次处理的 server 不同,但是登录状态一直保存着。因为我们使用了 redis 保存 session。
注意,我这里每次都是随机一个 server 去处理,你运行的结果不一定一样。
总结
session 为了解决存储用户大量数据和安全性的问题。sessions
库为 Go Web 开发中处理 session 提供了简单,灵活的方法。它依赖较少,可以即插即用,非常方便。
大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue😄
参考
gorilla/sessions GitHub:github.com/gorilla/sessions
Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib
以上就是《Gogorilla/sessions库安装使用》的详细内容,更多关于golang的资料请关注golang学习网公众号!
-
173 收藏
-
240 收藏
-
150 收藏
-
377 收藏
-
343 收藏
-
419 收藏
-
234 收藏
-
155 收藏
-
457 收藏
-
309 收藏
-
225 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 正直的彩虹
- 很好,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢up主分享技术贴!
- 2023-02-14 04:59:27
-
- 和谐的煎蛋
- 这篇技术贴出现的刚刚好,细节满满,很好,码住,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-01-27 21:12:05
-
- 可靠的大米
- 太详细了,码起来,感谢楼主的这篇文章,我会继续支持!
- 2023-01-26 08:31:37
-
- 害怕的蜜粉
- 细节满满,码起来,感谢up主的这篇技术贴,我会继续支持!
- 2023-01-26 00:38:16
-
- 哭泣的小蚂蚁
- 这篇博文太及时了,细节满满,感谢大佬分享,已加入收藏夹了,关注楼主了!希望楼主能多写Golang相关的文章。
- 2023-01-25 10:45:40
-
- 着急的黄豆
- 很棒,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢up主分享技术文章!
- 2023-01-22 10:20:35
-
- 拉长的棉花糖
- 很好,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢楼主分享文章内容!
- 2023-01-18 08:53:54
-
- 自信的小白菜
- 太详细了,已加入收藏夹了,感谢博主的这篇技术贴,我会继续支持!
- 2023-01-17 02:30:24
-
- 拼搏的煎饼
- 感谢大佬分享,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢博主分享技术贴!
- 2023-01-16 15:01:54
-
- 曾经的滑板
- 真优秀,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢老哥分享文章!
- 2023-01-16 04:37:00
-
- 沉默的小鸽子
- 细节满满,已加入收藏夹了,感谢作者的这篇文章内容,我会继续支持!
- 2023-01-15 22:11:18
-
- 帅气的小土豆
- 这篇技术贴真是及时雨啊,很详细,真优秀,mark,关注up主了!希望up主能多写Golang相关的文章。
- 2023-01-13 23:39:48
-
- 敏感的裙子
- 这篇技术贴出现的刚刚好,很详细,感谢大佬分享,已收藏,关注楼主了!希望楼主能多写Golang相关的文章。
- 2023-01-13 11:57:47
-
- 香蕉摩托
- 好细啊,码起来,感谢up主的这篇技术贴,我会继续支持!
- 2023-01-10 10:22:17
-
- 时尚的铅笔
- 这篇技术文章太及时了,细节满满,很有用,已收藏,关注师傅了!希望师傅能多写Golang相关的文章。
- 2023-01-06 13:13:14