diff --git a/_benchmarks/iris-mvc/main.go b/_benchmarks/iris-mvc/main.go index 4b0e0a18..0399d256 100644 --- a/_benchmarks/iris-mvc/main.go +++ b/_benchmarks/iris-mvc/main.go @@ -1,9 +1,5 @@ package main -/// TODO: remove this on the "master" branch, or even replace it -// with the "iris-mvc" (the new implementatioin is even faster, close to handlers version, -// with bindings or without). - import ( "github.com/kataras/iris/_benchmarks/iris-mvc/controllers" diff --git a/_examples/sessions/database/redis/main.go b/_examples/sessions/database/redis/main.go index 584451a1..d424aa5a 100644 --- a/_examples/sessions/database/redis/main.go +++ b/_examples/sessions/database/redis/main.go @@ -7,22 +7,21 @@ import ( "github.com/kataras/iris/sessions" "github.com/kataras/iris/sessions/sessiondb/redis" - "github.com/kataras/iris/sessions/sessiondb/redis/service" ) // tested with redis version 3.0.503. // for windows see: https://github.com/ServiceStack/redis-windows func main() { // replace with your running redis' server settings: - db := redis.New(service.Config{ - Network: "tcp", - Addr: "127.0.0.1:6379", - Password: "", - Database: "", - MaxIdle: 0, - MaxActive: 0, - IdleTimeout: time.Duration(5) * time.Minute, - Prefix: ""}) // optionally configure the bridge between your redis server + db := redis.New(redis.Config{ + Network: "tcp", + Addr: "127.0.0.1:6379", + Timeout: time.Duration(30) * time.Second, + MaxActive: 10, + Password: "", + Database: "", + Prefix: "", + }) // optionally configure the bridge between your redis server. // close connection when control+C/cmd+C iris.RegisterOnInterrupt(func() { @@ -33,10 +32,9 @@ func main() { sess := sessions.New(sessions.Config{ Cookie: "sessionscookieid", - Expires: 45 * time.Minute, // <=0 means unlimited life. Defaults to 0. + Expires: 0, // defauls to 0: unlimited life. Another good value is: 45 * time.Minute, AllowReclaim: true, - }, - ) + }) // // IMPORTANT: @@ -45,6 +43,7 @@ func main() { // the rest of the code stays the same. app := iris.New() + // app.Logger().SetLevel("debug") app.Get("/", func(ctx iris.Context) { ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead") diff --git a/sessions/sessiondb/redis/database.go b/sessions/sessiondb/redis/database.go index 8665bea3..fd16fa05 100644 --- a/sessions/sessiondb/redis/database.go +++ b/sessions/sessiondb/redis/database.go @@ -1,36 +1,39 @@ package redis import ( - "runtime" "time" - "github.com/kataras/golog" "github.com/kataras/iris/sessions" - "github.com/kataras/iris/sessions/sessiondb/redis/service" + + "github.com/kataras/golog" ) // Database the redis back-end session database for the sessions. type Database struct { - redis *service.Service + redis *Service } var _ sessions.Database = (*Database)(nil) // New returns a new redis database. -func New(cfg ...service.Config) *Database { - db := &Database{redis: service.New(cfg...)} - db.redis.Connect() +func New(cfg ...Config) *Database { + service := newService(cfg...) + if err := service.Connect(); err != nil { + panic(err) + } + + db := &Database{redis: service} _, err := db.redis.PingPong() if err != nil { golog.Debugf("error connecting to redis: %v", err) return nil } - runtime.SetFinalizer(db, closeDB) + // runtime.SetFinalizer(db, closeDB) return db } // Config returns the configuration for the redis server bridge, you can change them. -func (db *Database) Config() *service.Config { +func (db *Database) Config() *Config { return db.redis.Config } @@ -39,6 +42,7 @@ func (db *Database) Config() *service.Config { func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime { seconds, hasExpiration, found := db.redis.TTL(sid) if !found { + // 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.redis.Set(sid, sid, int64(expires.Seconds())); err != nil { golog.Debug(err) @@ -74,6 +78,8 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu return } + // fmt.Println("database.Set") + // fmt.Printf("lifetime.DurationUntilExpiration(): %s. Seconds: %v\n", lifetime.DurationUntilExpiration(), lifetime.DurationUntilExpiration().Seconds()) if err = db.redis.Set(db.makeKey(sid, key), valueBytes, int64(lifetime.DurationUntilExpiration().Seconds())); err != nil { golog.Debug(err) } @@ -98,7 +104,7 @@ func (db *Database) get(key string, outPtr interface{}) { } func (db *Database) keys(sid string) []string { - keys, err := db.redis.GetKeys(sid + db.redis.Config.Delim) + keys, err := db.redis.GetKeys(sid) if err != nil { golog.Debugf("unable to get all redis keys of session '%s': %v", sid, err) return nil diff --git a/sessions/sessiondb/redis/service.go b/sessions/sessiondb/redis/service.go new file mode 100644 index 00000000..75bf377e --- /dev/null +++ b/sessions/sessiondb/redis/service.go @@ -0,0 +1,359 @@ +package redis + +import ( + "fmt" + "strconv" + "time" + + "github.com/kataras/iris/core/errors" + + "github.com/mediocregopher/radix/v3" +) + +const ( + // DefaultRedisNetwork the redis network option, "tcp". + DefaultRedisNetwork = "tcp" + // DefaultRedisAddr the redis address option, "127.0.0.1:6379". + DefaultRedisAddr = "127.0.0.1:6379" + // DefaultRedisTimeout the redis idle timeout option, time.Duration(30) * time.Second + DefaultRedisTimeout = time.Duration(30) * time.Second + // DefaultDelim ths redis delim option, "-". + DefaultDelim = "-" +) + +// Config the redis configuration used inside sessions +type Config struct { + // Network protocol. Defaults to "tcp". + Network string + // Addr of the redis server. Defaults to "127.0.0.1:6379". + Addr string + // Password string .If no password then no 'AUTH'. Defaults to "". + Password string + // If Database is empty "" then no 'SELECT'. Defaults to "". + Database string + // MaxActive. Defaults to 10. + MaxActive int + // Timeout for connect, write and read, defautls to 30 seconds, 0 means no timeout. + Timeout time.Duration + // Prefix "myprefix-for-this-website". Defaults to "". + Prefix string + // Delim the delimeter for the keys on the sessiondb. Defaults to "-". + Delim string +} + +// DefaultConfig returns the default configuration for Redis service. +func DefaultConfig() Config { + return Config{ + Network: DefaultRedisNetwork, + Addr: DefaultRedisAddr, + Password: "", + Database: "", + MaxActive: 10, + Timeout: DefaultRedisTimeout, + Prefix: "", + Delim: DefaultDelim, + } +} + +var ( + // ErrRedisClosed an error with message 'Redis is already closed' + ErrRedisClosed = errors.New("Redis is already closed") + // ErrKeyNotFound an error with message 'Key $thekey doesn't found' + ErrKeyNotFound = errors.New("Key '%s' doesn't found") +) + +// Service the Redis service, contains the config and the redis pool +type Service struct { + // Connected is true when the Service has already connected + Connected bool + // Config the redis config for this redis + Config *Config + pool *radix.Pool +} + +// newService returns a Redis service filled by the passed config +// to connect call the .Connect(). +func newService(cfg ...Config) *Service { + c := DefaultConfig() + if len(cfg) > 0 { + c = cfg[0] + } + + r := &Service{Config: &c} + return r +} + +// Connect connects to the redis, called only once +func (r *Service) Connect() error { + c := r.Config + + if c.Timeout < 0 { + c.Timeout = DefaultRedisTimeout + } + + if c.Network == "" { + c.Network = DefaultRedisNetwork + } + + if c.Addr == "" { + c.Addr = DefaultRedisAddr + } + + if c.MaxActive == 0 { + c.MaxActive = 10 + } + + if c.Delim == "" { + c.Delim = DefaultDelim + } + + customConnFunc := func(network, addr string) (radix.Conn, error) { + var options []radix.DialOpt + + if c.Password != "" { + options = append(options, radix.DialAuthPass(c.Password)) + } + + if c.Timeout > 0 { + options = append(options, radix.DialTimeout(c.Timeout)) + } + + if c.Database != "" { // *dialOpts.selectDb is not exported on the 3rd-party library, + // but on its `DialSelectDB` option it does this: + // do.selectDB = strconv.Itoa(db) -> (string to int) + // so we can pass that string as int and it should work. + dbIndex, err := strconv.Atoi(c.Database) + if err == nil { + options = append(options, radix.DialSelectDB(dbIndex)) + } + + } + + return radix.Dial(network, addr, options...) + } + + pool, err := radix.NewPool(c.Network, c.Addr, c.MaxActive, radix.PoolConnFunc(customConnFunc)) + if err != nil { + return err + } + + r.Connected = true + r.pool = pool + return nil +} + +// PingPong sends a ping and receives a pong, if no pong received then returns false and filled error +func (r *Service) PingPong() (bool, error) { + var msg string + err := r.pool.Do(radix.Cmd(&msg, "PING")) + if err != nil { + return false, err + } + return (msg == "PONG"), nil +} + +// CloseConnection closes the redis connection +func (r *Service) CloseConnection() error { + if r.pool != nil { + return r.pool.Close() + } + return ErrRedisClosed +} + +// Set sets a key-value to the redis store. +// The expiration is setted by the MaxAgeSeconds. +func (r *Service) Set(key string, value interface{}, secondsLifetime int64) error { + // fmt.Printf("%#+v. %T. %s\n", value, value, value) + + // if vB, ok := value.([]byte); ok && secondsLifetime <= 0 { + // return r.pool.Do(radix.Cmd(nil, "MSET", r.Config.Prefix+key, string(vB))) + // } + + var cmd radix.CmdAction + // if has expiration, then use the "EX" to delete the key automatically. + if secondsLifetime > 0 { + cmd = radix.FlatCmd(nil, "SETEX", r.Config.Prefix+key, secondsLifetime, value) + } else { + cmd = radix.FlatCmd(nil, "SET", r.Config.Prefix+key, value) // MSET same performance... + } + + return r.pool.Do(cmd) +} + +// Get returns value, err by its key +//returns nil and a filled error if something bad happened. +func (r *Service) Get(key string) (interface{}, error) { + var redisVal interface{} + mn := radix.MaybeNil{Rcv: &redisVal} + + err := r.pool.Do(radix.Cmd(&mn, "GET", r.Config.Prefix+key)) + + if err != nil { + return nil, err + } + if mn.Nil { + return nil, ErrKeyNotFound.Format(key) + } + return redisVal, nil +} + +// TTL returns the seconds to expire, if the key has expiration and error if action failed. +// Read more at: https://redis.io/commands/ttl +func (r *Service) TTL(key string) (seconds int64, hasExpiration bool, found bool) { + var redisVal interface{} + err := r.pool.Do(radix.Cmd(&redisVal, "TTL", r.Config.Prefix+key)) + if err != nil { + return -2, false, false + } + seconds = redisVal.(int64) + // if -1 means the key has unlimited life time. + hasExpiration = seconds > -1 + // if -2 means key does not exist. + found = seconds != -2 + return +} + +func (r *Service) updateTTLConn(key string, newSecondsLifeTime int64) error { + var reply int + err := r.pool.Do(radix.FlatCmd(&reply, "EXPIRE", r.Config.Prefix+key, newSecondsLifeTime)) + if err != nil { + return err + } + + // https://redis.io/commands/expire#return-value + // + // 1 if the timeout was set. + // 0 if key does not exist. + + if reply == 1 { + return nil + } else if reply == 0 { + return fmt.Errorf("unable to update expiration, the key '%s' was stored without ttl", key) + } // do not check for -1. + + return nil +} + +// UpdateTTL will update the ttl of a key. +// Using the "EXPIRE" command. +// Read more at: https://redis.io/commands/expire#refreshing-expires +func (r *Service) UpdateTTL(key string, newSecondsLifeTime int64) error { + return r.updateTTLConn(key, newSecondsLifeTime) +} + +// UpdateTTLMany like `UpdateTTL` but for all keys starting with that "prefix", +// it is a bit faster operation if you need to update all sessions keys (although it can be even faster if we used hash but this will limit other features), +// look the `sessions/Database#OnUpdateExpiration` for example. +func (r *Service) UpdateTTLMany(prefix string, newSecondsLifeTime int64) error { + keys, err := r.getKeys(prefix) + if err != nil { + return err + } + + for _, key := range keys { + if err = r.updateTTLConn(key, newSecondsLifeTime); err != nil { // fail on first error. + return err + } + } + + return err +} + +// GetAll returns all redis entries using the "SCAN" command (2.8+). +func (r *Service) GetAll() (interface{}, error) { + var redisVal []interface{} + mn := radix.MaybeNil{Rcv: &redisVal} + err := r.pool.Do(radix.Cmd(&mn, "SCAN", strconv.Itoa(0))) // 0 -> cursor + + if err != nil { + return nil, err + } + + if mn.Nil { + return nil, err + } + + return redisVal, nil +} + +func (r *Service) getKeys(prefix string) ([]string, error) { + var keys []string + // err := r.pool.Do(radix.Cmd(&keys, "MATCH", r.Config.Prefix+prefix+"*")) + // if err != nil { + // return nil, err + // } + + scanner := radix.NewScanner(r.pool, radix.ScanOpts{ + Command: "SCAN", + Pattern: r.Config.Prefix + prefix + r.Config.Delim + "*", // get all of this session except its root sid. + // Count: 9999999999, + }) + + var key string + for scanner.Next(&key) { + keys = append(keys, key) + } + if err := scanner.Close(); err != nil { + return nil, err + } + + // if err := c.Send("SCAN", 0, "MATCH", r.Config.Prefix+prefix+"*", "COUNT", 9999999999); err != nil { + // return nil, err + // } + + // if err := c.Flush(); err != nil { + // return nil, err + // } + + // reply, err := c.Receive() + // if err != nil || reply == nil { + // return nil, err + // } + + // it returns []interface, with two entries, the first one is "0" and the second one is a slice of the keys as []interface{uint8....}. + + // if keysInterface, ok := reply.([]interface{}); ok { + // if len(keysInterface) == 2 { + // // take the second, it must contain the slice of keys. + // if keysSliceAsBytes, ok := keysInterface[1].([]interface{}); ok { + // keys := make([]string, len(keysSliceAsBytes), len(keysSliceAsBytes)) + // for i, k := range keysSliceAsBytes { + // keys[i] = fmt.Sprintf("%s", k)[len(r.Config.Prefix):] + // } + + // return keys, nil + // } + // } + // } + + return keys, nil +} + +// GetKeys returns all redis keys using the "SCAN" with MATCH command. +// Read more at: https://redis.io/commands/scan#the-match-option. +func (r *Service) GetKeys(prefix string) ([]string, error) { + return r.getKeys(prefix) +} + +// GetBytes returns value, err by its key +// you can use utils.Deserialize((.GetBytes("yourkey"),&theobject{}) +//returns nil and a filled error if something wrong happens +func (r *Service) GetBytes(key string) ([]byte, error) { + var redisVal []byte + mn := radix.MaybeNil{Rcv: &redisVal} + err := r.pool.Do(radix.Cmd(&mn, "GET", r.Config.Prefix+key)) + if err != nil { + return nil, err + } + if mn.Nil { + return nil, ErrKeyNotFound.Format(key) + } + + return redisVal, nil +} + +// Delete removes redis entry by specific key +func (r *Service) Delete(key string) error { + err := r.pool.Do(radix.Cmd(nil, "DEL", r.Config.Prefix+key)) + return err +} diff --git a/sessions/sessiondb/redis/service/config.go b/sessions/sessiondb/redis/service/config.go deleted file mode 100644 index e635fcbd..00000000 --- a/sessions/sessiondb/redis/service/config.go +++ /dev/null @@ -1,53 +0,0 @@ -package service - -import ( - "time" -) - -const ( - // DefaultRedisNetwork the redis network option, "tcp". - DefaultRedisNetwork = "tcp" - // DefaultRedisAddr the redis address option, "127.0.0.1:6379". - DefaultRedisAddr = "127.0.0.1:6379" - // DefaultRedisIdleTimeout the redis idle timeout option, time.Duration(5) * time.Minute. - DefaultRedisIdleTimeout = time.Duration(5) * time.Minute - // DefaultDelim ths redis delim option, "-". - DefaultDelim = "-" -) - -// Config the redis configuration used inside sessions -type Config struct { - // Network protocol. Defaults to "tcp". - Network string - // Addr of the redis server. Defaults to "127.0.0.1:6379". - Addr string - // Password string .If no password then no 'AUTH'. Defaults to "". - Password string - // If Database is empty "" then no 'SELECT'. Defaults to "". - Database string - // MaxIdle 0 no limit. - MaxIdle int - // MaxActive 0 no limit. - MaxActive int - // IdleTimeout time.Duration(5) * time.Minute. - IdleTimeout time.Duration - // Prefix "myprefix-for-this-website". Defaults to "". - Prefix string - // Delim the delimeter for the values. Defaults to "-". - Delim string -} - -// DefaultConfig returns the default configuration for Redis service. -func DefaultConfig() Config { - return Config{ - Network: DefaultRedisNetwork, - Addr: DefaultRedisAddr, - Password: "", - Database: "", - MaxIdle: 0, - MaxActive: 0, - IdleTimeout: DefaultRedisIdleTimeout, - Prefix: "", - Delim: DefaultDelim, - } -} diff --git a/sessions/sessiondb/redis/service/service.go b/sessions/sessiondb/redis/service/service.go deleted file mode 100644 index c38a5e39..00000000 --- a/sessions/sessiondb/redis/service/service.go +++ /dev/null @@ -1,331 +0,0 @@ -package service - -import ( - "fmt" - "time" - - "github.com/gomodule/redigo/redis" - "github.com/kataras/iris/core/errors" -) - -var ( - // ErrRedisClosed an error with message 'Redis is already closed' - ErrRedisClosed = errors.New("Redis is already closed") - // ErrKeyNotFound an error with message 'Key $thekey doesn't found' - ErrKeyNotFound = errors.New("Key '%s' doesn't found") -) - -// Service the Redis service, contains the config and the redis pool -type Service struct { - // Connected is true when the Service has already connected - Connected bool - // Config the redis config for this redis - Config *Config - pool *redis.Pool -} - -// PingPong sends a ping and receives a pong, if no pong received then returns false and filled error -func (r *Service) PingPong() (bool, error) { - c := r.pool.Get() - defer c.Close() - msg, err := c.Do("PING") - if err != nil || msg == nil { - return false, err - } - return (msg == "PONG"), nil -} - -// CloseConnection closes the redis connection -func (r *Service) CloseConnection() error { - if r.pool != nil { - return r.pool.Close() - } - return ErrRedisClosed -} - -// Set sets a key-value to the redis store. -// The expiration is setted by the MaxAgeSeconds. -func (r *Service) Set(key string, value interface{}, secondsLifetime int64) (err error) { - c := r.pool.Get() - defer c.Close() - if c.Err() != nil { - return c.Err() - } - - // if has expiration, then use the "EX" to delete the key automatically. - if secondsLifetime > 0 { - _, err = c.Do("SETEX", r.Config.Prefix+key, secondsLifetime, value) - } else { - _, err = c.Do("SET", r.Config.Prefix+key, value) - } - - return -} - -// Get returns value, err by its key -//returns nil and a filled error if something bad happened. -func (r *Service) Get(key string) (interface{}, error) { - c := r.pool.Get() - defer c.Close() - if err := c.Err(); err != nil { - return nil, err - } - - redisVal, err := c.Do("GET", r.Config.Prefix+key) - - if err != nil { - return nil, err - } - if redisVal == nil { - return nil, ErrKeyNotFound.Format(key) - } - return redisVal, nil -} - -// TTL returns the seconds to expire, if the key has expiration and error if action failed. -// Read more at: https://redis.io/commands/ttl -func (r *Service) TTL(key string) (seconds int64, hasExpiration bool, found bool) { - c := r.pool.Get() - defer c.Close() - redisVal, err := c.Do("TTL", r.Config.Prefix+key) - if err != nil { - return -2, false, false - } - seconds = redisVal.(int64) - // if -1 means the key has unlimited life time. - hasExpiration = seconds > -1 - // if -2 means key does not exist. - found = !(c.Err() != nil || seconds == -2) - return -} - -func (r *Service) updateTTLConn(c redis.Conn, key string, newSecondsLifeTime int64) error { - reply, err := c.Do("EXPIRE", r.Config.Prefix+key, newSecondsLifeTime) - - if err != nil { - return err - } - - // https://redis.io/commands/expire#return-value - // - // 1 if the timeout was set. - // 0 if key does not exist. - if hadTTLOrExists, ok := reply.(int); ok { - if hadTTLOrExists == 1 { - return nil - } else if hadTTLOrExists == 0 { - return fmt.Errorf("unable to update expiration, the key '%s' was stored without ttl", key) - } // do not check for -1. - } - - return nil -} - -// UpdateTTL will update the ttl of a key. -// Using the "EXPIRE" command. -// Read more at: https://redis.io/commands/expire#refreshing-expires -func (r *Service) UpdateTTL(key string, newSecondsLifeTime int64) error { - c := r.pool.Get() - defer c.Close() - err := c.Err() - if err != nil { - return err - } - - return r.updateTTLConn(c, key, newSecondsLifeTime) -} - -// UpdateTTLMany like `UpdateTTL` but for all keys starting with that "prefix", -// it is a bit faster operation if you need to update all sessions keys (although it can be even faster if we used hash but this will limit other features), -// look the `sessions/Database#OnUpdateExpiration` for example. -func (r *Service) UpdateTTLMany(prefix string, newSecondsLifeTime int64) error { - c := r.pool.Get() - defer c.Close() - if err := c.Err(); err != nil { - return err - } - - keys, err := r.getKeysConn(c, prefix) - if err != nil { - return err - } - - for _, key := range keys { - if err = r.updateTTLConn(c, key, newSecondsLifeTime); err != nil { // fail on first error. - return err - } - } - - return err -} - -// GetAll returns all redis entries using the "SCAN" command (2.8+). -func (r *Service) GetAll() (interface{}, error) { - c := r.pool.Get() - defer c.Close() - if err := c.Err(); err != nil { - return nil, err - } - - redisVal, err := c.Do("SCAN", 0) // 0 -> cursor - - if err != nil { - return nil, err - } - - if redisVal == nil { - return nil, err - } - - return redisVal, nil -} - -func (r *Service) getKeysConn(c redis.Conn, prefix string) ([]string, error) { - if err := c.Send("SCAN", 0, "MATCH", r.Config.Prefix+prefix+"*", "COUNT", 9999999999); err != nil { - return nil, err - } - - if err := c.Flush(); err != nil { - return nil, err - } - - reply, err := c.Receive() - if err != nil || reply == nil { - return nil, err - } - - // it returns []interface, with two entries, the first one is "0" and the second one is a slice of the keys as []interface{uint8....}. - - if keysInterface, ok := reply.([]interface{}); ok { - if len(keysInterface) == 2 { - // take the second, it must contain the slice of keys. - if keysSliceAsBytes, ok := keysInterface[1].([]interface{}); ok { - keys := make([]string, len(keysSliceAsBytes), len(keysSliceAsBytes)) - for i, k := range keysSliceAsBytes { - keys[i] = fmt.Sprintf("%s", k)[len(r.Config.Prefix):] - } - - return keys, nil - } - } - } - - return nil, nil -} - -// GetKeys returns all redis keys using the "SCAN" with MATCH command. -// Read more at: https://redis.io/commands/scan#the-match-option. -func (r *Service) GetKeys(prefix string) ([]string, error) { - c := r.pool.Get() - defer c.Close() - if err := c.Err(); err != nil { - return nil, err - } - - return r.getKeysConn(c, prefix) -} - -// GetBytes returns value, err by its key -// you can use utils.Deserialize((.GetBytes("yourkey"),&theobject{}) -//returns nil and a filled error if something wrong happens -func (r *Service) GetBytes(key string) ([]byte, error) { - c := r.pool.Get() - defer c.Close() - if err := c.Err(); err != nil { - return nil, err - } - - redisVal, err := c.Do("GET", r.Config.Prefix+key) - - if err != nil { - return nil, err - } - if redisVal == nil { - return nil, ErrKeyNotFound.Format(key) - } - - return redis.Bytes(redisVal, err) -} - -// Delete removes redis entry by specific key -func (r *Service) Delete(key string) error { - c := r.pool.Get() - defer c.Close() - - _, err := c.Do("DEL", r.Config.Prefix+key) - return err -} - -func dial(network string, addr string, pass string) (redis.Conn, error) { - if network == "" { - network = DefaultRedisNetwork - } - if addr == "" { - addr = DefaultRedisAddr - } - c, err := redis.Dial(network, addr) - if err != nil { - return nil, err - } - if pass != "" { - if _, err = c.Do("AUTH", pass); err != nil { - c.Close() - return nil, err - } - } - return c, err -} - -// Connect connects to the redis, called only once -func (r *Service) Connect() { - c := r.Config - - if c.IdleTimeout <= 0 { - c.IdleTimeout = DefaultRedisIdleTimeout - } - - if c.Network == "" { - c.Network = DefaultRedisNetwork - } - - if c.Addr == "" { - c.Addr = DefaultRedisAddr - } - - pool := &redis.Pool{IdleTimeout: c.IdleTimeout, MaxIdle: c.MaxIdle, MaxActive: c.MaxActive} - pool.TestOnBorrow = func(c redis.Conn, t time.Time) error { - _, err := c.Do("PING") - return err - } - - if c.Database != "" { - pool.Dial = func() (redis.Conn, error) { - red, err := dial(c.Network, c.Addr, c.Password) - if err != nil { - return nil, err - } - if _, err = red.Do("SELECT", c.Database); err != nil { - red.Close() - return nil, err - } - return red, err - } - } else { - pool.Dial = func() (redis.Conn, error) { - return dial(c.Network, c.Addr, c.Password) - } - } - r.Connected = true - r.pool = pool -} - -// New returns a Redis service filled by the passed config -// to connect call the .Connect(). -func New(cfg ...Config) *Service { - c := DefaultConfig() - if len(cfg) > 0 { - c = cfg[0] - } - r := &Service{pool: &redis.Pool{}, Config: &c} - return r -} diff --git a/view/amber.go b/view/amber.go index 056fbfa0..2888848a 100644 --- a/view/amber.go +++ b/view/amber.go @@ -25,7 +25,7 @@ type AmberEngine struct { templateCache map[string]*template.Template } -var _ Engine = &AmberEngine{} +var _ Engine = (*AmberEngine)(nil) // Amber creates and returns a new amber view engine. func Amber(directory, extension string) *AmberEngine { @@ -77,7 +77,7 @@ func (s *AmberEngine) AddFunc(funcName string, funcBody interface{}) { } // Load parses the templates to the engine. -// It's alos responsible to add the necessary global functions. +// It is responsible to add the necessary global functions. // // Returns an error if something bad happens, user is responsible to catch it. func (s *AmberEngine) Load() error { diff --git a/view/django.go b/view/django.go index c5bd4295..0c62f93c 100644 --- a/view/django.go +++ b/view/django.go @@ -88,7 +88,7 @@ func (dal *tDjangoAssetLoader) Get(path string) (io.Reader, error) { return bytes.NewBuffer(res), nil } -// DjangoEngine contains the amber view engine structure. +// DjangoEngine contains the django view engine structure. type DjangoEngine struct { // files configuration directory string @@ -106,7 +106,7 @@ type DjangoEngine struct { templateCache map[string]*pongo2.Template } -var _ Engine = &DjangoEngine{} +var _ Engine = (*DjangoEngine)(nil) // Django creates and returns a new amber view engine. func Django(directory, extension string) *DjangoEngine { @@ -195,7 +195,7 @@ func (s *DjangoEngine) RegisterTag(tagName string, fn TagParser) error { } // Load parses the templates to the engine. -// It's alos responsible to add the necessary global functions. +// It is responsible to add the necessary global functions. // // Returns an error if something bad happens, user is responsible to catch it. func (s *DjangoEngine) Load() error { diff --git a/view/engine.go b/view/engine.go index 93c99e70..70fac87f 100644 --- a/view/engine.go +++ b/view/engine.go @@ -22,7 +22,7 @@ func getLayout(layout string, globalLayout string) string { // Engine is the interface which all view engines should be implemented in order to be registered inside iris. type Engine interface { - // Load should load the templates from a directory of by binary(assets/go-bindata). + // Load should load the templates from a physical system directory or by an embedded one (assets/go-bindata). Load() error // ExecuteWriter should execute a template by its filename with an optional layout and bindingData. ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error diff --git a/view/handlebars.go b/view/handlebars.go index 68b1fcee..4736ae3d 100644 --- a/view/handlebars.go +++ b/view/handlebars.go @@ -27,6 +27,8 @@ type HandlebarsEngine struct { templateCache map[string]*raymond.Template } +var _ Engine = (*HandlebarsEngine)(nil) + // Handlebars creates and returns a new handlebars view engine. func Handlebars(directory, extension string) *HandlebarsEngine { s := &HandlebarsEngine{ @@ -95,7 +97,7 @@ func (s *HandlebarsEngine) AddFunc(funcName string, funcBody interface{}) { } // Load parses the templates to the engine. -// It's alos responsible to add the necessary global functions. +// It is responsible to add the necessary global functions. // // Returns an error if something bad happens, user is responsible to catch it. func (s *HandlebarsEngine) Load() error { diff --git a/view/html.go b/view/html.go index 8941674e..61d21af3 100644 --- a/view/html.go +++ b/view/html.go @@ -13,32 +13,30 @@ import ( "time" ) -type ( - // HTMLEngine contains the html view engine structure. - HTMLEngine struct { - // files configuration - directory string - extension string - assetFn func(name string) ([]byte, error) // for embedded, in combination with directory & extension - namesFn func() []string // for embedded, in combination with directory & extension - reload bool // if true, each time the ExecuteWriter is called the templates will be reloaded, each ExecuteWriter waits to be finished before writing to a new one. - // parser configuration - options []string // text options - left string - right string - layout string - rmu sync.RWMutex // locks for layoutFuncs and funcs - layoutFuncs map[string]interface{} - funcs map[string]interface{} +// HTMLEngine contains the html view engine structure. +type HTMLEngine struct { + // files configuration + directory string + extension string + assetFn func(name string) ([]byte, error) // for embedded, in combination with directory & extension + namesFn func() []string // for embedded, in combination with directory & extension + reload bool // if true, each time the ExecuteWriter is called the templates will be reloaded, each ExecuteWriter waits to be finished before writing to a new one. + // parser configuration + options []string // text options + left string + right string + layout string + rmu sync.RWMutex // locks for layoutFuncs and funcs + layoutFuncs map[string]interface{} + funcs map[string]interface{} - // - middleware func(name string, contents []byte) (string, error) - Templates *template.Template - // - } -) + // + middleware func(name string, contents []byte) (string, error) + Templates *template.Template + // +} -var _ Engine = &HTMLEngine{} +var _ Engine = (*HTMLEngine)(nil) var emptyFuncs = template.FuncMap{ "yield": func() (string, error) { @@ -132,8 +130,7 @@ func (s *HTMLEngine) Option(opt ...string) *HTMLEngine { } // Delims sets the action delimiters to the specified strings, to be used in -// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template -// definitions will inherit the settings. An empty delimiter stands for the +// templates. An empty delimiter stands for the // corresponding default: {{ or }}. func (s *HTMLEngine) Delims(left, right string) *HTMLEngine { s.left, s.right = left, right