add two new examples and share the app's specific logger instance with sessions databases and APIBuilder

This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-08-16 07:07:36 +03:00
parent ef7d365e81
commit 889b7942d3
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
23 changed files with 346 additions and 89 deletions

View File

@ -60,7 +60,9 @@
* [Multi](routing/subdomains/multi/main.go)
* [Wildcard](routing/subdomains/wildcard/main.go)
* [WWW](routing/subdomains/www/main.go)
* [WWW Method](routing/subdomains/www/www-method/main.go)
* [Redirection](routing/subdomains/redirect/main.go)
* [Multi Instances](routing/subdomains/redirect/multi-instances/main.go)
* [HTTP Errors View](routing/subdomains/http-errors-view/main.go)
* [HTTP Method Override](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go)
* [API Versioning](routing/versioning/main.go)

View File

@ -14,7 +14,7 @@ func newApp() *iris.Application {
if err != nil {
panic(err)
}
app.I18n.Subdomain
// app.I18n.Subdomain = false to disable resolve lang code from subdomain.
// app.I18n.LoadAssets for go-bindata.
// Default values:

View File

@ -17,9 +17,10 @@ func newApp() *iris.Application {
// Optionally, to minify the HTML5 error response.
// Note that minification might be slower, caching is advised.
test.UseError(iris.Minify)
// test.UseError(iris.Minify)
// or pass it to OnErrorCode:
// Register error code 404 handler.
test.OnErrorCode(iris.StatusNotFound, handleNotFoundTestSubdomain)
test.OnErrorCode(iris.StatusNotFound, iris.Minify, handleNotFoundTestSubdomain)
test.Get("/", testIndex)

View File

@ -0,0 +1,82 @@
package main
import (
"net/http"
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
hosts := map[string]*iris.Application{
"mydomain.com": createRoot("www.mydomain.com"), // redirects to www.
"www.mydomain.com": createWWW(),
"test.mydomain.com": createTest(),
}
for _, r := range hosts {
r.Build()
}
app.Downgrade(func(w http.ResponseWriter, r *http.Request) {
host := r.Host
if host == "" {
host = r.URL.Host
}
if router, ok := hosts[host]; ok {
router.ServeHTTP(w, r)
return
}
http.NotFound(w, r)
})
app.Listen(":80")
}
func createRoot(redirectTo string) *iris.Application {
app := iris.New()
app.Downgrade(func(w http.ResponseWriter, r *http.Request) {
fullScheme := "http://"
if r.TLS != nil {
fullScheme = "https://"
}
http.Redirect(w, r, fullScheme+redirectTo, iris.StatusMovedPermanently)
})
return app
}
func createWWW() *iris.Application {
app := iris.New()
app.Get("/", index)
users := app.Party("/users")
users.Get("/", usersIndex)
users.Get("/login", getLogin)
return app
}
func createTest() *iris.Application {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.WriteString("Test Index")
})
return app
}
func index(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com endpoint.")
}
func usersIndex(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com/users endpoint.")
}
func getLogin(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com/users/login endpoint.")
}

View File

