Update to 8.2.1 | LevelDB-based session database implemented, Read HISTORY.md

Former-commit-id: 4e341b185aedaaa9a04fdebe43b4364dec59e3c8
This commit is contained in:
kataras 2017-08-08 16:00:34 +03:00
parent 92d0b146df
commit 056f9a9415
10 changed files with 420 additions and 7 deletions

View File

@ -18,6 +18,23 @@ 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`.
# Tu, 08 August 2017 | v8.2.1
No API Changes. Great news for the unique iris sessions library, once again.
**NEW**: [LevelDB-based](https://github.com/google/leveldb) session database implemented, example [here](_examples/sessions/database/leveldb/main.go).
[Redis-based sessiondb](sessions/sessiondb/redis) has no longer the `MaxAgeSeconds` config field,
this is passed automatically by the session manager, now.
All [sessions databases](sessions/sessiondb) have an `Async(bool)` function, if turned on
then all synchronization between the memory store and the back-end database will happen
inside different go routines. By-default async is false but it's recommended to turn it on, it will make sessions to be stored faster, at most.
All reported issues have been fixed, the API is simplified by `v8.2.0` so everyone can
create and use any back-end storage for application's sessions persistence.
# Mo, 07 August 2017 | v8.2.0
No Common-API Changes.

View File

@ -20,7 +20,7 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b
### 📑 Table of contents
* [Installation](#-installation)
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-07-august-2017--v820)
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#tu-08-august-2017--v821)
* [Learn](#-learn)
* [HTTP Listening](_examples/#http-listening)
* [Configuration](_examples/#configuration)
@ -318,7 +318,7 @@ Thank You for your trust!
### 📌 Version
Current: **8.2.0**
Current: **8.2.1**
Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever".

View File

@ -12,6 +12,8 @@ import (
func main() {
db, _ := boltdb.New("./sessions/sessions.db", 0666, "users")
// use different go routines to sync the database
db.Async(true)
// close and unlock the database when control+C/cmd+C pressed
iris.RegisterOnInterrupt(func() {
@ -20,7 +22,7 @@ func main() {
sess := sessions.New(sessions.Config{
Cookie: "sessionscookieid",
Expires: 1 * time.Minute, // <=0 means unlimited life
Expires: 45 * time.Minute, // <=0 means unlimited life
})
//

View File

@ -13,6 +13,9 @@ import (
func main() {
db, _ := file.New("./sessions/", 0666)
// use different go routines to sync the database
db.Async(true)
sess := sessions.New(sessions.Config{
Cookie: "sessionscookieid",
Expires: 45 * time.Minute, // <=0 means unlimited life

View File

@ -0,0 +1,94 @@
package main
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/sessions/sessiondb/leveldb"
)
func main() {
db, _ := leveldb.New("./sessions/")
// use different go routines to sync the database
db.Async(true)
// close and unlock the database when control+C/cmd+C pressed
iris.RegisterOnInterrupt(func() {
db.Close()
})
sess := sessions.New(sessions.Config{
Cookie: "sessionscookieid",
Expires: 45 * time.Minute, // <=0 means unlimited life
})
//
// IMPORTANT:
//
sess.UseDatabase(db)
// the rest of the code stays the same.
app := iris.New()
app.Get("/", func(ctx context.Context) {
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
})
app.Get("/set", func(ctx context.Context) {
s := sess.Start(ctx)
//set session values
s.Set("name", "iris")
//test if setted here
ctx.Writef("All ok session setted to: %s", s.GetString("name"))
})
app.Get("/set/{key}/{value}", func(ctx context.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 setted to: %s", s.GetString(key))
})
app.Get("/get", func(ctx context.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 context.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 context.Context) {
// delete a specific key
sess.Start(ctx).Delete("name")
})
app.Get("/clear", func(ctx context.Context) {
// removes all entries
sess.Start(ctx).Clear()
})
app.Get("/destroy", func(ctx context.Context) {
//destroy, removes the entire session data and cookie
sess.Destroy(ctx)
})
app.Get("/update", func(ctx context.Context) {
// updates expire date with a new date
sess.ShiftExpiraton(ctx)
})
app.Run(iris.Addr(":8080"))
}

View File

@ -1,6 +1,8 @@
package main
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
@ -21,12 +23,14 @@ func main() {
IdleTimeout: service.DefaultRedisIdleTimeout,
Prefix: ""}) // optionally configure the bridge between your redis server
// use go routines to query the database
db.Async(true)
// close connection when control+C/cmd+C
iris.RegisterOnInterrupt(func() {
db.Close()
})
sess := sessions.New(sessions.Config{Cookie: "sessionscookieid"})
sess := sessions.New(sessions.Config{Cookie: "sessionscookieid", Expires: 45 * time.Minute})
//
// IMPORTANT:

102
doc.go
View File

@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version
8.2.0
8.2.1
Installation
@ -1267,6 +1267,106 @@ Running the example:
$ curl -s --cookie "mycookiesessionnameid=MTQ4NzE5Mz..." http://localhost:8080/secret
The cake is a lie!
Sessions persistence can be achieved using one (or more) `sessiondb`.
Example Code:
package main
import (
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions"
"github.com/kataras/iris/sessions/sessiondb/boltdb" // <- IMPORTANT
)
func main() {
db, _ := boltdb.New("./sessions/sessions.db", 0666, "users")
// use different go routines to sync the database
db.Async(true)
// close and unlock the database when control+C/cmd+C pressed
iris.RegisterOnInterrupt(func() {
db.Close()
})
sess := sessions.New(sessions.Config{
Cookie: "sessionscookieid",
Expires: 45 * time.Minute, // <=0 means unlimited life
})
//
// IMPORTANT:
//
sess.UseDatabase(db)
// the rest of the code stays the same.
app := iris.New()
app.Get("/", func(ctx context.Context) {
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
})
app.Get("/set", func(ctx context.Context) {
s := sess.Start(ctx)
//set session values
s.Set("name", "iris")
//test if setted here
ctx.Writef("All ok session setted to: %s", s.GetString("name"))
})
app.Get("/set/{key}/{value}", func(ctx context.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 setted to: %s", s.GetString(key))
})
app.Get("/get", func(ctx context.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 context.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 context.Context) {
// delete a specific key
sess.Start(ctx).Delete("name")
})
app.Get("/clear", func(ctx context.Context) {
// removes all entries
sess.Start(ctx).Clear()
})
app.Get("/destroy", func(ctx context.Context) {
//destroy, removes the entire session data and cookie
sess.Destroy(ctx)
})
app.Get("/update", func(ctx context.Context) {
// updates expire date with a new date
sess.ShiftExpiraton(ctx)
})
app.Run(iris.Addr(":8080"))
}
More examples:
https://github.com/kataras/iris/tree/master/sessions

View File

@ -32,7 +32,7 @@ import (
const (
// Version is the current version number of the Iris Web Framework.
Version = "8.2.0"
Version = "8.2.1"
)
// HTTP status codes as registered with IANA.

View File

@ -36,7 +36,10 @@ type Database struct {
async bool
}
// New creates and returns a new file-storage database instance based on the "path".
// New creates and returns a new file-storage database instance based on the "directoryPath".
// DirectoryPath should is the directory which the leveldb database will store the sessions,
// i.e ./sessions/
//
// It will remove any old session files.
func New(directoryPath string, fileMode os.FileMode) (*Database, error) {
lindex := directoryPath[len(directoryPath)-1]

View File

@ -0,0 +1,190 @@
package leveldb
import (
"bytes"
"runtime"
"github.com/kataras/golog"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/sessions"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/opt"
)
var (
// Options used to open the leveldb database, defaults to leveldb's default values.
Options = &opt.Options{}
// WriteOptions used to put and delete, defaults to leveldb's default values.
WriteOptions = &opt.WriteOptions{}
// ReadOptions used to iterate over the database, defaults to leveldb's default values.
ReadOptions = &opt.ReadOptions{}
)
// Database the LevelDB(file-based) session storage.
type Database struct {
// Service is the underline LevelDB database connection,
// it's initialized at `New` or `NewFromDB`.
// Can be used to get stats.
Service *leveldb.DB
async bool
}
// New creates and returns a new LevelDB(file-based) storage
// instance based on the "directoryPath".
// DirectoryPath should is the directory which the leveldb database will store the sessions,
// i.e ./sessions/
//
// It will remove any old session files.
func New(directoryPath string) (*Database, error) {
if directoryPath == "" {
return nil, errors.New("dir is missing")
}
// Second parameter is a "github.com/syndtr/goleveldb/leveldb/opt.Options{}"
// user can change the `Options` or create the sessiondb via `NewFromDB`
// if wants to use a customized leveldb database
// or an existing one, we don't require leveldb options at the constructor.
//
// The leveldb creates the directories, if necessary.
service, err := leveldb.OpenFile(directoryPath, Options)
if err != nil {
golog.Errorf("unable to initialize the LevelDB-based session database: %v", err)
return nil, err
}
return NewFromDB(service)
}
// NewFromDB same as `New` but accepts an already-created custom boltdb connection instead.
func NewFromDB(service *leveldb.DB) (*Database, error) {
if service == nil {
return nil, errors.New("underline database is missing")
}
db := &Database{Service: service}
runtime.SetFinalizer(db, closeDB)
return db, db.Cleanup()
}
// Cleanup removes any invalid(have expired) session entries,
// it's being called automatically on `New` as well.
func (db *Database) Cleanup() error {
iter := db.Service.NewIterator(nil, ReadOptions)
for iter.Next() {
// Remember that the contents of the returned slice should not be modified, and
// only valid until the next call to Next.
k := iter.Key()
if len(k) > 0 {
v := iter.Value()
storeDB, err := sessions.DecodeRemoteStore(v)
if err != nil {
continue
}
if storeDB.Lifetime.HasExpired() {
if err := db.Service.Delete(k, WriteOptions); err != nil {
golog.Warnf("troubles when cleanup a session remote store from LevelDB: %v", err)
}
}
}
}
iter.Release()
return iter.Error()
}
// Async if true passed then it will use different
// go routines to update the LevelDB(file-based) storage.
func (db *Database) Async(useGoRoutines bool) *Database {
db.async = useGoRoutines
return db
}
// Load loads the sessions from the LevelDB(file-based) session storage.
func (db *Database) Load(sid string) (storeDB sessions.RemoteStore) {
bsid := []byte(sid)
iter := db.Service.NewIterator(nil, ReadOptions)
for iter.Next() {
// Remember that the contents of the returned slice should not be modified, and
// only valid until the next call to Next.
k := iter.Key()
if len(k) > 0 {
v := iter.Value()
if bytes.Equal(k, bsid) { // session id should be the name of the key-value pair
store, err := sessions.DecodeRemoteStore(v) // decode the whole value, as a remote store
if err != nil {
golog.Errorf("error while trying to load from the remote store: %v", err)
} else {
storeDB = store
}
break
}
}
}
iter.Release()
if err := iter.Error(); err != nil {
golog.Errorf("error while trying to iterate over the database: %v", err)
}
return
}
// Sync syncs the database with the session's (memory) store.
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.Put(bsid, s, WriteOptions)
if err != nil {
golog.Errorf("error while writing the session(%s) to the database: %v", p.SessionID, err)
}
}
func (db *Database) destroy(bsid []byte) error {
return db.Service.Delete(bsid, WriteOptions)
}
// Close shutdowns the LevelDB 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 LevelDB connection: %v", err)
}
return err
}