扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
  • Iris框架中文文档

    • 概要
    • 功能列表
    • 安装
    • HTTP 主机配置
    • 配置信息
    • HTTP 路由
    • Context机制
    • 动态路由参数
    • 路由命名
    • 路由中间件
    • 打包Router
    • 错误处理
    • MVC 架构
    • MVC电影项目示例
    • MVC之Websockets
    • MVC中使用会话
    • 单一控制器
    • 视图(模板引擎)
    • 会话Sessions
    • Websocket

扫码订阅《 》或入驻星球,即可阅读文章!

会话Sessions


GOLANG ROADMAP

# Sessions

Iris 提供了一个快速、功能齐全且易于使用的 Sessions 管理器。

Iris 的 Sessions 管理器依赖于它自己的 kataras/iris/sessions (opens new window) 包。

一些微不足道的例子,

  • Overview (opens new window)
  • Standalone (opens new window)
  • Secure Cookie (opens new window)
  • Flash Messages (opens new window)
  • Databases (opens new window)
    • File (opens new window)
    • BoltDB (opens new window)
    • LevelDB (opens new window)
    • Redis (opens new window)

# Overview

import "github.com/kataras/iris/sessions"

sess := sessions.Start(http.ResponseWriter, *http.Request)
sess.
  ID() string
  Get(string) interface{}
  HasFlash() bool
  GetFlash(string) interface{}
  GetFlashString(string) string
  GetString(key string) string
  GetInt(key string) (int, error)
  GetInt64(key string) (int64, error)
  GetFloat32(key string) (float32, error)
  GetFloat64(key string) (float64, error)
  GetBoolean(key string) (bool, error)
  GetAll() map[string]interface{}
  GetFlashes() map[string]interface{}
  VisitAll(cb func(k string, v interface{}))
  Set(string, interface{})
  SetImmutable(key string, value interface{})
  SetFlash(string, interface{})
  Delete(string)
  Clear()
  ClearFlashes()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

这例子将说明如何存储 Session 中的数据。

你不需要使用除了 Iris 以外的第三方库 , 但如果你想使用别的任何库, 请务必要与 Iris 的标准库兼容。如果你想找一个更详细的例子,你可以点击 这里 (opens new window)。

在这个例子中,我们将仅允许通过身份验证的用户在 /secret 中的一定有效期内去查看我们的秘密消息。想要获得访问权限,首先必须去访问 /login 以获取有效的会话 Cookie ,然后将通过验证的用户设置为登录状态。另外,他可以访问 /logout 来撤销对我们秘密信息的访问。

// sessions.go
package main

import (
    "github.com/kataras/iris"

    "github.com/kataras/iris/sessions"
)

var (
    cookieNameForSessionID = "mycookiesessionnameid"
    sess                   = sessions.New(sessions.Config{Cookie: cookieNameForSessionID})
)

func secret(ctx iris.Context) {
    // 检查用户是否已通过身份验证
    if auth, _ := sess.Start(ctx).GetBoolean("authenticated"); !auth {
        ctx.StatusCode(iris.StatusForbidden)
        return
    }

    // 打印秘密消息
    ctx.WriteString("The cake is a lie!")
}

func login(ctx iris.Context) {
    session := sess.Start(ctx)

    // 在此处进行身份验证
    // ...

    // 将用户设置为已验证
    session.Set("authenticated", true)
}

func logout(ctx iris.Context) {
    session := sess.Start(ctx)

    // 撤销用户身份验证
    session.Set("authenticated", false)
}