@ -1,8 +1,6 @@
package main
import (
"github.com/kataras/iris/v12"
)
import "github.com/kataras/iris/v12"
func newApp() *iris.Application {
app := iris.New()
@ -44,9 +42,9 @@ func newApp() *iris.Application {
ctx.Writef("hi from www.mydomain.com")
})
}
// See also the "subdomains/redirect" to register redirect router wrappers between subdomains,
// See "subdomains/redirect" to register redirect router wrappers between subdomains,
// i.e mydomain.com to www.mydomain.com (like facebook does for SEO reasons(;)).
// And ./www-method example.
return app
}
@ -64,9 +62,7 @@ func main() {
// http://www.mydomain.com/contact
// http://www.mydomain.com/api/users
// http://www.mydomain.com/api/users/42
if err := app.Listen("mydomain.com:80"); err != nil {
panic(err)
}
app.Listen("mydomain.com:80")
}
func info(ctx iris.Context) {

View File

@ -0,0 +1,51 @@
package main
import "github.com/kataras/iris/v12"
func newApp() *iris.Application {
app := iris.New()
// This will create a new "www" subdomain
// and redirect root-level domain requests
// to that one:
www := app.WWW()
www.Get("/", info)
www.Get("/about", info)
www.Get("/contact", info)
www.PartyFunc("/api/users", func(r iris.Party) {
r.Get("/", info)
r.Get("/{id:uint64}", info)
r.Post("/", info)
r.Put("/{id:uint64}", info)
})
return app
}
func main() {
app := newApp()
// http://mydomain.com
// http://mydomain.com/about
// http://imydomain.com/contact
// http://mydomain.com/api/users
// http://mydomain.com/api/users/42
// http://www.mydomain.com
// http://www.mydomain.com/hi
// http://www.mydomain.com/about
// http://www.mydomain.com/contact
// http://www.mydomain.com/api/users
// http://www.mydomain.com/api/users/42
app.Listen("mydomain.com:80")
}
func info(ctx iris.Context) {
method := ctx.Method()
subdomain := ctx.Subdomain()
path := ctx.Path()
ctx.Writef("\nInfo\n\n")
ctx.Writef("Method: %s\nSubdomain: %s\nPath: %s", method, subdomain, path)
}

View File

@ -244,23 +244,6 @@ var (
ctx.Next()
}
// Minify is a middleware which minifies the responses
// based on the response content type.
// Note that minification might be slower, caching is advised.
// Customize the minifier through `Application.Minifier()`.
Minify = func(ctx Context) {
w := ctx.Application().Minifier().ResponseWriter(ctx.ResponseWriter().Naive(), ctx.Request())
// Note(@kataras):
// We don't use defer w.Close()
// because this response writer holds a sync.WaitGroup under the hoods
// and we MUST be sure that its wg.Wait is called on request cancelation
// and not in the end of handlers chain execution
// (which if running a time-consuming task it will delay its resource release).
ctx.OnCloseErr(w.Close)
ctx.ResponseWriter().SetWriter(w)
ctx.Next()
}
// MatchImagesAssets is a simple regex expression
// that can be passed to the DirOptions.Cache.CompressIgnore field
// in order to skip compression on already-compressed file types

View File

@ -4,6 +4,7 @@ import (
stdContext "context"
"io"
"net/http"
"sync"
"github.com/kataras/golog"
"github.com/tdewolff/minify/v2"
@ -84,3 +85,51 @@ type Application interface {
// Order may change.
FindClosestPaths(subdomain, searchPath string, n int) []string
}
var (
registeredApps []Application
mu sync.RWMutex
)
// RegisterApplication registers an application to the global shared storage.
func RegisterApplication(app Application) {
if app == nil {
return
}
mu.Lock()
registeredApps = append(registeredApps, app)
mu.Unlock()
}
// LastApplication returns the last registered Application.
// Handlers has access to the current Application,
// use `Context.Application()` instead.
func LastApplication() Application {
mu.RLock()
if n := len(registeredApps); n > 0 {
if app := registeredApps[n-1]; app != nil {
mu.RUnlock()
return app
}
}
mu.RUnlock()
return nil
}
// DefaultLogger returns a Logger instance for an Iris module.
// If the program contains at least one registered Iris Application
// before this call then it will return a child of that Application's Logger
// otherwise a fresh child of the `golog.Default` will be returned instead.
//
// It should be used when a module has no access to the Application or its Logger.
func DefaultLogger(prefix string) (logger *golog.Logger) {
if app := LastApplication(); app != nil {
logger = app.Logger()
} else {
logger = golog.Default
}
logger = logger.Child(prefix)
return
}

View File

@ -15,6 +15,8 @@ import (
"github.com/kataras/iris/v12/hero"
"github.com/kataras/iris/v12/macro"
macroHandler "github.com/kataras/iris/v12/macro/handler"
"github.com/kataras/golog"
)
// MethodNone is a Virtual method
@ -153,6 +155,8 @@ func overlapRoute(r *Route, next *Route) {
// APIBuilder the visible API for constructing the router
// and child routers.
type APIBuilder struct {
// the application logger.
logger *golog.Logger
// parent is the creator of this Party.
// It is nil on Root.
parent *APIBuilder // currently it's used only on UseRouter feature.
@ -227,8 +231,9 @@ var (
// NewAPIBuilder creates & returns a new builder
// which is responsible to build the API and the router handler.
func NewAPIBuilder() *APIBuilder {
func NewAPIBuilder(logger *golog.Logger) *APIBuilder {
return &APIBuilder{
logger: logger,
parent: nil,
macros: macro.Defaults,
errors: errgroup.New("API Builder"),
@ -240,6 +245,11 @@ func NewAPIBuilder() *APIBuilder {
}
}
// Logger returns the Application Logger.
func (api *APIBuilder) Logger() *golog.Logger {
return api.logger
}
// IsRoot reports whether this Party is the root Application's one.
// It will return false on all children Parties, no exception.
func (api *APIBuilder) IsRoot() bool {

View File

@ -8,6 +8,8 @@ import (
"time"
"github.com/kataras/iris/v12/context"
"github.com/kataras/golog"
)
//
@ -83,7 +85,7 @@ func BenchmarkAPIBuilder(b *testing.B) {
// i.e /gzhyweumidvelqewrvoyqmzopvuxli/{name:string}/bibrkratnrrhvsjwsxygfwmqwhcstc/{age:int}/end
paths := genPaths(routesLength, 15, 42)
api := NewAPIBuilder()
api := NewAPIBuilder(golog.Default)
requestHandler := NewDefaultHandler(nil, nil)
b.ReportAllocs()

View File

@ -246,7 +246,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
// logger.Debugf("API: %d registered %s (", len(registeredRoutes), tr)
// with:
pio.WriteRich(logger.Printer, debugLevel.Title, debugLevel.ColorCode, debugLevel.Style...)
fmt.Fprintf(logger.Printer, " %s API: %d registered %s (", time.Now().Format(logger.TimeFormat), len(registeredRoutes), tr)
fmt.Fprintf(logger.Printer, " %s %sAPI: %d registered %s (", time.Now().Format(logger.TimeFormat), logger.Prefix, len(registeredRoutes), tr)
//
logger.NewLine = bckpNewLine

View File

@ -6,6 +6,8 @@ import (
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/core/errgroup"
"github.com/kataras/iris/v12/macro"
"github.com/kataras/golog"
)
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
@ -13,6 +15,9 @@ import (
//
// Look the `APIBuilder` structure for its implementation.
type Party interface {
// Logger returns the Application Logger.
Logger() *golog.Logger
// IsRoot reports whether this Party is the root Application's one.
// It will return false on all children Parties, no exception.
IsRoot() bool

2
go.mod
View File

@ -20,7 +20,7 @@ require (
github.com/iris-contrib/schema v0.0.2
github.com/json-iterator/go v1.1.10
github.com/kataras/blocks v0.0.2
github.com/kataras/golog v0.1.0
github.com/kataras/golog v0.1.2
github.com/kataras/neffos v0.0.16
github.com/kataras/pio v0.0.10
github.com/kataras/sitemap v0.0.5

54
iris.go
View File

@ -99,20 +99,21 @@ type Application struct {
// New creates and returns a fresh empty iris *Application instance.
func New() *Application {
config := DefaultConfiguration()
app := &Application{
config: &config,
logger: golog.Default,
minifier: newMinifier(),
I18n: i18n.New(),
APIBuilder: router.NewAPIBuilder(),
Router: router.NewRouter(),
config: &config,
Router: router.NewRouter(),
I18n: i18n.New(),
minifier: newMinifier(),
}
logger := newLogger(app)
app.logger = logger
app.APIBuilder = router.NewAPIBuilder(logger)
app.ContextPool = context.New(func() interface{} {
return context.NewContext(app)
})
context.RegisterApplication(app)
return app
}
@ -161,6 +162,8 @@ func (app *Application) WWW() router.Party {
// Example: https://github.com/kataras/iris/tree/master/_examples/routing/subdomains/redirect
func (app *Application) SubdomainRedirect(from, to router.Party) router.Party {
sd := router.NewSubdomainRedirectWrapper(app.ConfigurationReadOnly().GetVHost, from.GetRelPath(), to.GetRelPath())
// TODO: add a debug message here or wait for a response from the issuer
// so we can force these to run on build state (last registered, first executed).
app.Router.WrapRouter(sd)
return to
}
@ -186,6 +189,22 @@ func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly {
return app.config
}
// Maybe, if it's requested:
// func (app *Application) SetName(appName string) *iris.Application {
// app.config.name = appName
// app.logger.SetChildPrefix(appName)
// return app
// }
func newLogger(app *Application) *golog.Logger {
logger := golog.Default.Child(app)
if prefix := os.Getenv("IRIS_APP_NAME"); prefix != "" {
logger.SetChildPrefix(prefix)
}
return logger
}
// Logger returns the golog logger instance(pointer) that is being used inside the "app".
//
// Available levels:
@ -200,7 +219,7 @@ func (app *Application) ConfigurationReadOnly() context.ConfigurationReadOnly {
// Defaults to "info" level.
//
// Callers can use the application's logger which is
// the same `golog.Default` logger,
// the same `golog.Default.LastChild()` logger,
// to print custom logs too.
// Usage:
// app.Logger().Error/Errorf("...")
@ -273,6 +292,25 @@ func newMinifier() *minify.M {
return m
}
// Minify is a middleware which minifies the responses
// based on the response content type.
// Note that minification might be slower, caching is advised.
// Customize the minifier through `Application.Minifier()`.
// Usage:
// app.Use(iris.Minify)
func Minify(ctx Context) {
w := ctx.Application().Minifier().ResponseWriter(ctx.ResponseWriter().Naive(), ctx.Request())
// Note(@kataras):
// We don't use defer w.Close()
// because this response writer holds a sync.WaitGroup under the hoods
// and we MUST be sure that its wg.Wait is called on request cancelation
// and not in the end of handlers chain execution
// (which if running a time-consuming task it will delay its resource release).
ctx.OnCloseErr(w.Close)
ctx.ResponseWriter().SetWriter(w)
ctx.Next()
}
// Minifier returns the minifier instance.
// By default it can minifies:
// - text/html

View File

@ -247,8 +247,8 @@ var _ websocket.ConnHandler = (*Application)(nil)
// It returns a collection of namespace and events that
// were registered through `HandleWebsocket` controllers.
func (app *Application) GetNamespaces() websocket.Namespaces {
if golog.Default.Level == golog.DebugLevel {
websocket.EnableDebug(golog.Default)
if logger := app.Router.Logger(); logger.Level == golog.DebugLevel {
websocket.EnableDebug(logger)
}
return websocket.JoinConnHandlers(app.websocketControllers...).GetNamespaces()

View File

@ -6,6 +6,7 @@ import (
"github.com/kataras/iris/v12/context"
"github.com/google/uuid"
"github.com/kataras/golog"
)
const (
@ -16,6 +17,9 @@ const (
type (
// Config is the configuration for sessions. Please read it before using sessions.
Config struct {
// Logger instance for sessions usage, e.g. { Logger: app.Logger() }.
// Defauls to a child of "sessions" of the latest Iris Application's main Logger.
Logger *golog.Logger
// Cookie string, the session's client cookie name, for example: "mysessionid"
//
// Defaults to "irissessionid".
@ -65,6 +69,10 @@ type (
// Validate corrects missing fields configuration fields and returns the right configuration
func (c Config) Validate() Config {
if c.Logger == nil {
c.Logger = context.DefaultLogger("sessions")
}
if c.Cookie == "" {
c.Cookie = DefaultCookieName
}

View File

@ -6,6 +6,8 @@ import (
"time"
"github.com/kataras/iris/v12/core/memstore"
"github.com/kataras/golog"
)
// ErrNotImplemented is returned when a particular feature is not yet implemented yet.
@ -22,6 +24,8 @@ var ErrNotImplemented = errors.New("not implemented yet")
//
// Look the `sessiondb` folder for databases implementations.
type Database interface {
// SetLogger should inject a logger to this Database.
SetLogger(*golog.Logger)
// 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.
Acquire(sid string, expires time.Duration) LifeTime
@ -36,7 +40,7 @@ type Database interface {
OnUpdateExpiration(sid string, newExpires time.Duration) error
// Set sets a key value of a specific session.
// The "immutable" input argument depends on the store, it may not implement it at all.
Set(sid string, lifetime LifeTime, key string, value interface{}, immutable bool)
Set(sid string, lifetime *LifeTime, key string, value interface{}, immutable bool)
// Get retrieves a session value based on the key.
Get(sid string, key string) interface{}
// Visit loops through all session keys and values.
@ -61,6 +65,8 @@ var _ Database = (*mem)(nil)
func newMemDB() Database { return &mem{values: make(map[string]*memstore.Store)} }
func (s *mem) SetLogger(*golog.Logger) {}
func (s *mem) Acquire(sid string, expires time.Duration) LifeTime {
s.mu.Lock()
s.values[sid] = new(memstore.Store)
@ -72,7 +78,7 @@ func (s *mem) Acquire(sid string, expires time.Duration) LifeTime {
func (s *mem) OnUpdateExpiration(string, time.Duration) error { return nil }
// immutable depends on the store, it may not implement it at all.
func (s *mem) Set(sid string, lifetime LifeTime, key string, value interface{}, immutable bool) {
func (s *mem) Set(sid string, lifetime *LifeTime, key string, value interface{}, immutable bool) {
s.mu.RLock()
s.values[sid].Save(key, value, immutable)
s.mu.RUnlock()

View File

@ -70,7 +70,7 @@ func (p *provider) newSession(man *Sessions, sid string, expires time.Duration)
lifetime.Begin(expires, onExpire)
}
sess.Lifetime = lifetime
sess.Lifetime = &lifetime
return sess
}

View File

@ -22,7 +22,7 @@ type (
mu sync.RWMutex // for flashes.
// Lifetime it contains the expiration data, use it for read-only information.
// See `Sessions.UpdateExpiration` too.
Lifetime LifeTime
Lifetime *LifeTime
// Man is the sessions manager that this session created of.
Man *Sessions

View File

@ -8,6 +8,7 @@ import (
"sync/atomic"
"time"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/sessions"
"github.com/dgraph-io/badger/v2"
@ -26,6 +27,7 @@ type Database struct {
// it's initialized at `New` or `NewFromDB`.
// Can be used to get stats.
Service *badger.DB
logger *golog.Logger
closed uint32 // if 1 is closed.
}
@ -53,11 +55,12 @@ func New(directoryPath string) (*Database, error) {
}
opts := badger.DefaultOptions(directoryPath)
opts.Logger = golog.Default.Child("[sessionsdb.badger]").DisableNewLine()
badgerLogger := context.DefaultLogger("sessionsdb.badger").DisableNewLine()
opts.Logger = badgerLogger
service, err := badger.Open(opts)
if err != nil {
golog.Errorf("unable to initialize the badger-based session database: %v", err)
badgerLogger.Errorf("unable to initialize the badger-based session database: %v\n", err)
return nil, err
}
@ -72,6 +75,12 @@ func NewFromDB(service *badger.DB) *Database {
return db
}
// SetLogger sets the logger once before server ran.
// By default the Iris one is injected.
func (db *Database) SetLogger(logger *golog.Logger) {
db.logger = logger
}
// 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) sessions.LifeTime {
@ -94,7 +103,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime
}
if err != nil {
golog.Error(err)
db.logger.Error(err)
}
return sessions.LifeTime{} // session manager will handle the rest.
@ -118,10 +127,10 @@ func makeKey(sid, key string) []byte {
// 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) {
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.Error(err)
db.logger.Error(err)
return
}
@ -131,7 +140,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu
})
if err != nil {
golog.Error(err)
db.logger.Error(err)
}
}
@ -149,7 +158,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) {
})
if err != nil && err != badger.ErrKeyNotFound {
golog.Error(err)
db.logger.Error(err)
return nil
}
@ -189,7 +198,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
return sessions.DefaultTranscoder.Unmarshal(valueBytes, &value)
})
if err != nil {
golog.Errorf("[sessionsdb.badger.Visit] %v", err)
db.logger.Errorf("[sessionsdb.badger.Visit] %v", err)
continue
}
@ -231,7 +240,7 @@ func (db *Database) Delete(sid string, key string) (deleted bool) {
txn := db.Service.NewTransaction(true)
err := txn.Delete(makeKey(sid, key))
if err != nil {
golog.Error(err)
db.logger.Error(err)
return false
}
return txn.Commit() == nil
@ -250,7 +259,7 @@ func (db *Database) Clear(sid string) {
for iter.Rewind(); iter.ValidForPrefix(prefix); iter.Next() {
key := iter.Item().Key()
if err := txn.Delete(key); err != nil {
golog.Warnf("Database.Clear: %s: %v", key, err)
db.logger.Warnf("Database.Clear: %s: %v", key, err)
continue
}
}
@ -264,10 +273,10 @@ func (db *Database) Release(sid string) {
// and remove the $sid.
txn := db.Service.NewTransaction(true)
if err := txn.Delete([]byte(sid)); err != nil {
golog.Warnf("Database.Release.Delete: %s: %v", sid, err)
db.logger.Warnf("Database.Release.Delete: %s: %v", sid, err)
}
if err := txn.Commit(); err != nil {
golog.Debugf("Database.Release.Commit: %s: %v", sid, err)
db.logger.Debugf("Database.Release.Commit: %s: %v", sid, err)
}
}
@ -282,7 +291,7 @@ func closeDB(db *Database) error {
}
err := db.Service.Close()
if err != nil {
golog.Warnf("closing the badger connection: %v", err)
db.logger.Warnf("closing the badger connection: %v", err)
} else {
atomic.StoreUint32(&db.closed, 1)
}

View File

@ -27,6 +27,7 @@ type Database struct {
// it's initialized at `New` or `NewFromDB`.
// Can be used to get stats.
Service *bolt.DB
logger *golog.Logger
}
var errPathMissing = errors.New("path is required")
@ -91,7 +92,7 @@ func (db *Database) getBucketForSession(tx *bolt.Tx, sid string) *bolt.Bucket {
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)
db.logger.Debugf("unreachable session access for '%s'", sid)
}
return b
@ -122,20 +123,20 @@ func (db *Database) cleanup() error {
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.
db.logger.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)
db.logger.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)
db.logger.Debugf("cleanup: unable to destroy a session '%s'", bsid)
return err
}
@ -149,6 +150,12 @@ func (db *Database) cleanup() error {
})
}
// SetLogger sets the logger once before server ran.
// By default the Iris one is injected.
func (db *Database) SetLogger(logger *golog.Logger) {
db.logger = logger
}
var expirationKey = []byte("exp") // it can be random.
// Acquire receives a session's lifetime from the database,
@ -166,14 +173,14 @@ func (db *Database) Acquire(sid string, expires time.Duration) (lifetime session
// 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)
db.logger.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)
db.logger.Debugf("unable to set an expiration value on session expiration bucket for '%s': %v", sid, err)
return err
}
@ -194,7 +201,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) (lifetime session
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)
db.logger.Debugf("acquire: unable to retrieve expiration value for '%s', value was: '%s': %v", sid, expValue, err)
return
}
@ -207,7 +214,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) (lifetime session
return
})
if err != nil {
golog.Debugf("unable to acquire session '%s': %v", sid, err)
db.logger.Debugf("unable to acquire session '%s': %v", sid, err)
return sessions.LifeTime{}
}
@ -227,7 +234,7 @@ func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) err
root := db.getBucket(tx)
b := root.Bucket(expirationName)
if b == nil {
// golog.Debugf("tried to reset the expiration value for '%s' while its configured lifetime is unlimited or the session is already expired and not found now", sid)
// db.logger.Debugf("tried to reset the expiration value for '%s' while its configured lifetime is unlimited or the session is already expired and not found now", sid)
return sessions.ErrNotFound
}
@ -235,7 +242,7 @@ func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) err
})
if err != nil {
golog.Debugf("unable to reset the expiration value for '%s': %v", sid, err)
db.logger.Debugf("unable to reset the expiration value for '%s': %v", sid, err)
}
return err
@ -247,10 +254,10 @@ func makeKey(key string) []byte {
// 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) {
func (db *Database) Set(sid string, _ *sessions.LifeTime, key string, value interface{}, immutable bool) {
valueBytes, err := sessions.DefaultTranscoder.Marshal(value)
if err != nil {
golog.Debug(err)
db.logger.Debug(err)
return
}
@ -268,7 +275,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu
})
if err != nil {
golog.Debug(err)
db.logger.Debug(err)
}
}
@ -288,7 +295,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) {
return sessions.DefaultTranscoder.Unmarshal(valueBytes, &value)
})
if err != nil {
golog.Debugf("session '%s' key '%s' cannot be retrieved: %v", sid, key, err)
db.logger.Debugf("session '%s' key '%s' cannot be retrieved: %v", sid, key, err)
}
return
@ -305,7 +312,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
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)
db.logger.Debugf("unable to retrieve value of key '%s' of '%s': %v", k, sid, err)
return err
}
@ -315,7 +322,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
})
if err != nil {
golog.Debugf("Database.Visit: %s: %v", sid, err)
db.logger.Debugf("Database.Visit: %s: %v", sid, err)
}
}
@ -332,7 +339,7 @@ func (db *Database) Len(sid string) (n int) {
})
if err != nil {
golog.Debugf("Database.Len: %s: %v", sid, err)
db.logger.Debugf("Database.Len: %s: %v", sid, err)
}
return
@ -366,7 +373,7 @@ func (db *Database) Clear(sid string) {
})
if err != nil {
golog.Debugf("Database.Clear: %s: %v", sid, err)
db.logger.Debugf("Database.Clear: %s: %v", sid, err)
}
}
@ -384,7 +391,7 @@ func (db *Database) Release(sid string) {
})
if err != nil {
golog.Debugf("Database.Release: %s: %v", sid, err)
db.logger.Debugf("Database.Release: %s: %v", sid, err)
}
}
@ -396,7 +403,7 @@ func (db *Database) Close() error {
func closeDB(db *Database) error {
err := db.Service.Close()
if err != nil {
golog.Warnf("closing the BoltDB connection: %v", err)
db.logger.Warnf("closing the BoltDB connection: %v", err)
}
return err

View File

@ -77,7 +77,8 @@ func DefaultConfig() Config {
// Database the redis back-end session database for the sessions.
type Database struct {
c Config
c Config
logger *golog.Logger
}
var _ sessions.Database = (*Database)(nil)
@ -131,6 +132,12 @@ func (db *Database) Config() *Config {
return &db.c // 6 Aug 2019 - keep that for no breaking change.
}
// SetLogger sets the logger once before server ran.
// By default the Iris one is injected.
func (db *Database) SetLogger(logger *golog.Logger) {
db.logger = logger
}
// 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) sessions.LifeTime {
@ -140,7 +147,7 @@ func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime
// fmt.Printf("db.Acquire expires: %s. Seconds: %v\n", expires, expires.Seconds())
// not found, create an entry with ttl and return an empty lifetime, session manager will do its job.
if err := db.c.Driver.Set(key, sid, int64(expires.Seconds())); err != nil {
golog.Debug(err)
db.logger.Debug(err)
}
return sessions.LifeTime{} // session manager will handle the rest.
@ -168,17 +175,17 @@ func (db *Database) makeKey(sid, key string) string {
// 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) {
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.Error(err)
db.logger.Error(err)
return
}
// fmt.Println("database.Set")
// fmt.Printf("lifetime.DurationUntilExpiration(): %s. Seconds: %v\n", lifetime.DurationUntilExpiration(), lifetime.DurationUntilExpiration().Seconds())
if err = db.c.Driver.Set(db.makeKey(sid, key), valueBytes, int64(lifetime.DurationUntilExpiration().Seconds())); err != nil {
golog.Debug(err)
db.logger.Debug(err)
}
}
@ -196,7 +203,7 @@ func (db *Database) get(key string, outPtr interface{}) error {
}
if err = sessions.DefaultTranscoder.Unmarshal(data.([]byte), outPtr); err != nil {
golog.Debugf("unable to unmarshal value of key: '%s': %v", key, err)
db.logger.Debugf("unable to unmarshal value of key: '%s': %v", key, err)
return err
}
@ -206,7 +213,7 @@ func (db *Database) get(key string, outPtr interface{}) error {
func (db *Database) keys(sid string) []string {
keys, err := db.c.Driver.GetKeys(db.makeKey(sid, ""))
if err != nil {
golog.Debugf("unable to get all redis keys of session '%s': %v", sid, err)
db.logger.Debugf("unable to get all redis keys of session '%s': %v", sid, err)
return nil
}
@ -233,7 +240,7 @@ func (db *Database) Len(sid string) (n int) {
func (db *Database) Delete(sid string, key string) (deleted bool) {
err := db.c.Driver.Delete(db.makeKey(sid, key))
if err != nil {
golog.Error(err)
db.logger.Error(err)
}
return err == nil
}
@ -243,7 +250,7 @@ func (db *Database) Clear(sid string) {
keys := db.keys(sid)
for _, key := range keys {
if err := db.c.Driver.Delete(key); err != nil {
golog.Debugf("unable to delete session '%s' value of key: '%s': %v", sid, key, err)
db.logger.Debugf("unable to delete session '%s' value of key: '%s': %v", sid, key, err)
}
}
}
@ -256,7 +263,7 @@ func (db *Database) Release(sid string) {
// and remove the $sid.
err := db.c.Driver.Delete(db.c.Prefix + sid)
if err != nil {
golog.Debugf("Database.Release.Driver.Delete: %s: %v", sid, err)
db.logger.Debugf("Database.Release.Driver.Delete: %s: %v", sid, err)
}
}

View File

@ -50,6 +50,7 @@ func New(cfg Config) *Sessions {
// UseDatabase adds a session database to the manager's provider,
// a session db doesn't have write access
func (s *Sessions) UseDatabase(db Database) {
db.SetLogger(s.config.Logger) // inject the logger.
s.provider.RegisterDatabase(db)
}