mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Update to version 10.6.1 | Re-implement the BoltDB as built'n session database and more. Please read the HISTORY file for further details
Former-commit-id: fa68a914bec5fe4f595bdeaea84ecab6374ba643
This commit is contained in:
parent
cfe484f769
commit
b22151d4b7
|
@ -17,6 +17,12 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
|
||||||
|
|
||||||
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you.
|
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you.
|
||||||
|
|
||||||
|
# We, 25 April 2018 | v10.6.1
|
||||||
|
|
||||||
|
- Re-implement the [BoltDB](https://github.com/coreos/bbolt) as built'n back-end storage for sessions(`sessiondb`) using the latest features: [/sessions/sessiondb/boltdb/database.go](sessions/sessiondb/boltdb/database.go), example can be found at [/_examples/sessions/database/boltdb/main.go](_examples/sessions/database/boltdb/main.go).
|
||||||
|
- Fix a minor issue on [Badger sessiondb example](_examples/sessions/database/badger/main.go). Its `sessions.Config { Expires }` field was `2 *time.Second`, it's `45 *time.Minute` now.
|
||||||
|
- Other minor improvements to the badger sessiondb.
|
||||||
|
|
||||||
# Su, 22 April 2018 | v10.6.0
|
# Su, 22 April 2018 | v10.6.0
|
||||||
|
|
||||||
- Fix open redirect by @wozz via PR: https://github.com/kataras/iris/pull/972.
|
- Fix open redirect by @wozz via PR: https://github.com/kataras/iris/pull/972.
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
|
|
||||||
**Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `go get -u github.com/kataras/iris` ή αφήστε το αυτόματο updater να το κάνει αυτό για σας.
|
**Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `go get -u github.com/kataras/iris` ή αφήστε το αυτόματο updater να το κάνει αυτό για σας.
|
||||||
|
|
||||||
|
# We, 25 April 2018 | v10.6.1
|
||||||
|
|
||||||
|
This history entry is not translated yet to the Greek language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#we-25-april-2018--v1061) instead.
|
||||||
|
|
||||||
# Su, 22 April 2018 | v10.6.0
|
# Su, 22 April 2018 | v10.6.0
|
||||||
|
|
||||||
This history entry is not translated yet to the Greek language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-22-april-2018--v1060) instead.
|
This history entry is not translated yet to the Greek language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-22-april-2018--v1060) instead.
|
||||||
|
|
|
@ -37,6 +37,10 @@
|
||||||
> 请记住,如果您无法升级,那么就不要这样做,我们在此版本中没有任何安全修复程序,但在某些时候建议您最好进行升级,我们总是会添加您喜欢的新功能!
|
> 请记住,如果您无法升级,那么就不要这样做,我们在此版本中没有任何安全修复程序,但在某些时候建议您最好进行升级,我们总是会添加您喜欢的新功能!
|
||||||
|
|
||||||
|
|
||||||
|
# We, 25 April 2018 | v10.6.1
|
||||||
|
|
||||||
|
This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#we-25-april-2018--v1061) instead.
|
||||||
|
|
||||||
# Su, 22 April 2018 | v10.6.0
|
# Su, 22 April 2018 | v10.6.0
|
||||||
|
|
||||||
This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-22-april-2018--v1060) instead.
|
This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-22-april-2018--v1060) instead.
|
||||||
|
|
|
@ -106,7 +106,7 @@ _Updated at: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- [HISTORY](HISTORY.md#sa-24-march-2018--v1050) file is your best friend, it contains information about the latest features and changes
|
- [HISTORY](HISTORY.md#we-25-april-2018--v1061) file is your best friend, it contains information about the latest features and changes
|
||||||
- Did you happen to find a bug? Post it at [github issues](https://github.com/kataras/iris/issues)
|
- Did you happen to find a bug? Post it at [github issues](https://github.com/kataras/iris/issues)
|
||||||
- Do you have any questions or need to speak with someone experienced to solve a problem at real-time? Join us to the [community chat](https://chat.iris-go.com)
|
- Do you have any questions or need to speak with someone experienced to solve a problem at real-time? Join us to the [community chat](https://chat.iris-go.com)
|
||||||
- Complete our form-based user experience report by clicking [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)
|
- Complete our form-based user experience report by clicking [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)
|
||||||
|
|
|
@ -108,7 +108,7 @@ _Η τελευταία ενημέρωση έγινε την [Τρίτη, 21 Νο
|
||||||
|
|
||||||
## Υποστήριξη
|
## Υποστήριξη
|
||||||
|
|
||||||
- To [HISTORY](HISTORY_GR.md#sa-24-march-2018--v1050) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές
|
- To [HISTORY](HISTORY_GR.md#we-25-april-2018--v1061) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές
|
||||||
- Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues)
|
- Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues)
|
||||||
- Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com)
|
- Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com)
|
||||||
- Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)
|
- Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)
|
||||||
|
|
|
@ -106,7 +106,7 @@ _Обновлено: [Вторник, 21 ноября 2017 г.](_benchmarks/READ
|
||||||
|
|
||||||
## Поддержка
|
## Поддержка
|
||||||
|
|
||||||
- Файл [HISTORY](HISTORY.md#sa-24-march-2018--v1050) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях
|
- Файл [HISTORY](HISTORY.md#we-25-april-2018--v1061) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях
|
||||||
- Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues)
|
- Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues)
|
||||||
- У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com)
|
- У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com)
|
||||||
- Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)
|
- Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)
|
||||||
|
|
|
@ -102,7 +102,7 @@ _更新于: [2017年11月21日星期二](_benchmarks/README_UNIX.md)_
|
||||||
|
|
||||||
## 支持
|
## 支持
|
||||||
|
|
||||||
- [更新记录](HISTORY_ZH.md#sa-24-march-2018--v1050) 是您最好的朋友,它包含有关最新功能和更改的信息
|
- [更新记录](HISTORY_ZH.md#we-25-april-2018--v1061) 是您最好的朋友,它包含有关最新功能和更改的信息
|
||||||
- 你碰巧找到了一个错误? 请提交 [github issues](https://github.com/kataras/iris/issues)
|
- 你碰巧找到了一个错误? 请提交 [github issues](https://github.com/kataras/iris/issues)
|
||||||
- 您是否有任何疑问或需要与有经验的人士交谈以实时解决问题? [加入我们的聊天](https://chat.iris-go.com)
|
- 您是否有任何疑问或需要与有经验的人士交谈以实时解决问题? [加入我们的聊天](https://chat.iris-go.com)
|
||||||
- [点击这里完成我们基于表单的用户体验报告](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)
|
- [点击这里完成我们基于表单的用户体验报告](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
10.6.0:https://github.com/kataras/iris/blob/master/HISTORY.md#su-22-april-2018--v1060
|
10.6.1:https://github.com/kataras/iris/blob/master/HISTORY.md#we-25-april-2018--v1061
|
|
@ -24,7 +24,7 @@ func main() {
|
||||||
|
|
||||||
sess := sessions.New(sessions.Config{
|
sess := sessions.New(sessions.Config{
|
||||||
Cookie: "sessionscookieid",
|
Cookie: "sessionscookieid",
|
||||||
Expires: 45 * time.Second, // <=0 means unlimited life. Defaults to 0.
|
Expires: 45 * time.Minute, // <=0 means unlimited life. Defaults to 0.
|
||||||
})
|
})
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
96
_examples/sessions/database/boltdb/main.go
Normal file
96
_examples/sessions/database/boltdb/main.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kataras/iris"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/sessions"
|
||||||
|
"github.com/kataras/iris/sessions/sessiondb/boltdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := boltdb.New("./sessions.db", os.FileMode(0750))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// close and unlobkc the database when control+C/cmd+C pressed
|
||||||
|
iris.RegisterOnInterrupt(func() {
|
||||||
|
db.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
defer db.Close() // close and unlock the database if application errored.
|
||||||
|
|
||||||
|
sess := sessions.New(sessions.Config{
|
||||||
|
Cookie: "sessionscookieid",
|
||||||
|
Expires: 45 * time.Minute, // <=0 means unlimited life. Defaults to 0.
|
||||||
|
})
|
||||||
|
|
||||||
|
//
|
||||||
|
// IMPORTANT:
|
||||||
|
//
|
||||||
|
sess.UseDatabase(db)
|
||||||
|
|
||||||
|
// the rest of the code stays the same.
|
||||||
|
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)
|
||||||
|
//set session values
|
||||||
|
s.Set("name", "iris")
|
||||||
|
|
||||||
|
//test if setted here
|
||||||
|
ctx.Writef("All ok session value of the 'name' is: %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)
|
||||||
|
// set session values
|
||||||
|
s.Set(key, value)
|
||||||
|
|
||||||
|
// test if setted here
|
||||||
|
ctx.Writef("All ok session value of the '%s' is: %s", key, s.GetString(key))
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/get", func(ctx iris.Context) {
|
||||||
|
// get a specific key, as string, if no found returns just an empty string
|
||||||
|
name := sess.Start(ctx).GetString("name")
|
||||||
|
|
||||||
|
ctx.Writef("The 'name' on the /set was: %s", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/get/{key}", func(ctx iris.Context) {
|
||||||
|
// get a specific key, as string, if no found returns just an empty string
|
||||||
|
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) {
|
||||||
|
// delete a specific key
|
||||||
|
sess.Start(ctx).Delete("name")
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/clear", func(ctx iris.Context) {
|
||||||
|
// removes all entries
|
||||||
|
sess.Start(ctx).Clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/destroy", func(ctx iris.Context) {
|
||||||
|
//destroy, removes the entire session data and cookie
|
||||||
|
sess.Destroy(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/update", func(ctx iris.Context) {
|
||||||
|
// updates expire date with a new date
|
||||||
|
sess.ShiftExpiration(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
|
||||||
|
}
|
|
@ -15,13 +15,13 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
// replace with your running redis' server settings:
|
// replace with your running redis' server settings:
|
||||||
db := redis.New(service.Config{
|
db := redis.New(service.Config{
|
||||||
Network: service.DefaultRedisNetwork,
|
Network: "tcp",
|
||||||
Addr: service.DefaultRedisAddr,
|
Addr: "127.0.0.1:6379",
|
||||||
Password: "",
|
Password: "",
|
||||||
Database: "",
|
Database: "",
|
||||||
MaxIdle: 0,
|
MaxIdle: 0,
|
||||||
MaxActive: 0,
|
MaxActive: 0,
|
||||||
IdleTimeout: service.DefaultRedisIdleTimeout,
|
IdleTimeout: time.Duration(5) * time.Minute,
|
||||||
Prefix: ""}) // optionally configure the bridge between your redis server
|
Prefix: ""}) // optionally configure the bridge between your redis server
|
||||||
|
|
||||||
// close connection when control+C/cmd+C
|
// close connection when control+C/cmd+C
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version is the string representation of the current local Iris Web Framework version.
|
// Version is the string representation of the current local Iris Web Framework version.
|
||||||
Version = "10.6.0"
|
Version = "10.6.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckForUpdates checks for any available updates
|
// CheckForUpdates checks for any available updates
|
||||||
|
|
2
doc.go
2
doc.go
|
@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
|
||||||
|
|
||||||
Current Version
|
Current Version
|
||||||
|
|
||||||
10.6.0
|
10.6.1
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package sessions
|
package sessions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -26,13 +27,10 @@ func GetCookie(ctx context.Context, name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Value
|
return c.Value
|
||||||
|
|
||||||
// return ctx.GetCookie(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCookie adds a cookie
|
// AddCookie adds a cookie
|
||||||
func AddCookie(ctx context.Context, cookie *http.Cookie, reclaim bool) {
|
func AddCookie(ctx context.Context, cookie *http.Cookie, reclaim bool) {
|
||||||
// http.SetCookie(ctx.ResponseWriter(), cookie)
|
|
||||||
if reclaim {
|
if reclaim {
|
||||||
ctx.Request().AddCookie(cookie)
|
ctx.Request().AddCookie(cookie)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +63,7 @@ func RemoveCookie(ctx context.Context, config Config) {
|
||||||
// IsValidCookieDomain returns true if the receiver is a valid domain to set
|
// IsValidCookieDomain returns true if the receiver is a valid domain to set
|
||||||
// valid means that is recognised as 'domain' by the browser, so it(the cookie) can be shared with subdomains also
|
// valid means that is recognised as 'domain' by the browser, so it(the cookie) can be shared with subdomains also
|
||||||
func IsValidCookieDomain(domain string) bool {
|
func IsValidCookieDomain(domain string) bool {
|
||||||
if domain == "0.0.0.0" || domain == "127.0.0.1" {
|
if net.IP([]byte(domain)).IsLoopback() {
|
||||||
// for these type of hosts, we can't allow subdomains persistence,
|
// for these type of hosts, we can't allow subdomains persistence,
|
||||||
// the web browser doesn't understand the mysubdomain.0.0.0.0 and mysubdomain.127.0.0.1 mysubdomain.32.196.56.181. as scorrectly ubdomains because of the many dots
|
// the web browser doesn't understand the mysubdomain.0.0.0.0 and mysubdomain.127.0.0.1 mysubdomain.32.196.56.181. as scorrectly ubdomains because of the many dots
|
||||||
// so don't set a cookie domain here, let browser handle this
|
// so don't set a cookie domain here, let browser handle this
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package badger
|
package badger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
|
@ -24,6 +26,8 @@ type Database struct {
|
||||||
// it's initialized at `New` or `NewFromDB`.
|
// it's initialized at `New` or `NewFromDB`.
|
||||||
// Can be used to get stats.
|
// Can be used to get stats.
|
||||||
Service *badger.DB
|
Service *badger.DB
|
||||||
|
|
||||||
|
closed uint32 // if 1 is closed.
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ sessions.Database = (*Database)(nil)
|
var _ sessions.Database = (*Database)(nil)
|
||||||
|
@ -76,7 +80,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime
|
||||||
txn := db.Service.NewTransaction(true)
|
txn := db.Service.NewTransaction(true)
|
||||||
defer txn.Commit(nil)
|
defer txn.Commit(nil)
|
||||||
|
|
||||||
bsid := []byte(sid)
|
bsid := makePrefix(sid)
|
||||||
item, err := txn.Get(bsid)
|
item, err := txn.Get(bsid)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// found, return the expiration.
|
// found, return the expiration.
|
||||||
|
@ -98,10 +102,14 @@ func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime
|
||||||
return sessions.LifeTime{} // session manager will handle the rest.
|
return sessions.LifeTime{} // session manager will handle the rest.
|
||||||
}
|
}
|
||||||
|
|
||||||
var delim = byte('*')
|
var delim = byte('_')
|
||||||
|
|
||||||
|
func makePrefix(sid string) []byte {
|
||||||
|
return append([]byte(sid), delim)
|
||||||
|
}
|
||||||
|
|
||||||
func makeKey(sid, key string) []byte {
|
func makeKey(sid, key string) []byte {
|
||||||
return append([]byte(sid), append([]byte(key), delim)...)
|
return append(makePrefix(sid), []byte(key)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets a key value of a specific session.
|
// Set sets a key value of a specific session.
|
||||||
|
@ -114,8 +122,8 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.Service.Update(func(txn *badger.Txn) error {
|
err = db.Service.Update(func(txn *badger.Txn) error {
|
||||||
return txn.SetWithTTL(makeKey(sid, key), valueBytes, lifetime.DurationUntilExpiration())
|
dur := lifetime.DurationUntilExpiration()
|
||||||
// return txn.Set(makeKey(sid, key), valueBytes)
|
return txn.SetWithTTL(makeKey(sid, key), valueBytes, dur)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -149,7 +157,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) {
|
||||||
|
|
||||||
// Visit loops through all session keys and values.
|
// Visit loops through all session keys and values.
|
||||||
func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
|
func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
|
||||||
prefix := append([]byte(sid), delim)
|
prefix := makePrefix(sid)
|
||||||
|
|
||||||
txn := db.Service.NewTransaction(false)
|
txn := db.Service.NewTransaction(false)
|
||||||
defer txn.Discard()
|
defer txn.Discard()
|
||||||
|
@ -171,7 +179,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(string(item.Key()), value)
|
cb(string(bytes.TrimPrefix(item.Key(), prefix)), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +192,7 @@ var iterOptionsNoValues = badger.IteratorOptions{
|
||||||
|
|
||||||
// Len returns the length of the session's entries (keys).
|
// Len returns the length of the session's entries (keys).
|
||||||
func (db *Database) Len(sid string) (n int) {
|
func (db *Database) Len(sid string) (n int) {
|
||||||
prefix := append([]byte(sid), delim)
|
prefix := makePrefix(sid)
|
||||||
|
|
||||||
txn := db.Service.NewTransaction(false)
|
txn := db.Service.NewTransaction(false)
|
||||||
iter := txn.NewIterator(iterOptionsNoValues)
|
iter := txn.NewIterator(iterOptionsNoValues)
|
||||||
|
@ -211,7 +219,7 @@ func (db *Database) Delete(sid string, key string) (deleted bool) {
|
||||||
|
|
||||||
// Clear removes all session key values but it keeps the session entry.
|
// Clear removes all session key values but it keeps the session entry.
|
||||||
func (db *Database) Clear(sid string) {
|
func (db *Database) Clear(sid string) {
|
||||||
prefix := append([]byte(sid), delim)
|
prefix := makePrefix(sid)
|
||||||
|
|
||||||
txn := db.Service.NewTransaction(true)
|
txn := db.Service.NewTransaction(true)
|
||||||
defer txn.Commit(nil)
|
defer txn.Commit(nil)
|
||||||
|
@ -241,9 +249,14 @@ func (db *Database) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeDB(db *Database) error {
|
func closeDB(db *Database) error {
|
||||||
|
if atomic.LoadUint32(&db.closed) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
err := db.Service.Close()
|
err := db.Service.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
golog.Warnf("closing the badger connection: %v", err)
|
golog.Warnf("closing the badger connection: %v", err)
|
||||||
|
} else {
|
||||||
|
atomic.StoreUint32(&db.closed, 1)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
362
sessions/sessiondb/boltdb/database.go
Normal file
362
sessions/sessiondb/boltdb/database.go
Normal file
|
@ -0,0 +1,362 @@
|
||||||
|
package boltdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/bbolt"
|
||||||
|
"github.com/kataras/golog"
|
||||||
|
"github.com/kataras/iris/core/errors"
|
||||||
|
"github.com/kataras/iris/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultFileMode used as the default database's "fileMode"
|
||||||
|
// for creating the sessions directory path, opening and write
|
||||||
|
// the session boltdb(file-based) storage.
|
||||||
|
var (
|
||||||
|
DefaultFileMode = 0755
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database the BoltDB(file-based) session storage.
|
||||||
|
type Database struct {
|
||||||
|
table []byte
|
||||||
|
// Service is the underline BoltDB database connection,
|
||||||
|
// it's initialized at `New` or `NewFromDB`.
|
||||||
|
// Can be used to get stats.
|
||||||
|
Service *bolt.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
var errPathMissing = errors.New("path is required")
|
||||||
|
|
||||||
|
// New creates and returns a new BoltDB(file-based) storage
|
||||||
|
// instance based on the "path".
|
||||||
|
// Path should include the filename and the directory(aka fullpath), i.e sessions/store.db.
|
||||||
|
//
|
||||||
|
// It will remove any old session files.
|
||||||
|
func New(path string, fileMode os.FileMode) (*Database, error) {
|
||||||
|
if path == "" {
|
||||||
|
golog.Error(errPathMissing)
|
||||||
|
return nil, errPathMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileMode <= 0 {
|
||||||
|
fileMode = os.FileMode(DefaultFileMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create directories if necessary
|
||||||
|
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, fileMode,
|
||||||
|
&bolt.Options{Timeout: 20 * time.Second},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
golog.Errorf("unable to initialize the BoltDB-based session database: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewFromDB(service, "sessions")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFromDB same as `New` but accepts an already-created custom boltdb connection instead.
|
||||||
|
func NewFromDB(service *bolt.DB, bucketName string) (*Database, error) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) getBucket(tx *bolt.Tx) *bolt.Bucket {
|
||||||
|
return tx.Bucket(db.table)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) getBucketForSession(tx *bolt.Tx, sid string) *bolt.Bucket {
|
||||||
|
b := db.getBucket(tx).Bucket([]byte(sid))
|
||||||
|
if b == nil {
|
||||||
|
// session does not exist, it shouldn't happen, session bucket creation happens once at `Acquire`,
|
||||||
|
// no need to accept the `bolt.bucket.CreateBucketIfNotExists`'s performance cost.
|
||||||
|
golog.Debugf("unreachable session access for '%s'", sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
expirationBucketName = []byte("expiration")
|
||||||
|
delim = []byte("_")
|
||||||
|
)
|
||||||
|
|
||||||
|
// expiration lives on its own bucket for each session bucket.
|
||||||
|
func getExpirationBucketName(bsid []byte) []byte {
|
||||||
|
return append(bsid, append(delim, expirationBucketName...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup removes any invalid(have expired) session entries on initialization.
|
||||||
|
func (db *Database) cleanup() error {
|
||||||
|
return db.Service.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := db.getBucket(tx)
|
||||||
|
c := b.Cursor()
|
||||||
|
// loop through all buckets, find one with expiration.
|
||||||
|
for bsid, v := c.First(); bsid != nil; bsid, v = c.Next() {
|
||||||
|
if len(bsid) == 0 { // empty key, continue to the next session bucket.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
expirationName := getExpirationBucketName(bsid)
|
||||||
|
if bExp := b.Bucket(expirationName); bExp != nil { // has expiration.
|
||||||
|
_, expValue := bExp.Cursor().First() // the expiration bucket contains only one key(we don't care, see `Acquire`) value(time.Time) pair.
|
||||||
|
if expValue == nil {
|
||||||
|
golog.Debugf("cleanup: expiration is there but its value is empty '%s'", v) // should never happen.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var expirationTime time.Time
|
||||||
|
if err := sessions.DefaultTranscoder.Unmarshal(expValue, &expirationTime); err != nil {
|
||||||
|
golog.Debugf("cleanup: unable to retrieve expiration value for '%s'", v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if expirationTime.Before(time.Now()) {
|
||||||
|
// expired, delete the expiration bucket.
|
||||||
|
if err := b.DeleteBucket(expirationName); err != nil {
|
||||||
|
golog.Debugf("cleanup: unable to destroy a session '%s'", bsid)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// and the session bucket, if any.
|
||||||
|
return b.DeleteBucket(bsid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var expirationKey = []byte("exp") // it can be random.
|
||||||
|
|
||||||
|
// Acquire receives a session's lifetime from the database,
|
||||||
|
// if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration.
|
||||||
|
func (db *Database) Acquire(sid string, expires time.Duration) (lifetime sessions.LifeTime) {
|
||||||
|
bsid := []byte(sid)
|
||||||
|
err := db.Service.Update(func(tx *bolt.Tx) (err error) {
|
||||||
|
root := db.getBucket(tx)
|
||||||
|
|
||||||
|
if expires > 0 { // should check or create the expiration bucket.
|
||||||
|
name := getExpirationBucketName(bsid)
|
||||||
|
b := root.Bucket(name)
|
||||||
|
if b == nil {
|
||||||
|
// not found, create a session bucket and an expiration bucket and save the given "expires" of time.Time,
|
||||||
|
// don't return a lifetime, let it empty, session manager will do its job.
|
||||||
|
b, err = root.CreateBucket(name)
|
||||||
|
if err != nil {
|
||||||
|
golog.Debugf("unable to create a session bucket for '%s': %v", sid, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
expirationTime := time.Now().Add(expires)
|
||||||
|
timeBytes, err := sessions.DefaultTranscoder.Marshal(expirationTime)
|
||||||
|
if err != nil {
|
||||||
|
golog.Debugf("unable to set an expiration value on session expiration bucket for '%s': %v", sid, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.Put(expirationKey, timeBytes); err == nil {
|
||||||
|
// create the session bucket now, so the rest of the calls can be easly get the bucket without any further checks.
|
||||||
|
_, err = root.CreateBucket(bsid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// found, get the associated expiration bucket, wrap its value and return.
|
||||||
|
_, expValue := b.Cursor().First()
|
||||||
|
if expValue == nil {
|
||||||
|
return nil // does not expire.
|
||||||
|
}
|
||||||
|
|
||||||
|
var expirationTime time.Time
|
||||||
|
if err = sessions.DefaultTranscoder.Unmarshal(expValue, &expirationTime); err != nil {
|
||||||
|
golog.Debugf("acquire: unable to retrieve expiration value for '%s', value was: '%s': %v", sid, expValue, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lifetime = sessions.LifeTime{Time: expirationTime}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// does not expire, just create the session bucket if not exists so we can be ready later on.
|
||||||
|
_, err = root.CreateBucketIfNotExists(bsid)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
golog.Debugf("unable to acquire session '%s': %v", sid, err)
|
||||||
|
return sessions.LifeTime{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeKey(key string) []byte {
|
||||||
|
return []byte(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets a key value of a specific session.
|
||||||
|
// Ignore the "immutable".
|
||||||
|
func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, value interface{}, immutable bool) {
|
||||||
|
valueBytes, err := sessions.DefaultTranscoder.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
golog.Debug(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Service.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := db.getBucketForSession(tx, sid)
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author's notes:
|
||||||
|
// expiration is handlded by the session manager for the whole session, so the `db.Destroy` will be called when and if needed.
|
||||||
|
// Therefore we don't have to implement a TTL here, but we need a `db.Cleanup`, as we did previously, method to delete any expired if server restarted
|
||||||
|
// (badger does not need a `Cleanup` because we set the TTL based on the lifetime.DurationUntilExpiration()).
|
||||||
|
return b.Put(makeKey(key), valueBytes)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
golog.Debug(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a session value based on the key.
|
||||||
|
func (db *Database) Get(sid string, key string) (value interface{}) {
|
||||||
|
err := db.Service.View(func(tx *bolt.Tx) error {
|
||||||
|
b := db.getBucketForSession(tx, sid)
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
valueBytes := b.Get(makeKey(key))
|
||||||
|
if len(valueBytes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessions.DefaultTranscoder.Unmarshal(valueBytes, &value)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
golog.Debugf("session '%s' key '%s' not found", sid, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit loops through all session keys and values.
|
||||||
|
func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
|
||||||
|
db.Service.View(func(tx *bolt.Tx) error {
|
||||||
|
b := db.getBucketForSession(tx, sid)
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.ForEach(func(k []byte, v []byte) error {
|
||||||
|
var value interface{}
|
||||||
|
if err := sessions.DefaultTranscoder.Unmarshal(v, &value); err != nil {
|
||||||
|
golog.Debugf("unable to retrieve value of key '%s' of '%s': %v", k, sid, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(string(k), value)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the length of the session's entries (keys).
|
||||||
|
func (db *Database) Len(sid string) (n int) {
|
||||||
|
db.Service.View(func(tx *bolt.Tx) error {
|
||||||
|
b := db.getBucketForSession(tx, sid)
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n = b.Stats().KeyN
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var errNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
// Delete removes a session key value based on its key.
|
||||||
|
func (db *Database) Delete(sid string, key string) (deleted bool) {
|
||||||
|
err := db.Service.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := db.getBucketForSession(tx, sid)
|
||||||
|
if b == nil {
|
||||||
|
return errNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Delete(makeKey(key))
|
||||||
|
})
|
||||||
|
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all session key values but it keeps the session entry.
|
||||||
|
func (db *Database) Clear(sid string) {
|
||||||
|
db.Service.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := db.getBucketForSession(tx, sid)
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.ForEach(func(k []byte, v []byte) error {
|
||||||
|
return b.Delete(k)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release destroys the session, it clears and removes the session entry,
|
||||||
|
// session manager will create a new session ID on the next request after this call.
|
||||||
|
func (db *Database) Release(sid string) {
|
||||||
|
db.Service.Update(func(tx *bolt.Tx) error {
|
||||||
|
// delete the session bucket.
|
||||||
|
b := db.getBucket(tx)
|
||||||
|
bsid := []byte(sid)
|
||||||
|
if err := b.DeleteBucket(bsid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// and try to delete the associated expiration bucket, if exists, ignore error.
|
||||||
|
b.DeleteBucket(getExpirationBucketName(bsid))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close shutdowns the BoltDB connection.
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user