# Sessions
Iris 提供了一个快速、功能齐全且易于使用的 Sessions 管理器。
Iris 的 Sessions 管理器依赖于它自己的 kataras/iris/sessions (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
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
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
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
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
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
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