mirror of
https://github.com/kataras/iris.git
synced 2026-01-08 12:31:58 +00:00
🌈 sessions were re-written, update to 4.0.0-alpha.2, read HISTORY.md
**Sessions were re-written ** - Developers can use more than one 'session database', at the same time, to store the sessions - Easy to develop a custom session database (only two functions are required (Load & Update)), [learn more](https://github.com/iris-contrib/sessiondb/blob/master/redis/database.go) - Session databases are located [here](https://github.com/iris-contrib/sessiondb), contributions are welcome - The only frontend deleted 'thing' is the: **config.Sessions.Provider** - No need to register a database, the sessions works out-of-the-box - No frontend/API changes except the `context.Session().Set/Delete/Clear`, they doesn't return errors anymore, btw they (errors) were always nil :) - Examples (master branch) were updated. ```sh $ go get github.com/iris-contrib/sessiondb/$DATABASE ``` ```go db := $DATABASE.New(configurationHere{}) iris.UseSessionDB(db) ``` > Note: Book is not updated yet, examples are up-to-date as always.
This commit is contained in:
@@ -1,442 +0,0 @@
|
||||
# Folder Information
|
||||
|
||||
This folder contains the sessions support for Iris. The folder name is plural (session's') so the `/sessions/providers`, because you can use both of them at the same time.
|
||||
|
||||
# Package information
|
||||
|
||||
This package is new and unique, if you notice a bug or issue [post it here](https://github.com/kataras/iris/issues).
|
||||
|
||||
|
||||
- Cleans the temp memory when a sessions is iddle, and re-loccate it , fast, to the temp memory when it's necessary. Also most used/regular sessions are going front in the memory's list.
|
||||
|
||||
- Supports redisstore and normal memory routing. If redisstore is used but fails to connect then ,automatically, switching to the memory storage.
|
||||
|
||||
|
||||
**A session can be defined as a server-side storage of information that is desired to persist throughout the user's interaction with the web site** or web application.
|
||||
|
||||
Instead of storing large and constantly changing information via cookies in the user's browser, **only a unique identifier is stored on the client side** (called a "session id"). This session id is passed to the web server every time the browser makes an HTTP request (ie a page link or AJAX request). The web application pairs this session id with it's internal database/memory and retrieves the stored variables for use by the requested page.
|
||||
|
||||
----
|
||||
|
||||
You will see two different ways to use the sessions, I'm using the first. No performance differences.
|
||||
|
||||
## How to use - easy way
|
||||
|
||||
Example **memory**
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// these are the defaults
|
||||
//iris.Config().Session.Provider = "memory"
|
||||
//iris.Config().Session.Secret = "irissessionid"
|
||||
//iris.Config().Session.Life = time.Duration(60) *time.Minute
|
||||
|
||||
iris.Get("/set", func(c *iris.Context) {
|
||||
|
||||
//set session values
|
||||
c.Session().Set("name", "iris")
|
||||
|
||||
//test if setted here
|
||||
c.Write("All ok session setted to: %s", c.Session().GetString("name"))
|
||||
})
|
||||
|
||||
iris.Get("/get", func(c *iris.Context) {
|
||||
name := c.Session().GetString("name")
|
||||
|
||||
c.Write("The name on the /set was: %s", name)
|
||||
})
|
||||
|
||||
iris.Get("/delete", func(c *iris.Context) {
|
||||
//get the session for this context
|
||||
|
||||
c.Session().Delete("name")
|
||||
|
||||
})
|
||||
|
||||
iris.Get("/clear", func(c *iris.Context) {
|
||||
|
||||
// removes all entries
|
||||
c.Session().Clear()
|
||||
})
|
||||
|
||||
iris.Get("/destroy", func(c *iris.Context) {
|
||||
//destroy, removes the entire session and cookie
|
||||
c.SessionDestroy()
|
||||
})
|
||||
|
||||
println("Server is listening at :8080")
|
||||
iris.Listen("8080")
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
Example default **redis**
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
iris.Config().Session.Provider = "redis"
|
||||
|
||||
iris.Get("/set", func(c *iris.Context) {
|
||||
|
||||
//set session values
|
||||
c.Session().Set("name", "iris")
|
||||
|
||||
//test if setted here
|
||||
c.Write("All ok session setted to: %s", c.Session().GetString("name"))
|
||||
})
|
||||
|
||||
iris.Get("/get", func(c *iris.Context) {
|
||||
name := c.Session().GetString("name")
|
||||
|
||||
c.Write("The name on the /set was: %s", name)
|
||||
})
|
||||
|
||||
iris.Get("/delete", func(c *iris.Context) {
|
||||
//get the session for this context
|
||||
|
||||
c.Session().Delete("name")
|
||||
|
||||
})
|
||||
|
||||
iris.Get("/clear", func(c *iris.Context) {
|
||||
|
||||
// removes all entries
|
||||
c.Session().Clear()
|
||||
})
|
||||
|
||||
iris.Get("/destroy", func(c *iris.Context) {
|
||||
//destroy, removes the entire session and cookie
|
||||
c.SessionDestroy()
|
||||
})
|
||||
|
||||
println("Server is listening at :8080")
|
||||
iris.Listen("8080")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Example customized **redis**
|
||||
```go
|
||||
// Config the redis config
|
||||
type Config struct {
|
||||
// Network "tcp"
|
||||
Network string
|
||||
// Addr "127.0.01:6379"
|
||||
Addr string
|
||||
// Password string .If no password then no 'AUTH'. Default ""
|
||||
Password string
|
||||
// If Database is empty "" then no 'SELECT'. Default ""
|
||||
Database string
|
||||
// MaxIdle 0 no limit
|
||||
MaxIdle int
|
||||
// MaxActive 0 no limit
|
||||
MaxActive int
|
||||
// IdleTimeout 5 * time.Minute
|
||||
IdleTimeout time.Duration
|
||||
// Prefix "myprefix-for-this-website". Default ""
|
||||
Prefix string
|
||||
// MaxAgeSeconds how much long the redis should keep the session in seconds. Default 2520.0 (42minutes)
|
||||
MaxAgeSeconds int
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/sessions/providers/redis"
|
||||
)
|
||||
|
||||
func init() {
|
||||
redis.Config.Addr = "127.0.0.1:2222"
|
||||
redis.Config.MaxAgeSeconds = 5000.0
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
iris.Config().Session.Provider = "redis"
|
||||
|
||||
iris.Get("/set", func(c *iris.Context) {
|
||||
|
||||
//set session values
|
||||
c.Session().Set("name", "iris")
|
||||
|
||||
//test if setted here
|
||||
c.Write("All ok session setted to: %s", c.Session().GetString("name"))
|
||||
})
|
||||
|
||||
iris.Get("/get", func(c *iris.Context) {
|
||||
name := c.Session().GetString("name")
|
||||
|
||||
c.Write("The name on the /set was: %s", name)
|
||||
})
|
||||
|
||||
iris.Get("/delete", func(c *iris.Context) {
|
||||
//get the session for this context
|
||||
|
||||
c.Session().Delete("name")
|
||||
|
||||
})
|
||||
|
||||
iris.Get("/clear", func(c *iris.Context) {
|
||||
|
||||
// removes all entries
|
||||
c.Session().Clear()
|
||||
})
|
||||
|
||||
iris.Get("/destroy", func(c *iris.Context) {
|
||||
//destroy, removes the entire session and cookie
|
||||
c.SessionDestroy()
|
||||
})
|
||||
|
||||
println("Server is listening at :8080")
|
||||
iris.Listen("8080")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## How to use - hard way
|
||||
|
||||
```go
|
||||
// New creates & returns a new Manager and start its GC
|
||||
// accepts 4 parameters
|
||||
// first is the providerName (string) ["memory","redis"]
|
||||
// second is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
|
||||
// third is the gcDuration (time.Duration)
|
||||
// when this time passes it removes from
|
||||
// temporary memory GC the value which hasn't be used for a long time(gcDuration)
|
||||
// this is for the client's/browser's Cookie life time(expires) also
|
||||
|
||||
New(provider string, cName string, gcDuration time.Duration) *sessions.Manager
|
||||
|
||||
```
|
||||
|
||||
Example **memory**
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/sessions"
|
||||
|
||||
_ "github.com/kataras/iris/sessions/providers/memory" // here we add the memory provider and store
|
||||
)
|
||||
|
||||
var sess *sessions.Manager
|
||||
|
||||
func init() {
|
||||
sess = sessions.New("memory", "irissessionid", time.Duration(60)*time.Minute)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
iris.Get("/set", func(c *iris.Context) {
|
||||
//get the session for this context
|
||||
session := sess.Start(c)
|
||||
|
||||
//set session values
|
||||
session.Set("name", "kataras")
|
||||
|
||||
//test if setted here
|
||||
c.Write("All ok session setted to: %s", session.Get("name"))
|
||||
})
|
||||
|
||||
iris.Get("/get", func(c *iris.Context) {
|
||||
//get the session for this context
|
||||
session := sess.Start(c)
|
||||
|
||||
var name string
|
||||
|
||||
//get the session value
|
||||
if v := session.Get("name"); v != nil {
|
||||
name = v.(string)
|
||||
}
|
||||
// OR just name = session.GetString("name")
|
||||
|
||||
c.Write("The name on the /set was: %s", name)
|
||||
})
|
||||
|
||||
iris.Get("/delete", func(c *iris.Context) {
|
||||
//get the session for this context
|
||||
session := sess.Start(c)
|
||||
|
||||
session.Delete("name")
|
||||
|
||||
})
|
||||
|
||||
iris.Get("/clear", func(c *iris.Context) {
|
||||
//get the session for this context
|
||||
session := sess.Start(c)
|
||||
// removes all entries
|
||||
session.Clear()
|
||||
})
|
||||
|
||||
iris.Get("/destroy", func(c *iris.Context) {
|
||||
//destroy, removes the entire session and cookie
|
||||
sess.Destroy(c)
|
||||
})
|
||||
|
||||
iris.Listen("8080")
|
||||
}
|
||||
|
||||
// session.GetAll() returns all values a map[interface{}]interface{}
|
||||
// session.VisitAll(func(key interface{}, value interface{}) { /* loops for each entry */})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
Example **redis** with default configuration
|
||||
|
||||
The default redis client points to 127.0.0.1:6379
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/sessions"
|
||||
|
||||
_ "github.com/kataras/iris/sessions/providers/redis"
|
||||
// here we add the redis provider and store
|
||||
//with the default redis client points to 127.0.0.1:6379
|
||||
)
|
||||
|
||||
var sess *sessions.Manager
|
||||
|
||||
func init() {
|
||||
sess = sessions.New("redis", "irissessionid", time.Duration(60)*time.Minute)
|
||||
}
|
||||
|
||||
//... usage: same as memory
|
||||
```
|
||||
|
||||
Example **redis** with custom configuration
|
||||
```go
|
||||
type Config struct {
|
||||
// Network "tcp"
|
||||
Network string
|
||||
// Addr "127.0.01:6379"
|
||||
Addr string
|
||||
// Password string .If no password then no 'AUTH'. Default ""
|
||||
Password string
|
||||
// If Database is empty "" then no 'SELECT'. Default ""
|
||||
Database string
|
||||
// MaxIdle 0 no limit
|
||||
MaxIdle int
|
||||
// MaxActive 0 no limit
|
||||
MaxActive int
|
||||
// IdleTimeout 5 * time.Minute
|
||||
IdleTimeout time.Duration
|
||||
//Prefix "myprefix-for-this-website". Default ""
|
||||
Prefix string
|
||||
// MaxAgeSeconds how much long the redis should keep the session in seconds. Default 2520.0 (42minutes)
|
||||
MaxAgeSeconds int
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/sessions"
|
||||
|
||||
"github.com/kataras/iris/sessions/providers/redis"
|
||||
// here we add the redis provider and store
|
||||
//with the default redis client points to 127.0.0.1:6379
|
||||
)
|
||||
|
||||
var sess *sessions.Manager
|
||||
|
||||
func init() {
|
||||
// you can config the redis after init also, but before any client's request
|
||||
// but it's always a good idea to do it before sessions.New...
|
||||
redis.Config.Network = "tcp"
|
||||
redis.Config.Addr = "127.0.0.1:6379"
|
||||
redis.Config.Prefix = "myprefix-for-this-website"
|
||||
|
||||
sess = sessions.New("redis", "irissessionid", time.Duration(60)*time.Minute)
|
||||
}
|
||||
|
||||
//...usage: same as memory
|
||||
```
|
||||
|
||||
### Security: Prevent session hijacking
|
||||
|
||||
> This section is external
|
||||
|
||||
|
||||
**cookie only and token**
|
||||
|
||||
Through this simple example of hijacking a session, you can see that it's very dangerous because it allows attackers to do whatever they want. So how can we prevent session hijacking?
|
||||
|
||||
The first step is to only set session ids in cookies, instead of in URL rewrites. Also, we should set the httponly cookie property to true. This restricts client side scripts that want access to the session id. Using these techniques, cookies cannot be accessed by XSS and it won't be as easy as we showed to get a session id from a cookie manager.
|
||||
|
||||
The second step is to add a token to every request. Similar to the way we dealt with repeat forms in previous sections, we add a hidden field that contains a token. When a request is sent to the server, we can verify this token to prove that the request is unique.
|
||||
|
||||
```go
|
||||
h := md5.New()
|
||||
salt:="secret%^7&8888"
|
||||
io.WriteString(h,salt+time.Now().String())
|
||||
token:=fmt.Sprintf("%x",h.Sum(nil))
|
||||
if r.Form["token"]!=token{
|
||||
// ask to log in
|
||||
}
|
||||
session.Set("token",token)
|
||||
|
||||
```
|
||||
|
||||
|
||||
**Session id timeout**
|
||||
|
||||
Another solution is to add a create time for every session, and to replace expired session ids with new ones. This can prevent session hijacking under certain circumstances.
|
||||
|
||||
```go
|
||||
|
||||
createtime := session.Get("createtime")
|
||||
if createtime == nil {
|
||||
session.Set("createtime", time.Now().Unix())
|
||||
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
|
||||
sess.Destroy(c)
|
||||
session = sess.Start(c)
|
||||
}
|
||||
```
|
||||
|
||||
We set a value to save the create time and check if it's expired (I set 60 seconds here). This step can often thwart session hijacking attempts.
|
||||
|
||||
Combine the two solutions above and you will be able to prevent most session hijacking attempts from succeeding. On the one hand, session ids that are frequently reset will result in an attacker always getting expired and useless session ids; on the other hand, by setting the httponly property on cookies and ensuring that session ids can only be passed via cookies, all URL based attacks are mitigated.
|
||||
@@ -1,14 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"github.com/iris-contrib/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrProviderNotFound returns an error with message: 'Provider was not found. Please try to _ import one'
|
||||
ErrProviderNotFound = errors.New("Provider with name '%s' was not found. Please try to _ import this")
|
||||
// ErrProviderRegister returns an error with message: 'On provider registration. Trace: nil or empty named provider are not acceptable'
|
||||
ErrProviderRegister = errors.New("On provider registration. Trace: nil or empty named provider are not acceptable")
|
||||
// ErrProviderAlreadyExists returns an error with message: 'On provider registration. Trace: provider with name '%s' already exists, maybe you register it twice'
|
||||
ErrProviderAlreadyExists = errors.New("On provider registration. Trace: provider with name '%s' already exists, maybe you register it twice")
|
||||
)
|
||||
@@ -1,171 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/config"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/sessions/store"
|
||||
"github.com/kataras/iris/utils"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type (
|
||||
// IManager is the interface which Manager should implement
|
||||
IManager interface {
|
||||
Start(context.IContext) store.IStore
|
||||
Destroy(context.IContext)
|
||||
GC()
|
||||
}
|
||||
// Manager implements the IManager interface
|
||||
// contains the cookie's name, the provider and a duration for GC and cookie life expire
|
||||
Manager struct {
|
||||
config *config.Sessions
|
||||
provider IProvider
|
||||
mu sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
var _ IManager = &Manager{}
|
||||
|
||||
var (
|
||||
continueOnError = true
|
||||
providers = make(map[string]IProvider)
|
||||
)
|
||||
|
||||
// newManager creates & returns a new Manager
|
||||
func newManager(c config.Sessions) (*Manager, error) {
|
||||
provider, found := providers[c.Provider]
|
||||
if !found {
|
||||
return nil, ErrProviderNotFound.Format(c.Provider)
|
||||
}
|
||||
if c.DecodeCookie {
|
||||
c.Cookie = base64.URLEncoding.EncodeToString([]byte(c.Cookie)) // change the cookie's name/key to a more safe(?)
|
||||
// get the real value for your tests by:
|
||||
//sessIdKey := url.QueryEscape(base64.URLEncoding.EncodeToString([]byte(iris.Config.Sessions.Cookie)))
|
||||
}
|
||||
|
||||
manager := &Manager{}
|
||||
manager.config = &c
|
||||
manager.provider = provider
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
// Register registers a provider
|
||||
func Register(provider IProvider) {
|
||||
if provider == nil {
|
||||
ErrProviderRegister.Panic()
|
||||
}
|
||||
providerName := provider.Name()
|
||||
|
||||
if _, exists := providers[providerName]; exists {
|
||||
if !continueOnError {
|
||||
ErrProviderAlreadyExists.Panicf(providerName)
|
||||
} else {
|
||||
// do nothing it's a map it will overrides the existing provider.
|
||||
}
|
||||
}
|
||||
|
||||
providers[providerName] = provider
|
||||
}
|
||||
|
||||
// Manager implementation
|
||||
|
||||
func (m *Manager) generateSessionID() string {
|
||||
return base64.URLEncoding.EncodeToString(utils.Random(32))
|
||||
}
|
||||
|
||||
var dotB = byte('.')
|
||||
|
||||
// Start starts the session
|
||||
func (m *Manager) Start(ctx context.IContext) store.IStore {
|
||||
|
||||
m.mu.Lock()
|
||||
var store store.IStore
|
||||
requestCtx := ctx.GetRequestCtx()
|
||||
cookieValue := string(requestCtx.Request.Header.Cookie(m.config.Cookie))
|
||||
|
||||
if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
|
||||
sid := m.generateSessionID()
|
||||
store, _ = m.provider.Init(sid)
|
||||
cookie := fasthttp.AcquireCookie()
|
||||
// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
|
||||
cookie.SetKey(m.config.Cookie)
|
||||
cookie.SetValue(sid)
|
||||
cookie.SetPath("/")
|
||||
if !m.config.DisableSubdomainPersistence {
|
||||
requestDomain := ctx.HostString()
|
||||
if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
|
||||
requestDomain = requestDomain[0:portIdx]
|
||||
}
|
||||
|
||||
if requestDomain == "0.0.0.0" || requestDomain == "127.0.0.1" {
|
||||
// for these type of hosts, we can't allow subdomains persistance,
|
||||
// the web browser doesn't understand the mysubdomain.0.0.0.0 and mysubdomain.127.0.0.1 as scorrectly ubdomains because of the many dots
|
||||
// so don't set a domain here
|
||||
|
||||
} else if strings.Count(requestDomain, ".") > 0 { // there is a problem with .localhost setted as the domain, so we check that first
|
||||
|
||||
// RFC2109, we allow level 1 subdomains, but no further
|
||||
// if we have localhost.com , we want the localhost.com.
|
||||
// so if we have something like: mysubdomain.localhost.com we want the localhost here
|
||||
// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
|
||||
// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
|
||||
if dotIdx := strings.LastIndexByte(requestDomain, dotB); dotIdx > 0 {
|
||||
// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
|
||||
s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
|
||||
if secondDotIdx := strings.LastIndexByte(s, dotB); secondDotIdx > 0 {
|
||||
//is mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
|
||||
s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
|
||||
}
|
||||
// replace the s with the requestDomain before the domain's siffux
|
||||
subdomainSuff := strings.LastIndexByte(requestDomain, dotB)
|
||||
if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
|
||||
requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
|
||||
}
|
||||
}
|
||||
// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
|
||||
cookie.SetDomain("." + requestDomain) // . to allow persistance
|
||||
}
|
||||
|
||||
}
|
||||
cookie.SetHTTPOnly(true)
|
||||
cookie.SetExpire(m.config.Expires)
|
||||
requestCtx.Response.Header.SetCookie(cookie)
|
||||
fasthttp.ReleaseCookie(cookie)
|
||||
} else {
|
||||
store, _ = m.provider.Read(cookieValue)
|
||||
}
|
||||
|
||||
m.mu.Unlock()
|
||||
return store
|
||||
}
|
||||
|
||||
// Destroy kills the session and remove the associated cookie
|
||||
func (m *Manager) Destroy(ctx context.IContext) {
|
||||
cookieValue := string(ctx.GetRequestCtx().Request.Header.Cookie(m.config.Cookie))
|
||||
if cookieValue == "" { // nothing to destroy
|
||||
return
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.provider.Destroy(cookieValue)
|
||||
ctx.RemoveCookie(m.config.Cookie)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// GC tick-tock for the store cleanup
|
||||
// it's a blocking function, so run it with go routine, it's totally safe
|
||||
func (m *Manager) GC() {
|
||||
m.mu.Lock()
|
||||
|
||||
m.provider.GC(m.config.GcDuration)
|
||||
// set a timer for the next GC
|
||||
time.AfterFunc(m.config.GcDuration, func() {
|
||||
m.GC()
|
||||
}) // or m.expire.Unix() if Nanosecond() doesn't works here
|
||||
m.mu.Unlock()
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/sessions/store"
|
||||
)
|
||||
|
||||
// IProvider the type which Provider must implement
|
||||
type IProvider interface {
|
||||
Name() string
|
||||
Init(string) (store.IStore, error)
|
||||
Read(string) (store.IStore, error)
|
||||
Destroy(string) error
|
||||
Update(string) error
|
||||
GC(time.Duration)
|
||||
}
|
||||
|
||||
type (
|
||||
// Provider implements the IProvider
|
||||
// contains the temp sessions memory, the store and some options for the cookies
|
||||
Provider struct {
|
||||
name string
|
||||
mu sync.Mutex
|
||||
sessions map[string]*list.Element // underline TEMPORARY memory store
|
||||
list *list.List // for GC
|
||||
NewStore func(sessionId string, cookieLifeDuration time.Duration) store.IStore
|
||||
OnDestroy func(store store.IStore) // this is called when .Destroy
|
||||
cookieLifeDuration time.Duration
|
||||
}
|
||||
)
|
||||
|
||||
var _ IProvider = &Provider{}
|
||||
|
||||
// NewProvider returns a new empty Provider
|
||||
func NewProvider(name string) *Provider {
|
||||
provider := &Provider{name: name, list: list.New()}
|
||||
provider.sessions = make(map[string]*list.Element, 0)
|
||||
return provider
|
||||
}
|
||||
|
||||
// Init creates the store for the first time for this session and returns it
|
||||
func (p *Provider) Init(sid string) (store.IStore, error) {
|
||||
p.mu.Lock()
|
||||
|
||||
newSessionStore := p.NewStore(sid, p.cookieLifeDuration)
|
||||
|
||||
elem := p.list.PushBack(newSessionStore)
|
||||
p.sessions[sid] = elem
|
||||
p.mu.Unlock()
|
||||
return newSessionStore, nil
|
||||
}
|
||||
|
||||
// Read returns the store which sid parameter is belongs
|
||||
func (p *Provider) Read(sid string) (store.IStore, error) {
|
||||
p.mu.Lock()
|
||||
if elem, found := p.sessions[sid]; found {
|
||||
p.mu.Unlock() // yes defer is slow
|
||||
return elem.Value.(store.IStore), nil
|
||||
}
|
||||
p.mu.Unlock()
|
||||
// if not found
|
||||
sessionStore, err := p.Init(sid)
|
||||
return sessionStore, err
|
||||
|
||||
}
|
||||
|
||||
// Destroy always returns a nil error, for now.
|
||||
func (p *Provider) Destroy(sid string) error {
|
||||
p.mu.Lock()
|
||||
if elem, found := p.sessions[sid]; found {
|
||||
elem.Value.(store.IStore).Destroy()
|
||||
delete(p.sessions, sid)
|
||||
p.list.Remove(elem)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates the lastAccessedTime, and moves the memory place element to the front
|
||||
// always returns a nil error, for now
|
||||
func (p *Provider) Update(sid string) error {
|
||||
p.mu.Lock()
|
||||
|
||||
if elem, found := p.sessions[sid]; found {
|
||||
elem.Value.(store.IStore).SetLastAccessedTime(time.Now())
|
||||
p.list.MoveToFront(elem)
|
||||
}
|
||||
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GC clears the memory
|
||||
func (p *Provider) GC(duration time.Duration) {
|
||||
p.mu.Lock()
|
||||
p.cookieLifeDuration = duration
|
||||
defer p.mu.Unlock() //let's defer it and trust the go
|
||||
|
||||
for {
|
||||
elem := p.list.Back()
|
||||
if elem == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// if the time has passed. session was expired, then delete the session and its memory place
|
||||
if (elem.Value.(store.IStore).LastAccessedTime().Unix() + duration.Nanoseconds()) < time.Now().Unix() {
|
||||
p.list.Remove(elem)
|
||||
delete(p.sessions, elem.Value.(store.IStore).ID())
|
||||
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Name the provider's name, example: 'memory' or 'redis'
|
||||
func (p *Provider) Name() string {
|
||||
return p.name
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
"github.com/kataras/iris/sessions/store"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register()
|
||||
}
|
||||
|
||||
var (
|
||||
// Provider the memory provider
|
||||
Provider = sessions.NewProvider("memory")
|
||||
)
|
||||
|
||||
// register registers itself (the new provider with its memory store) to the sessions providers
|
||||
// must runs only once
|
||||
func register() {
|
||||
// the actual work is here.
|
||||
Provider.NewStore = func(sessionId string, cookieLifeDuration time.Duration) store.IStore {
|
||||
return &Store{sid: sessionId, lastAccessedTime: time.Now(), values: make(map[string]interface{}, 0)}
|
||||
}
|
||||
sessions.Register(Provider)
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/sessions/store"
|
||||
)
|
||||
|
||||
// Store the memory store, contains the session id and the values
|
||||
type Store struct {
|
||||
sid string
|
||||
lastAccessedTime time.Time
|
||||
values map[string]interface{} // here is the real memory store
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var _ store.IStore = &Store{}
|
||||
|
||||
// GetAll returns all values
|
||||
func (s *Store) GetAll() map[string]interface{} {
|
||||
return s.values
|
||||
}
|
||||
|
||||
// VisitAll loop each one entry and calls the callback function func(key,value)
|
||||
func (s *Store) VisitAll(cb func(k string, v interface{})) {
|
||||
for key := range s.values {
|
||||
cb(key, s.values[key])
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value of an entry by its key
|
||||
func (s *Store) Get(key string) interface{} {
|
||||
Provider.Update(s.sid)
|
||||
if value, found := s.values[key]; found {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetString same as Get but returns as string, if nil then returns an empty string
|
||||
func (s *Store) GetString(key string) string {
|
||||
if value := s.Get(key); value != nil {
|
||||
if v, ok := value.(string); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetInt same as Get but returns as int, if nil then returns -1
|
||||
func (s *Store) GetInt(key string) int {
|
||||
if value := s.Get(key); value != nil {
|
||||
if v, ok := value.(int); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// Set fills the session with an entry, it receives a key and a value
|
||||
// returns an error, which is always nil
|
||||
func (s *Store) Set(key string, value interface{}) error {
|
||||
s.mu.Lock()
|
||||
s.values[key] = value
|
||||
s.mu.Unlock()
|
||||
Provider.Update(s.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes an entry by its key
|
||||
// returns an error, which is always nil
|
||||
func (s *Store) Delete(key string) error {
|
||||
s.mu.Lock()
|
||||
delete(s.values, key)
|
||||
s.mu.Unlock()
|
||||
Provider.Update(s.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear removes all entries
|
||||
// returns an error, which is always nil
|
||||
func (s *Store) Clear() error {
|
||||
s.mu.Lock()
|
||||
for key := range s.values {
|
||||
delete(s.values, key)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
Provider.Update(s.sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns the session id
|
||||
func (s *Store) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// LastAccessedTime returns the last time this session has been used
|
||||
func (s *Store) LastAccessedTime() time.Time {
|
||||
return s.lastAccessedTime
|
||||
}
|
||||
|
||||
// SetLastAccessedTime updates the last accessed time
|
||||
func (s *Store) SetLastAccessedTime(lastacc time.Time) {
|
||||
s.lastAccessedTime = lastacc
|
||||
}
|
||||
|
||||
// Destroy deletes all keys
|
||||
func (s *Store) Destroy() {
|
||||
// clears without provider's update.
|
||||
s.mu.Lock()
|
||||
for key := range s.values {
|
||||
delete(s.values, key)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/sessions/store"
|
||||
"github.com/kataras/iris/utils"
|
||||
)
|
||||
|
||||
/*Notes only for me
|
||||
--------
|
||||
Here we are setting a structure which keeps the current session's values setted by store.Set(key,value)
|
||||
this is the RedisValue struct.
|
||||
if noexists
|
||||
RedisValue := RedisValue{sessionid,values)
|
||||
|
||||
RedisValue.values[thekey]=thevalue
|
||||
|
||||
|
||||
service.Set(store.sid,RedisValue)
|
||||
|
||||
because we are using the same redis service for all sessions, and this is the best way to separate them,
|
||||
without prefix and all that which I tried and failed to deserialize them correctly if the value is string...
|
||||
so again we will keep the current server's sessions into memory
|
||||
and fetch them(the sessions) from the redis at each first session run. Yes this is the fastest way to get/set a session
|
||||
and at the same time they are keep saved to the redis and the GC will cleanup the memory after a while like we are doing
|
||||
with the memory provider. Or just have a values field inside the Store and use just it, yes better simpler approach.
|
||||
Ok then, let's convert it again.
|
||||
*/
|
||||
|
||||
// Values is just a type of a map[string]interface{}
|
||||
type Values map[string]interface{}
|
||||
|
||||
// Store the redis session store
|
||||
type Store struct {
|
||||
sid string
|
||||
lastAccessedTime time.Time
|
||||
values Values
|
||||
cookieLifeDuration time.Duration //used on .Set-> SETEX on redis
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var _ store.IStore = &Store{}
|
||||
|
||||
// NewStore creates and returns a new store based on the session id(string) and the cookie life duration (time.Duration)
|
||||
func NewStore(sid string, cookieLifeDuration time.Duration) *Store {
|
||||
s := &Store{sid: sid, lastAccessedTime: time.Now(), cookieLifeDuration: cookieLifeDuration}
|
||||
//fetch the values from this session id and copy-> store them
|
||||
val, err := redis.GetBytes(sid)
|
||||
if err == nil {
|
||||
err = utils.DeserializeBytes(val, &s.values)
|
||||
if err != nil {
|
||||
//if deserialization failed
|
||||
s.values = Values{}
|
||||
}
|
||||
|
||||
}
|
||||
if s.values == nil {
|
||||
//if key/sid wasn't found or was found but no entries in it(L72)
|
||||
s.values = Values{}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// serialize the values to be stored as strings inside the Redis, we panic at any serialization error here
|
||||
func serialize(values Values) []byte {
|
||||
val, err := utils.SerializeBytes(values)
|
||||
if err != nil {
|
||||
panic("On redisstore.serialize: " + err.Error())
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// update updates the real redis store
|
||||
func (s *Store) update() {
|
||||
go redis.Set(s.sid, serialize(s.values), s.cookieLifeDuration.Seconds()) //set/update all the values, in goroutine
|
||||
}
|
||||
|
||||
// GetAll returns all values
|
||||
func (s *Store) GetAll() map[string]interface{} {
|
||||
return s.values
|
||||
}
|
||||
|
||||
// VisitAll loop each one entry and calls the callback function func(key,value)
|
||||
func (s *Store) VisitAll(cb func(k string, v interface{})) {
|
||||
for key := range s.values {
|
||||
cb(key, s.values[key])
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value of an entry by its key
|
||||
func (s *Store) Get(key string) interface{} {
|
||||
Provider.Update(s.sid)
|
||||
if value, found := s.values[key]; found {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetString same as Get but returns as string, if nil then returns an empty string
|
||||
func (s *Store) GetString(key string) string {
|
||||
if value := s.Get(key); value != nil {
|
||||
if v, ok := value.(string); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetInt same as Get but returns as int, if nil then returns -1
|
||||
func (s *Store) GetInt(key string) int {
|
||||
if value := s.Get(key); value != nil {
|
||||
if v, ok := value.(int); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// Set fills the session with an entry, it receives a key and a value
|
||||
// returns an error, which is always nil
|
||||
func (s *Store) Set(key string, value interface{}) error {
|
||||
s.mu.Lock()
|
||||
s.values[key] = value
|
||||
s.mu.Unlock()
|
||||
Provider.Update(s.sid)
|
||||
|
||||
s.update()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes an entry by its key
|
||||
// returns an error, which is always nil
|
||||
func (s *Store) Delete(key string) error {
|
||||
s.mu.Lock()
|
||||
delete(s.values, key)
|
||||
s.mu.Unlock()
|
||||
Provider.Update(s.sid)
|
||||
s.update()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear removes all entries
|
||||
// returns an error, which is always nil
|
||||
func (s *Store) Clear() error {
|
||||
//we are not using the Redis.Delete, I made so work for nothing.. we wanted only the .Set at the end...
|
||||
s.mu.Lock()
|
||||
for key := range s.values {
|
||||
delete(s.values, key)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
Provider.Update(s.sid)
|
||||
s.update()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns the session id
|
||||
func (s *Store) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// LastAccessedTime returns the last time this session has been used
|
||||
func (s *Store) LastAccessedTime() time.Time {
|
||||
return s.lastAccessedTime
|
||||
}
|
||||
|
||||
// SetLastAccessedTime updates the last accessed time
|
||||
func (s *Store) SetLastAccessedTime(lastacc time.Time) {
|
||||
s.lastAccessedTime = lastacc
|
||||
}
|
||||
|
||||
// Destroy deletes entirely the session, from the memory, the client's cookie and the store
|
||||
func (s *Store) Destroy() {
|
||||
// remove the whole value which is the s.values from real redis
|
||||
redis.Delete(s.sid)
|
||||
s.mu.Lock()
|
||||
for key := range s.values {
|
||||
delete(s.values, key)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/sessions"
|
||||
"github.com/kataras/iris/sessions/providers/redis/service"
|
||||
"github.com/kataras/iris/sessions/store"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register()
|
||||
}
|
||||
|
||||
var (
|
||||
// Provider is the redis provider
|
||||
Provider = sessions.NewProvider("redis")
|
||||
// redis is the default redis service, you can set configs via this object
|
||||
redis = service.New()
|
||||
// Config is just the Redis(service)' config
|
||||
Config = redis.Config
|
||||
|
||||
// Empty() because maybe the user wants to edit the default configs.
|
||||
//the Connect goes to the first NewStore, when user ask for session, so you have the time to change the default configs
|
||||
)
|
||||
|
||||
// register registers itself (the new provider with its memory store) to the sessions providers
|
||||
// must runs only once
|
||||
func register() {
|
||||
// the actual work is here.
|
||||
Provider.NewStore = func(sessionId string, cookieLifeDuration time.Duration) store.IStore {
|
||||
//println("memory.go:49-> requesting new memory store with sessionid: " + sessionId)
|
||||
if !redis.Connected {
|
||||
redis.Connect()
|
||||
_, err := redis.PingPong()
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
// don't use to get the logger, just prin these to the console... atm
|
||||
println("Redis Connection error on iris/sessions/providers/redisstore.Connect: " + err.Error())
|
||||
println("But don't panic, auto-switching to memory store right now!")
|
||||
}
|
||||
}
|
||||
}
|
||||
return NewStore(sessionId, cookieLifeDuration)
|
||||
}
|
||||
|
||||
sessions.Register(Provider)
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/iris-contrib/errors"
|
||||
"github.com/kataras/iris/config"
|
||||
)
|
||||
|
||||
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.Redis
|
||||
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.Return()
|
||||
}
|
||||
|
||||
// Set sets to the redis
|
||||
// key string, value string, you can use utils.Serialize(&myobject{}) to convert an object to []byte
|
||||
func (r *Service) Set(key string, value []byte, maxageseconds ...float64) (err error) { // map[interface{}]interface{}) (err error) {
|
||||
maxage := config.DefaultRedisMaxAgeSeconds //1 year
|
||||
c := r.pool.Get()
|
||||
defer c.Close()
|
||||
if err = c.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
if len(maxageseconds) > 0 {
|
||||
if max := maxageseconds[0]; max >= 0 {
|
||||
maxage = max
|
||||
}
|
||||
}
|
||||
_, err = c.Do("SETEX", r.Config.Prefix+key, maxage, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns value, err by its key
|
||||
// you can use utils.Deserialize((.Get("yourkey"),&theobject{})
|
||||
//returns nil and a filled error if something wrong happens
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// GetString returns value, err by its key
|
||||
// you can use utils.Deserialize((.GetString("yourkey"),&theobject{})
|
||||
//returns empty string and a filled error if something wrong happens
|
||||
func (r *Service) GetString(key string) (string, error) {
|
||||
redisVal, err := r.Get(key)
|
||||
if redisVal == nil {
|
||||
return "", ErrKeyNotFound.Format(key)
|
||||
}
|
||||
|
||||
sVal, err := redis.String(redisVal, err)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sVal, nil
|
||||
}
|
||||
|
||||
// GetInt returns value, err by its key
|
||||
// you can use utils.Deserialize((.GetInt("yourkey"),&theobject{})
|
||||
//returns -1 int and a filled error if something wrong happens
|
||||
func (r *Service) GetInt(key string) (int, error) {
|
||||
redisVal, err := r.Get(key)
|
||||
if redisVal == nil {
|
||||
return -1, ErrKeyNotFound.Format(key)
|
||||
}
|
||||
|
||||
intVal, err := redis.Int(redisVal, err)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return intVal, nil
|
||||
}
|
||||
|
||||
// GetStringMap returns map[string]string, err by its key
|
||||
//returns nil and a filled error if something wrong happens
|
||||
func (r *Service) GetStringMap(key string) (map[string]string, error) {
|
||||
redisVal, err := r.Get(key)
|
||||
if redisVal == nil {
|
||||
return nil, ErrKeyNotFound.Format(key)
|
||||
}
|
||||
|
||||
_map, err := redis.StringMap(redisVal, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return _map, nil
|
||||
}
|
||||
|
||||
// GetAll returns all keys and their values from a specific key (map[string]string)
|
||||
// returns a filled error if something bad happened
|
||||
func (r *Service) GetAll(key string) (map[string]string, error) {
|
||||
c := r.pool.Get()
|
||||
defer c.Close()
|
||||
if err := c.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply, err := c.Do("HGETALL", r.Config.Prefix+key)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reply == nil {
|
||||
return nil, ErrKeyNotFound.Format(key)
|
||||
}
|
||||
|
||||
return redis.StringMap(reply, err)
|
||||
|
||||
}
|
||||
|
||||
// GetAllKeysByPrefix returns all []string keys by a key prefix from the redis
|
||||
func (r *Service) GetAllKeysByPrefix(prefix string) ([]string, error) {
|
||||
c := r.pool.Get()
|
||||
defer c.Close()
|
||||
if err := c.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply, err := c.Do("KEYS", r.Config.Prefix+prefix)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reply == nil {
|
||||
return nil, ErrKeyNotFound.Format(prefix)
|
||||
}
|
||||
return redis.Strings(reply, err)
|
||||
|
||||
}
|
||||
|
||||
// Delete removes redis entry by specific key
|
||||
func (r *Service) Delete(key string) error {
|
||||
c := r.pool.Get()
|
||||
defer c.Close()
|
||||
if _, err := c.Do("DEL", r.Config.Prefix+key); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dial(network string, addr string, pass string) (redis.Conn, error) {
|
||||
if network == "" {
|
||||
network = config.DefaultRedisNetwork
|
||||
}
|
||||
if addr == "" {
|
||||
addr = config.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 = config.DefaultRedisIdleTimeout
|
||||
}
|
||||
|
||||
if c.Network == "" {
|
||||
c.Network = config.DefaultRedisNetwork
|
||||
}
|
||||
|
||||
if c.Addr == "" {
|
||||
c.Addr = config.DefaultRedisAddr
|
||||
}
|
||||
|
||||
if c.MaxAgeSeconds <= 0 {
|
||||
c.MaxAgeSeconds = config.DefaultRedisMaxAgeSeconds
|
||||
}
|
||||
|
||||
pool := &redis.Pool{IdleTimeout: config.DefaultRedisIdleTimeout, 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.Redis) *Service {
|
||||
c := config.DefaultRedis().Merge(cfg)
|
||||
r := &Service{pool: &redis.Pool{}, Config: &c}
|
||||
return r
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import "github.com/kataras/iris/config"
|
||||
|
||||
// New creates & returns a new Manager and start its GC
|
||||
func New(cfg ...config.Sessions) *Manager {
|
||||
c := config.DefaultSessions().Merge(cfg)
|
||||
// If provider is empty then return nil manager, means that the sessions are disabled
|
||||
if c.Provider == "" {
|
||||
return nil
|
||||
}
|
||||
manager, err := newManager(c)
|
||||
if err != nil {
|
||||
panic(err.Error()) // we have to panic here because we will start GC after and if provider is nil then many panics will come
|
||||
}
|
||||
//run the GC here
|
||||
go manager.GC()
|
||||
return manager
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Package store the package is in diffent folder to reduce the import cycles from the ./context/context.go *
|
||||
package store
|
||||
|
||||
import "time"
|
||||
|
||||
// IStore is the interface which all session stores should implement
|
||||
type IStore interface {
|
||||
Get(string) interface{}
|
||||
GetString(string) string
|
||||
GetInt(string) int
|
||||
Set(string, interface{}) error
|
||||
Delete(string) error
|
||||
Clear() error
|
||||
VisitAll(func(string, interface{}))
|
||||
GetAll() map[string]interface{}
|
||||
ID() string
|
||||
LastAccessedTime() time.Time
|
||||
SetLastAccessedTime(time.Time)
|
||||
Destroy()
|
||||
}
|
||||
Reference in New Issue
Block a user