Go gorilla/sessions库安装使用

目录
  • 简介
  • 快速使用
  • cookie 存储
  • 记录登录状态
  • 第三方后端存储
  • 总结

简介

上一篇文章《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 存储的本地磁盘路径。后续参数依次指定hashKeyblockKey(可省略),前者用于验证,后者用于加密,我们可以使用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对象,然后读取里面的nameage值。

运行:

$ 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
<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>

登录请求根据方法不同需要执行不同的操作,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 . %}
<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 页面,都会增加计数器,保存在 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

以上就是Go gorilla/sessions库安装使用的详细内容,更多关于Go gorilla/sessions库的资料请关注我们其它相关文章!

(0)

相关推荐

  • Go gorilla securecookie库的安装使用详解

    目录 简介 快速使用 使用 JSON 自定义编解码 Hash/Block 函数 更换 Key 总结 简介 cookie 是用于在 Web 客户端(一般是浏览器)和服务器之间传输少量数据的一种机制.由服务器生成,发送到客户端保存,客户端后续的每次请求都会将 cookie 带上.cookie 现在已经被多多少少地滥用了.很多公司使用 cookie 来收集用户信息.投放广告等. cookie 有两大缺点: 每次请求都需要传输,故不能用来存放大量数据: 安全性较低,通过浏览器工具,很容易看到由网站服务器

  • golang实现文件上传并转存数据库功能

    本文实例为大家分享了golang实现文件上传并转存数据库的具体代码,供大家参考,具体内容如下 需求 上传图片,且可选择将图片保存到数据中. 一.流程图 二.步骤 1.上传文件接口 获取文件,并返回base64string流 代码如下(示例): func setIconPost(c *gin.Context)  {     //获取文件,icon实现对上传文件的访问,header是对上传文件信息的标记     icon,header,err :=c.Request.FormFile("file&q

  • go语言标准库fmt包的一键入门

    目录 ️ 实战场景 Print 系列函数 Fprint 函数 Sprint 函数 Errorf 函数 ️ 格式化占位符 通用部分 整型相关 浮点数与复数 布尔型和指针 ️ 标准输入 fmt.Scan fmt.Scanf fmt.Scanln Fscan 和 Sscan 系列函数 ️ 实战场景 本篇博客为大家带来 Go 语言中的 fmt 包,该包实现了标准输入和输出两大部分内容. 首先对外输出,包含 Print,Fprint,Sprint,Errorof 等内容,其中 Print 在之前的博客中已

  • go日志库logrus的安装及快速使用

    目录 安装简介 快速使用 支持的日志级别 日期 打印调用位置 添加字段 给字段值加引号 设置钩子 设置channel 输出日志 安装简介 Logrus是Go的结构化日志记录器,与标准的日志记录器库完全API兼容. go get安装的logrus库 go get github.com/sirupsen/logrus 快速使用 package main import ( log "github.com/sirupsen/logrus" ) func main() { log.SetLeve

  • Go语言测试库testify使用学习

    目录 简介 准备工作 assert Contains DirExists ElementsMatch Empty EqualError EqualValues Error ErrorAs ErrorIs 逆断言 Assertions 对象 require mock suite 测试 HTTP 服务器 总结 简介 testify可以说是最流行的(从 GitHub star 数来看)Go 语言测试库了.testify提供了很多方便的函数帮助我们做assert和错误信息输出.使用标准库testing,

  • Golang标准库unsafe源码解读

    目录 引言 unsafe包 unsafe构成 type ArbitraryType int type Pointer *ArbitraryType 灵活转换 潜在的危险性 正确的使用姿势 错误的使用姿势 func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr 引言 当你阅读Golang源码时一定遇到过unsafe.Pointe

  • Go gorilla/sessions库安装使用

    目录 简介 快速使用 cookie 存储 记录登录状态 第三方后端存储 总结 简介 上一篇文章<Go 每日一库之 securecookie>中,我们介绍了 cookie.同时提到 cookie 有两个缺点,一是数据不宜过大,二是安全问题.session 是服务器端的存储方案,可以存储大量的数据,而且不需要向客户端传输,从而解决了这两个问题. 但是 session 需要一个能唯一标识用户的 ID,这个 ID 一般存放在 cookie 中发送到客户端保存,随每次请求一起发送到服务器.cookie

  • python anaconda 安装 环境变量 升级 以及特殊库安装的方法

    Anaconda 是一个旗舰版的python安装包, 因为普通的python没有库, 如果需要安装一些重要的库, 要经常一个一个下载,会非常麻烦. 所以这个一个集成的, 可以手动批量升级的软件. 而且库的安装也很全下载速度快. 从官网下载完以后, next 安装好. 配置环境变量, 把安装的文件夹的绝对路径拷贝到 环境变量的path里面. 不配置python都启动不了, 当然,如果之前安装过其他版本的python 可以考虑把之前多余的环境变量路径删掉. 打开anaconda prompt, 输入

  • python psutil库安装教程

    确认本机已安装python环境 查看pip版本 安装psutil 卸载第三方库 总结 以上所述是小编给大家介绍的python psutil库安装教程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的.在此也非常感谢大家对我们网站的支持! 您可能感兴趣的文章: python使用psutil模块获取系统状态 Python psutil模块简单使用实例

  • Python Matplotlib库安装与基本作图示例

    本文实例讲述了Python Matplotlib库安装与基本作图.分享给大家供大家参考,具体如下: 不论是数据挖掘还是数据建模,都免不了数据可视化的问题.对于Python来说,Matplotlib是著名的绘图库,它主要用于二维绘图,简单的三维绘图. 安装Matplotlib 通过pip安装Matplotlib步骤: 在cmd窗口下,进入到pip安装目录,在命令提示符中依次输入 python -m pip install -U pip setuptools python -m pip instal

  • Python Numpy库安装与基本操作示例

    本文实例讲述了Python Numpy库安装与基本操作.分享给大家供大家参考,具体如下: 概述 NumPy(Numeric Python)扩展包提供了数组功能,以及对数据进行快速处理的函数. NumPy 通常与 SciPy(Scientific Python)和 Matplotlib(绘图库)一起使用. 安装 通过pip安装numpy pip install numpy Numpy基本操作 >>> import numpy as np #一般以np作为numpy的别名 >>&

  • Python模块/包/库安装的六种方法及区别

    方法1: 单文件模块 直接把文件拷贝到 $python_dir/Lib 方法2: 多文件模块,文件内有setup.py文件 在官网或者GitHub上下载模块包(压缩文件zip或tar.gz),解压缩之后,文件夹下会有setup.py文件,从命令行窗口进入该文件夹,然后输入命令: python setup.py install 方法3:easy_install 方式 先下载 ez_setup.py,运行 python ez_setup 进行easy_install工具的安装,之后就可以使用easy

  • win10中的dlib库安装过程

    之前试过很多方法结果都失败,最后终于发现一个成功的方法,先记一下以防忘记. 参考:记一次Win10环境python3.7安装dlib模块趟过的坑 由于我是通过Anaconda安装的Python,所以环境与这位博主的有所不同,所以具体情况需要根据自己的环境更改. 接下来直接开始! 1.安装visualStudio,简称VS,我下的版本是最新版的VS2019 下载地址:https://visualstudio.microsoft.com/zh-hans/downloads/ 下的免费版,由于只是用来

  • pycharm第三方库安装失败的问题及解决经验分享

    一.报错信息:[file][Default Settint]---Project Interpreter 点击搜索suds安装模块报错 解决:依据上图提示找到C:\Program Files\JetBrains\PyCharm 2017.2.3\helpers\packaging_tool.py 文件的192行和109行 将do_install函数和do_uninstall函数修改为如下格式 def do_install(pkgs): try: try: from pip._internal i

  • win10下python3.8的PIL库安装

    1.找到Python的位置 我的是在 C:\Users\admin\AppData\Local\Programs\Python\Python38 AppData这个文件是个隐藏文件需要查询得先把隐藏文件显示出来 win10里面在Microsoft store 下载的Python我只找到了exe文件,所以就卸载然后重新下载过. 去官网下载的时候下载速度极其慢,可以去这下: https://python123.io/download 2.PIL库的安装 PIL库:具有强大的图像处理能力 在上面找到的

随机推荐