func main() {
    app := iris.New()

    app.Get("/secret", secret)
    app.Get("/login", login)
    app.Get("/logout", logout)

    app.Run(iris.Addr(":8080"))
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
$ go run sessions.go

$ curl -s http://localhost:8080/secret
Forbidden

$ curl -s -I http://localhost:8080/login
Set-Cookie: mysessionid=MTQ4NzE5Mz...

$ curl -s --cookie "mysessionid=MTQ4NzE5Mz..." http://localhost:8080/secret
The cake is a lie!

1
2
3
4
5
6
7
8
9
10
11

# 后端存储

有时你需要一个后端的存储,即文件存储或者 Redis 内存数据库存储,这可以让你的会话数据在服务器重启后保持不变。

通过调用 .UseDatabase(database) 来注册一个数据库是非常容易的。

让我们查看一个使用快速键值存储的简单示例 bolt db (opens new window) 。

package main

import (
    "time"

    "github.com/kataras/iris"

    "github.com/kataras/iris/sessions"
    "github.com/kataras/iris/sessions/sessiondb/boltdb"
)

func main() {
    db, _ := boltdb.New("./sessions/sessions.db", 0666, "users")
    // 使用不同的协程来同步数据库
    db.Async(true)

    // 按下 control+C/cmd+C 来关闭并解锁数据库
    iris.RegisterOnInterrupt(func() {
        db.Close()
    })

    sess := sessions.New(sessions.Config{
        Cookie:  "sessionscookieid",
        Expires: 45 * time.Minute, // <=0 意味永久的存活
    })

    //
    // 非常重要:
    //
    sess.UseDatabase(db)

    // 剩下的其它代码保持不变。
    app := iris.New()

    app.Get("/", func(ctx iris.Context) {
        ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
    })
    app.Get("/set", func(ctx iris.Context) {
        s := sess.Start(ctx)
        // 设置一个 session 值
        s.Set("name", "iris")

        // 在这测试已设置的 session 值
        ctx.Writef("All ok session setted to: %s", s.GetString("name"))
    })

    app.Get("/set/{key}/{value}", func(ctx iris.Context) {
        key, value := ctx.Params().Get("key"), ctx.Params().Get("value")
        s := sess.Start(ctx)
        // 设置一个 session 值
        s.Set(key, value)

        // 在这测试已设置的 session 值
        ctx.Writef("All ok session setted to: %s", s.GetString(key))
    })

    app.Get("/get", func(ctx iris.Context) {
        // 获取一个特定的键,如字符串,如果没有获取到,则返回一个空字符串
        name := sess.Start(ctx).GetString("name")

        ctx.Writef("The name on the /set was: %s", name)
    })

    app.Get("/get/{key}", func(ctx iris.Context) {
        // 获取一个特定的键,如字符串,如果没有获取到,则返回一个空字符串
        name := sess.Start(ctx).GetString(ctx.Params().Get("key"))

        ctx.Writef("The name on the /set was: %s", name)
    })

    app.Get("/delete", func(ctx iris.Context) {
        // 删除一个特定的键
        sess.Start(ctx).Delete("name")
    })

    app.Get("/clear", func(ctx iris.Context) {
        // 删除所有键值对
        sess.Start(ctx).Clear()
    })

    app.Get("/destroy", func(ctx iris.Context) {
        // destroy方法,删除整个会话数据和 Cookie
        sess.Destroy(ctx)
    })

    app.Get("/update", func(ctx iris.Context) {
        // 更新过期的日期以及新的日期
        sess.ShiftExpiration(ctx)
    })

    app.Run(iris.Addr(":8080"))
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

# 创建一个自定义后端会话存储

你可以通过实现 Database interface 接口来创建你自己的后端存储。

type Database interface {
    Load(sid string) returns struct {
        // 该值包含整个内存存储
        // 此存储包含当前从内存调用更新的会话数据(键和值)
        // 这样,数据库每次访问都可以访问整个会话数据
        Values memstore.Store
        // 在插入时,它包含到期日期时间
        // 在更新时,它包含新的到期日期时间(如果更新了,或者此更旧)
        // 在删除时,此值会为0
        // 在清除时,此值会为0
        // 在销毁时,此值会为0
        Lifetime LifeTime
    }

    Sync(accepts struct {
        // 值包含整个内存存储
        // 此存储包含当前从内存调用更新的会话数据(键和值)
        // 这样,数据库每次都可以访问整个会话数据
        Values memstore.Store
        // 在插入时,它包含到期日期时间
        // 在更新时,它包含新的到期日期时间(如果更新了,或者此更旧)
        // 在删除时,此值会为0
        // 在清除时,此值会为0
        // 在销毁时,此值会为0
        Lifetime LifeTime
    })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

下面是 boltdb 会话数据库的代码

package boltdb

import (
    "bytes"
    "os"
    "path/filepath"
    "runtime"
    "time"

    "github.com/boltdb/bolt"
    "github.com/kataras/golog"
    "github.com/kataras/iris/core/errors"
    "github.com/kataras/iris/sessions"
)

// DefaultFileMode 用作默认数据库的 "fileMode"
// 用于创建会话目录路径, 打开以及写入
// 会话 boltdb (基于文件)  存储
var (
    DefaultFileMode = 0666
)

// 数据库 BoltDB (基于文件) 会话存储
type Database struct {
    table []byte
    // Service 是 BoltDB 数据库连接的基础,
    // 它的初始化在 `New` 或者 `NewFromDB`,
    // 可用于获取统计数据。
    Service *bolt.DB
    async   bool
}

var (
    // 当 path 或 tableName 为空时。`New`  返回 ErrOptionsMissing
    ErrOptionsMissing = errors.New("required options are missing")
)

// New 创建并返回一个新的 BoltDB (基于文件) 存储实例
// 该实例基于 "path" 创建。
//  "path" 应包括文件名和目录(也称为 fullpath), 即 sessions/store.db.
//
// 它将删除任何旧的会话文件
func New(path string, fileMode os.FileMode, bucketName string) (*Database, error) {
    if path == "" || bucketName == "" {
        return nil, ErrOptionsMissing
    }

    if fileMode <= 0 {
        fileMode = os.FileMode(DefaultFileMode)
    }

    // 必要时创建一个目录
    if err := os.MkdirAll(filepath.Dir(path), fileMode); err != nil {
        golog.Errorf("error while trying to create the necessary directories for %s: %v", path, err)
        return nil, err
    }

    service, err := bolt.Open(path, 0600,
        &bolt.Options{Timeout: 15 * time.Second},
    )

    if err != nil {
        golog.Errorf("unable to initialize the BoltDB-based session database: %v", err)
        return nil, err
    }

    return NewFromDB(service, bucketName)
}

// NewFromDB 与 `New` 相同,但接受已经创建的自定义 Boltdb 连接
func NewFromDB(service *bolt.DB, bucketName string) (*Database, error) {
    if bucketName == "" {
        return nil, ErrOptionsMissing
    }
    bucket := []byte(bucketName)

    service.Update(func(tx *bolt.Tx) (err error) {
        _, err = tx.CreateBucketIfNotExists(bucket)
        return
    })

    db := &Database{table: bucket, Service: service}

    runtime.SetFinalizer(db, closeDB)
    return db, db.Cleanup()
}

// 清除将删除任何的失效(已过期)会话条目,
// 它也会在 `New` 上自动调用。
func (db *Database) Cleanup() error {
    err := db.Service.Update(func(tx *bolt.Tx) error {
        b := db.getBucket(tx)
        c := b.Cursor()
        for k, v := c.First(); k != nil; k, v = c.Next() {
            if len(k) == 0 { // 空键, 继续获取下一对
                continue
            }

            storeDB, err := sessions.DecodeRemoteStore(v)
            if err != nil {
                continue
            }

            if storeDB.Lifetime.HasExpired() {
                if err := c.Delete(); err != nil {
                    golog.Warnf("troubles when cleanup a session remote store from BoltDB: %v", err)
                }
            }
        }

        return nil
    })

    return err
}

// Async 如果为 true ,那么它将使用不同的协程
// 去更新 BoltDB (基于文件) 存储。
func (db *Database) Async(useGoRoutines bool) *Database {
    db.async = useGoRoutines
    return db
}

// 加载从 BoltDB (基于文件) 会话存储中加载的会话.
func (db *Database) Load(sid string) (storeDB sessions.RemoteStore) {
    bsid := []byte(sid)
    err := db.Service.View(func(tx *bolt.Tx) (err error) {
        // db.getSessBucket(tx, sid)
        b := db.getBucket(tx)
        c := b.Cursor()
        for k, v := c.First(); k != nil; k, v = c.Next() {
            if len(k) == 0 { // 空键,继续加载下一对
                continue
            }

            if bytes.Equal(k, bsid) { // 会话 Id 应该是键值对的名称
                storeDB, err = sessions.DecodeRemoteStore(v) // 解码整个值,作为远程存储
                break
            }
        }
        return
    })

    if err != nil {
        golog.Errorf("error while trying to load from the remote store: %v", err)
    }

    return
}

// 将数据库和会话(内存)存储同步
func (db *Database) Sync(p sessions.SyncPayload) {
    if db.async {
        go db.sync(p)
    } else {
        db.sync(p)
    }
}

func (db *Database) sync(p sessions.SyncPayload) {
    bsid := []byte(p.SessionID)

    if p.Action == sessions.ActionDestroy {
        if err := db.destroy(bsid); err != nil {
            golog.Errorf("error while destroying a session(%s) from boltdb: %v",
                p.SessionID, err)
        }
        return
    }

    s, err := p.Store.Serialize()
    if err != nil {
        golog.Errorf("error while serializing the remote store: %v", err)
    }

    err = db.Service.Update(func(tx *bolt.Tx) error {
        return db.getBucket(tx).Put(bsid, s)
    })
    if err != nil {
        golog.Errorf("error while writing the session bucket: %v", err)
    }
}

func (db *Database) destroy(bsid []byte) error {
    return db.Service.Update(func(tx *bolt.Tx) error {
        return db.getBucket(tx).Delete(bsid)
    })
}

func (db *Database) getBucket(tx *bolt.Tx) *bolt.Bucket {
    return tx.Bucket(db.table)
}

// Len 报告存储到 BoltDB 表的会话数。
func (db *Database) Len() (num int) {
    db.Service.View(func(tx *bolt.Tx) error {
        // Assume bucket exists and has keys
        b := db.getBucket(tx)
        if b == nil {
            return nil
        }

        b.ForEach(func([]byte, []byte) error {
            num++
            return nil
        })
        return nil
    })
    return
}

// 关闭 BoltDB 连接。
func (db *Database) Close() error {
    return closeDB(db)
}

func closeDB(db *Database) error {
    err := db.Service.Close()
    if err != nil {
        golog.Warnf("closing the BoltDB connection: %v", err)
    }

    return err
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
  • Sessions
  • Overview
  • 后端存储
  • 创建一个自定义后端会话存储