mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
SessionsPolicy and sessions adaptor, history and _example written.
Former-commit-id: e8b0dde3cb3b72919f01b9d836d8ccb3d4e20214
This commit is contained in:
parent
82afcc5aa6
commit
13e83fc57e
115
HISTORY.md
115
HISTORY.md
|
@ -855,8 +855,121 @@ editors worked before but I couldn't let some developers without support.
|
||||||
|
|
||||||
### Sessions
|
### Sessions
|
||||||
|
|
||||||
- IMPROVEMENT: [Sessions manager](https://github.com/kataras/go-sessions) works even faster now.
|
|
||||||
|
|
||||||
|
Sessions manager is also an Adaptor now, `iris.SessionsPolicy`.
|
||||||
|
So far we used the `kataras/go-sessions`, you could always use other session manager ofcourse but you would lose the `context.Session()`
|
||||||
|
and its returning value, the `iris.Session` now.
|
||||||
|
|
||||||
|
`SessionsPolicy` gives the developers the opportunity to adapt any,
|
||||||
|
compatible with a particular simple interface(Start and Destroy methods), third-party sessions managers.
|
||||||
|
|
||||||
|
- The API for sessions inside context is the same, no matter what session manager you wanna to adapt.
|
||||||
|
- The API for sessions inside context didn't changed, it's the same as you knew it.
|
||||||
|
|
||||||
|
- Iris, of course, has built'n `SessionsPolicy` adaptor(the kataras/go-sessions: edited to remove fasthttp dependencies).
|
||||||
|
- Sessions manager works even faster now and a bug fixed for some browsers.
|
||||||
|
|
||||||
|
- Functions like, adding a database or store(i.e: `UseDatabase`) depends on the session manager of your choice,
|
||||||
|
Iris doesn't requires these things
|
||||||
|
to adapt a package as a session manager. So `iris.UseDatabase` has been removed and depends on the `mySessions.UseDatabase` you 'll see below.
|
||||||
|
|
||||||
|
- `iris.DestroySessionByID and iris.DestroyAllSessions` have been also removed, depends on the session manager of your choice, `mySessions.DestroyByID and mySessions.DestroyAll` should do the job now.
|
||||||
|
|
||||||
|
|
||||||
|
> Don't worry about forgetting to adapt any feature that you use inside Iris, Iris will print you a how-to-fix message at iris.DevMode log level.
|
||||||
|
|
||||||
|
**[Example](https://github.com/kataras/iris/tree/6.2/adaptors/sessions/_example) code:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/kataras/iris.v6"
|
||||||
|
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||||
|
"gopkg.in/kataras/iris.v6/adaptors/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
app.Adapt(iris.DevLogger()) // enable all (error) logs
|
||||||
|
app.Adapt(httprouter.New()) // select the httprouter as the servemux
|
||||||
|
|
||||||
|
mySessions := sessions.New(sessions.Config{
|
||||||
|
// Cookie string, the session's client cookie name, for example: "mysessionid"
|
||||||
|
//
|
||||||
|
// Defaults to "irissessionid"
|
||||||
|
Cookie: "mysessionid",
|
||||||
|
// base64 urlencoding,
|
||||||
|
// if you have strange name cookie name enable this
|
||||||
|
DecodeCookie: false,
|
||||||
|
// it's time.Duration, from the time cookie is created, how long it can be alive?
|
||||||
|
// 0 means no expire.
|
||||||
|
// -1 means expire when browser closes
|
||||||
|
// or set a value, like 2 hours:
|
||||||
|
Expires: time.Hour * 2,
|
||||||
|
// the length of the sessionid's cookie's value
|
||||||
|
CookieLength: 32,
|
||||||
|
// if you want to invalid cookies on different subdomains
|
||||||
|
// of the same host, then enable it
|
||||||
|
DisableSubdomainPersistence: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// OPTIONALLY:
|
||||||
|
// import "gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis"
|
||||||
|
// or import "github.com/kataras/go-sessions/sessiondb/$any_available_community_database"
|
||||||
|
// mySessions.UseDatabase(redis.New(...))
|
||||||
|
|
||||||
|
app.Adapt(mySessions) // Adapt the session manager we just created.
|
||||||
|
|
||||||
|
app.Get("/", func(ctx *iris.Context) {
|
||||||
|
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
|
||||||
|
})
|
||||||
|
app.Get("/set", func(ctx *iris.Context) {
|
||||||
|
|
||||||
|
//set session values
|
||||||
|
ctx.Session().Set("name", "iris")
|
||||||
|
|
||||||
|
//test if setted here
|
||||||
|
ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name"))
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/get", func(ctx *iris.Context) {
|
||||||
|
// get a specific key, as string, if no found returns just an empty string
|
||||||
|
name := ctx.Session().GetString("name")
|
||||||
|
|
||||||
|
ctx.Writef("The name on the /set was: %s", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/delete", func(ctx *iris.Context) {
|
||||||
|
// delete a specific key
|
||||||
|
ctx.Session().Delete("name")
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/clear", func(ctx *iris.Context) {
|
||||||
|
// removes all entries
|
||||||
|
ctx.Session().Clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/destroy", func(ctx *iris.Context) {
|
||||||
|
|
||||||
|
//destroy, removes the entire session and cookie
|
||||||
|
ctx.SessionDestroy()
|
||||||
|
msg := "You have to refresh the page to completely remove the session (browsers works this way, it's not iris-specific.)"
|
||||||
|
|
||||||
|
ctx.Writef(msg)
|
||||||
|
ctx.Log(iris.DevMode, msg)
|
||||||
|
}) // Note about destroy:
|
||||||
|
//
|
||||||
|
// You can destroy a session outside of a handler too, using the:
|
||||||
|
// mySessions.DestroyByID
|
||||||
|
// mySessions.DestroyAll
|
||||||
|
|
||||||
|
app.Listen(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### Websockets
|
### Websockets
|
||||||
|
|
||||||
|
|
21
adaptors/sessions/LICENSE
Normal file
21
adaptors/sessions/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016-2017 Gerasimos Maropoulos
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
87
adaptors/sessions/_example/main.go
Normal file
87
adaptors/sessions/_example/main.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/kataras/iris.v6"
|
||||||
|
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||||
|
"gopkg.in/kataras/iris.v6/adaptors/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
app.Adapt(iris.DevLogger()) // enable all (error) logs
|
||||||
|
app.Adapt(httprouter.New()) // select the httprouter as the servemux
|
||||||
|
|
||||||
|
mySessions := sessions.New(sessions.Config{
|
||||||
|
// Cookie string, the session's client cookie name, for example: "mysessionid"
|
||||||
|
//
|
||||||
|
// Defaults to "irissessionid"
|
||||||
|
Cookie: "mysessionid",
|
||||||
|
// base64 urlencoding,
|
||||||
|
// if you have strange name cookie name enable this
|
||||||
|
DecodeCookie: false,
|
||||||
|
// it's time.Duration, from the time cookie is created, how long it can be alive?
|
||||||
|
// 0 means no expire.
|
||||||
|
// -1 means expire when browser closes
|
||||||
|
// or set a value, like 2 hours:
|
||||||
|
Expires: time.Hour * 2,
|
||||||
|
// the length of the sessionid's cookie's value
|
||||||
|
CookieLength: 32,
|
||||||
|
// if you want to invalid cookies on different subdomains
|
||||||
|
// of the same host, then enable it
|
||||||
|
DisableSubdomainPersistence: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// OPTIONALLY:
|
||||||
|
// import "gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis"
|
||||||
|
// or import "github.com/kataras/go-sessions/sessiondb/$any_available_community_database"
|
||||||
|
// mySessions.UseDatabase(redis.New(...))
|
||||||
|
|
||||||
|
app.Adapt(mySessions) // Adapt the session manager we just created.
|
||||||
|
|
||||||
|
app.Get("/", func(ctx *iris.Context) {
|
||||||
|
ctx.Writef("You should navigate to the /set, /get, /delete, /clear,/destroy instead")
|
||||||
|
})
|
||||||
|
app.Get("/set", func(ctx *iris.Context) {
|
||||||
|
|
||||||
|
//set session values
|
||||||
|
ctx.Session().Set("name", "iris")
|
||||||
|
|
||||||
|
//test if setted here
|
||||||
|
ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name"))
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/get", func(ctx *iris.Context) {
|
||||||
|
// get a specific key, as string, if no found returns just an empty string
|
||||||
|
name := ctx.Session().GetString("name")
|
||||||
|
|
||||||
|
ctx.Writef("The name on the /set was: %s", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/delete", func(ctx *iris.Context) {
|
||||||
|
// delete a specific key
|
||||||
|
ctx.Session().Delete("name")
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/clear", func(ctx *iris.Context) {
|
||||||
|
// removes all entries
|
||||||
|
ctx.Session().Clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/destroy", func(ctx *iris.Context) {
|
||||||
|
|
||||||
|
//destroy, removes the entire session and cookie
|
||||||
|
ctx.SessionDestroy()
|
||||||
|
msg := "You have to refresh the page to completely remove the session (browsers works this way, it's not iris-specific.)"
|
||||||
|
|
||||||
|
ctx.Writef(msg)
|
||||||
|
ctx.Log(iris.DevMode, msg)
|
||||||
|
}) // Note about destroy:
|
||||||
|
//
|
||||||
|
// You can destroy a session outside of a handler too, using the:
|
||||||
|
// mySessions.DestroyByID
|
||||||
|
// mySessions.DestroyAll
|
||||||
|
|
||||||
|
app.Listen(":8080")
|
||||||
|
}
|
74
adaptors/sessions/config.go
Normal file
74
adaptors/sessions/config.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultCookieName the secret cookie's name for sessions
|
||||||
|
DefaultCookieName = "irissessionid"
|
||||||
|
// DefaultCookieLength is the default Session Manager's CookieLength, which is 32
|
||||||
|
DefaultCookieLength = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Config is the configuration for sessions
|
||||||
|
// has 5 fields
|
||||||
|
// first is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
|
||||||
|
// second enable if you want to decode the cookie's key also
|
||||||
|
// third is the time which the client's cookie expires
|
||||||
|
// forth is the cookie length (sessionid) int, defaults to 32, do not change if you don't have any reason to do
|
||||||
|
// fifth is the DisableSubdomainPersistence which you can set it to true in order dissallow your q subdomains to have access to the session cook
|
||||||
|
Config struct {
|
||||||
|
// Cookie string, the session's client cookie name, for example: "mysessionid"
|
||||||
|
//
|
||||||
|
// Defaults to "irissessionid"
|
||||||
|
Cookie string
|
||||||
|
|
||||||
|
// DecodeCookie set it to true to decode the cookie key with base64 URLEncoding
|
||||||
|
//
|
||||||
|
// Defaults to false
|
||||||
|
DecodeCookie bool
|
||||||
|
|
||||||
|
// Expires the duration of which the cookie must expires (created_time.Add(Expires)).
|
||||||
|
// If you want to delete the cookie when the browser closes, set it to -1.
|
||||||
|
//
|
||||||
|
// 0 means no expire, (24 years)
|
||||||
|
// -1 means when browser closes
|
||||||
|
// > 0 is the time.Duration which the session cookies should expire.
|
||||||
|
//
|
||||||
|
// Defaults to infinitive/unlimited life duration(0)
|
||||||
|
Expires time.Duration
|
||||||
|
|
||||||
|
// CookieLength the length of the sessionid's cookie's value, let it to 0 if you don't want to change it
|
||||||
|
//
|
||||||
|
// Defaults to 32
|
||||||
|
CookieLength int
|
||||||
|
|
||||||
|
// DisableSubdomainPersistence set it to true in order dissallow your q subdomains to have access to the session cookie
|
||||||
|
//
|
||||||
|
// Defaults to false
|
||||||
|
DisableSubdomainPersistence bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate corrects missing fields configuration fields and returns the right configuration
|
||||||
|
func (c Config) Validate() Config {
|
||||||
|
|
||||||
|
if c.Cookie == "" {
|
||||||
|
c.Cookie = DefaultCookieName
|
||||||
|
}
|
||||||
|
|
||||||
|
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(Sessions.Cookie)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.CookieLength <= 0 {
|
||||||
|
c.CookieLength = DefaultCookieLength
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
165
adaptors/sessions/cookie.go
Normal file
165
adaptors/sessions/cookie.go
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/gob"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// CookieExpireDelete may be set on Cookie.Expire for expiring the given cookie.
|
||||||
|
CookieExpireDelete = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
// CookieExpireUnlimited indicates that the cookie doesn't expire.
|
||||||
|
CookieExpireUnlimited = time.Now().AddDate(24, 10, 10)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetCookie returns cookie's value by it's name
|
||||||
|
// returns empty string if nothing was found
|
||||||
|
func GetCookie(name string, req *http.Request) string {
|
||||||
|
c, err := req.Cookie(name)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCookie adds a cookie
|
||||||
|
func AddCookie(cookie *http.Cookie, res http.ResponseWriter) {
|
||||||
|
if v := cookie.String(); v != "" {
|
||||||
|
http.SetCookie(res, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveCookie deletes a cookie by it's name/key
|
||||||
|
func RemoveCookie(name string, res http.ResponseWriter, req *http.Request) {
|
||||||
|
c, err := req.Cookie(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Expires = CookieExpireDelete
|
||||||
|
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||||
|
c.MaxAge = -1
|
||||||
|
c.Value = ""
|
||||||
|
c.Path = "/"
|
||||||
|
AddCookie(c, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidCookieDomain returns true if the receiver is a valid domain to set
|
||||||
|
// valid means that is recognised as 'domain' by the browser, so it(the cookie) can be shared with subdomains also
|
||||||
|
func IsValidCookieDomain(domain string) bool {
|
||||||
|
if domain == "0.0.0.0" || domain == "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 mysubdomain.32.196.56.181. as scorrectly ubdomains because of the many dots
|
||||||
|
// so don't set a cookie domain here, let browser handle this
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
dotLen := strings.Count(domain, ".")
|
||||||
|
if dotLen == 0 {
|
||||||
|
// we don't have a domain, maybe something like 'localhost', browser doesn't see the .localhost as wildcard subdomain+domain
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if dotLen >= 3 {
|
||||||
|
if lastDotIdx := strings.LastIndexByte(domain, '.'); lastDotIdx != -1 {
|
||||||
|
// chekc the last part, if it's number then propably it's ip
|
||||||
|
if len(domain) > lastDotIdx+1 {
|
||||||
|
_, err := strconv.Atoi(domain[lastDotIdx+1:])
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeCookieValue(value string) string {
|
||||||
|
return base64.URLEncoding.EncodeToString([]byte(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeCookieValue(value string) (string, error) {
|
||||||
|
v, err := base64.URLEncoding.DecodeString(value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// ----------------------------------Strings & Serialization----------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
// -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const (
|
||||||
|
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||||
|
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||||
|
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||||
|
)
|
||||||
|
|
||||||
|
// random takes a parameter (int) and returns random slice of byte
|
||||||
|
// ex: var randomstrbytes []byte; randomstrbytes = Random(32)
|
||||||
|
// note: this code doesn't belongs to me, but it works just fine*
|
||||||
|
//
|
||||||
|
// Used for the default SessionIDGenerator which you can change.
|
||||||
|
func random(n int) []byte {
|
||||||
|
src := rand.NewSource(time.Now().UnixNano())
|
||||||
|
b := make([]byte, n)
|
||||||
|
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||||
|
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||||
|
if remain == 0 {
|
||||||
|
cache, remain = src.Int63(), letterIdxMax
|
||||||
|
}
|
||||||
|
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||||
|
b[i] = letterBytes[idx]
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
cache >>= letterIdxBits
|
||||||
|
remain--
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomString accepts a number(10 for example) and returns a random string using simple but fairly safe random algorithm
|
||||||
|
func randomString(n int) string {
|
||||||
|
return string(random(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize serialize any type to gob bytes and after returns its the base64 encoded string
|
||||||
|
func Serialize(m interface{}) (string, error) {
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
encoder := gob.NewEncoder(&b)
|
||||||
|
err := encoder.Encode(m)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(b.Bytes()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize accepts an encoded string and a data struct which will be filled with the desierialized string
|
||||||
|
// using gob decoder
|
||||||
|
func Deserialize(str string, m interface{}) error {
|
||||||
|
by, err := base64.StdEncoding.DecodeString(str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
b.Write(by)
|
||||||
|
d := gob.NewDecoder(&b)
|
||||||
|
// d := gob.NewDecoder(bytes.NewBufferString(str))
|
||||||
|
err = d.Decode(&m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
17
adaptors/sessions/database.go
Normal file
17
adaptors/sessions/database.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
// Database is the interface which all session databases should implement
|
||||||
|
// By design it doesn't support any type of cookie session like other frameworks,
|
||||||
|
// I want to protect you, believe me, no context access (although we could)
|
||||||
|
// The scope of the database is to session somewhere the sessions in order to
|
||||||
|
// keep them after restarting the server, nothing more.
|
||||||
|
// the values are sessiond by the underline session, the check for new sessions, or
|
||||||
|
// 'this session value should added' are made automatically by q, you are able just to set the values to your backend database with Load function.
|
||||||
|
// session database doesn't have any write or read access to the session, the loading of
|
||||||
|
// the initial data is done by the Load(string) map[string]interfface{} function
|
||||||
|
// synchronization are made automatically, you can register more than one session database
|
||||||
|
// but the first non-empty Load return data will be used as the session values.
|
||||||
|
type Database interface {
|
||||||
|
Load(string) map[string]interface{}
|
||||||
|
Update(string, map[string]interface{})
|
||||||
|
}
|
126
adaptors/sessions/provider.go
Normal file
126
adaptors/sessions/provider.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/kataras/iris.v6"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// provider contains the sessions and external databases (load and update).
|
||||||
|
// It's the session memory manager
|
||||||
|
provider struct {
|
||||||
|
// we don't use RWMutex because all actions have read and write at the same action function.
|
||||||
|
// (or write to a *session's value which is race if we don't lock)
|
||||||
|
// narrow locks are fasters but are useless here.
|
||||||
|
mu sync.Mutex
|
||||||
|
sessions map[string]*session
|
||||||
|
databases []Database
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// newProvider returns a new sessions provider
|
||||||
|
func newProvider() *provider {
|
||||||
|
return &provider{
|
||||||
|
sessions: make(map[string]*session, 0),
|
||||||
|
databases: make([]Database, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterDatabase adds a session database
|
||||||
|
// a session db doesn't have write access
|
||||||
|
func (p *provider) RegisterDatabase(db Database) {
|
||||||
|
p.mu.Lock() // for any case
|
||||||
|
p.databases = append(p.databases, db)
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSession returns a new session from sessionid
|
||||||
|
func (p *provider) newSession(sid string, expires time.Duration) *session {
|
||||||
|
|
||||||
|
sess := &session{
|
||||||
|
sid: sid,
|
||||||
|
provider: p,
|
||||||
|
values: p.loadSessionValues(sid),
|
||||||
|
flashes: make(map[string]*flashMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
if expires > 0 { // if not unlimited life duration and no -1 (cookie remove action is based on browser's session)
|
||||||
|
time.AfterFunc(expires, func() {
|
||||||
|
// the destroy makes the check if this session is exists then or not,
|
||||||
|
// this is used to destroy the session from the server-side also
|
||||||
|
// it's good to have here for security reasons, I didn't add it on the gc function to separate its action
|
||||||
|
p.Destroy(sid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provider) loadSessionValues(sid string) map[string]interface{} {
|
||||||
|
|
||||||
|
for i, n := 0, len(p.databases); i < n; i++ {
|
||||||
|
if dbValues := p.databases[i].Load(sid); dbValues != nil && len(dbValues) > 0 {
|
||||||
|
return dbValues // return the first non-empty from the registered stores.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values := make(map[string]interface{})
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provider) updateDatabases(sid string, newValues map[string]interface{}) {
|
||||||
|
for i, n := 0, len(p.databases); i < n; i++ {
|
||||||
|
p.databases[i].Update(sid, newValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init creates the session and returns it
|
||||||
|
func (p *provider) Init(sid string, expires time.Duration) iris.Session {
|
||||||
|
newSession := p.newSession(sid, expires)
|
||||||
|
p.mu.Lock()
|
||||||
|
p.sessions[sid] = newSession
|
||||||
|
p.mu.Unlock()
|
||||||
|
return newSession
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read returns the store which sid parameter belongs
|
||||||
|
func (p *provider) Read(sid string, expires time.Duration) iris.Session {
|
||||||
|
p.mu.Lock()
|
||||||
|
if sess, found := p.sessions[sid]; found {
|
||||||
|
sess.runFlashGC() // run the flash messages GC, new request here of existing session
|
||||||
|
p.mu.Unlock()
|
||||||
|
return sess
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
return p.Init(sid, expires) // if not found create new
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroys the session, removes all sessions and flash values,
|
||||||
|
// the session itself and updates the registered session databases,
|
||||||
|
// this called from sessionManager which removes the client's cookie also.
|
||||||
|
func (p *provider) Destroy(sid string) {
|
||||||
|
p.mu.Lock()
|
||||||
|
if sess, found := p.sessions[sid]; found {
|
||||||
|
sess.values = nil
|
||||||
|
sess.flashes = nil
|
||||||
|
delete(p.sessions, sid)
|
||||||
|
p.updateDatabases(sid, nil)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyAll removes all sessions
|
||||||
|
// from the server-side memory (and database if registered).
|
||||||
|
// Client's session cookie will still exist but it will be reseted on the next request.
|
||||||
|
func (p *provider) DestroyAll() {
|
||||||
|
p.mu.Lock()
|
||||||
|
for _, sess := range p.sessions {
|
||||||
|
delete(p.sessions, sess.ID())
|
||||||
|
p.updateDatabases(sess.ID(), nil)
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
}
|
294
adaptors/sessions/session.go
Normal file
294
adaptors/sessions/session.go
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kataras/go-errors"
|
||||||
|
"gopkg.in/kataras/iris.v6"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
|
||||||
|
// session is an 'object' which wraps the session provider with its session databases, only frontend user has access to this session object.
|
||||||
|
// implements the iris.Session interface
|
||||||
|
session struct {
|
||||||
|
sid string
|
||||||
|
values map[string]interface{} // here are the real values
|
||||||
|
// we could set the flash messages inside values but this will bring us more problems
|
||||||
|
// because of session databases and because of
|
||||||
|
// users may want to get all sessions and save them or display them
|
||||||
|
// but without temp values (flash messages) which are removed after fetching.
|
||||||
|
// so introduce a new field here.
|
||||||
|
// NOTE: flashes are not managed by third-party, only inside session struct.
|
||||||
|
flashes map[string]*flashMessage
|
||||||
|
mu sync.RWMutex
|
||||||
|
createdAt time.Time
|
||||||
|
provider *provider
|
||||||
|
}
|
||||||
|
|
||||||
|
flashMessage struct {
|
||||||
|
// if true then this flash message is removed on the flash gc
|
||||||
|
shouldRemove bool
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ iris.Session = &session{}
|
||||||
|
|
||||||
|
// ID returns the session's id
|
||||||
|
func (s *session) ID() string {
|
||||||
|
return s.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value of an entry by its key
|
||||||
|
func (s *session) Get(key string) interface{} {
|
||||||
|
s.mu.RLock()
|
||||||
|
value := s.values[key]
|
||||||
|
s.mu.RUnlock()
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// when running on the session manager removes any 'old' flash messages
|
||||||
|
func (s *session) runFlashGC() {
|
||||||
|
s.mu.Lock()
|
||||||
|
for key, v := range s.flashes {
|
||||||
|
if v.shouldRemove {
|
||||||
|
delete(s.flashes, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasFlash returns true if this request has available flash messages
|
||||||
|
func (s *session) HasFlash() bool {
|
||||||
|
return s.flashes != nil && len(s.flashes) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFlash returns a flash message which removed on the next request
|
||||||
|
//
|
||||||
|
// To check for flash messages we use the HasFlash() Method
|
||||||
|
// and to obtain the flash message we use the GetFlash() Method.
|
||||||
|
// There is also a method GetFlashes() to fetch all the messages.
|
||||||
|
//
|
||||||
|
// Fetching a message deletes it from the session.
|
||||||
|
// This means that a message is meant to be displayed only on the first page served to the user
|
||||||
|
func (s *session) GetFlash(key string) (v interface{}) {
|
||||||
|
s.mu.Lock()
|
||||||
|
if valueStorage, found := s.flashes[key]; found {
|
||||||
|
valueStorage.shouldRemove = true
|
||||||
|
v = valueStorage.value
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString same as Get but returns as string, if nil then returns an empty string
|
||||||
|
func (s *session) GetString(key string) string {
|
||||||
|
if value := s.Get(key); value != nil {
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFlashString same as GetFlash but returns as string, if nil then returns an empty string
|
||||||
|
func (s *session) GetFlashString(key string) string {
|
||||||
|
if value := s.GetFlash(key); value != nil {
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var errFindParse = errors.New("Unable to find the %s with key: %s. Found? %#v")
|
||||||
|
|
||||||
|
// GetInt same as Get but returns as int, if not found then returns -1 and an error
|
||||||
|
func (s *session) GetInt(key string) (int, error) {
|
||||||
|
v := s.Get(key)
|
||||||
|
if vint, ok := v.(int); ok {
|
||||||
|
return vint, nil
|
||||||
|
} else if vstring, sok := v.(string); sok {
|
||||||
|
return strconv.Atoi(vstring)
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, errFindParse.Format("int", key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 same as Get but returns as int64, if not found then returns -1 and an error
|
||||||
|
func (s *session) GetInt64(key string) (int64, error) {
|
||||||
|
v := s.Get(key)
|
||||||
|
if vint64, ok := v.(int64); ok {
|
||||||
|
return vint64, nil
|
||||||
|
} else if vint, ok := v.(int); ok {
|
||||||
|
return int64(vint), nil
|
||||||
|
} else if vstring, sok := v.(string); sok {
|
||||||
|
return strconv.ParseInt(vstring, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, errFindParse.Format("int64", key, v)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat32 same as Get but returns as float32, if not found then returns -1 and an error
|
||||||
|
func (s *session) GetFloat32(key string) (float32, error) {
|
||||||
|
v := s.Get(key)
|
||||||
|
if vfloat32, ok := v.(float32); ok {
|
||||||
|
return vfloat32, nil
|
||||||
|
} else if vfloat64, ok := v.(float64); ok {
|
||||||
|
return float32(vfloat64), nil
|
||||||
|
} else if vint, ok := v.(int); ok {
|
||||||
|
return float32(vint), nil
|
||||||
|
} else if vstring, sok := v.(string); sok {
|
||||||
|
vfloat64, err := strconv.ParseFloat(vstring, 32)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return float32(vfloat64), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, errFindParse.Format("float32", key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat64 same as Get but returns as float64, if not found then returns -1 and an error
|
||||||
|
func (s *session) GetFloat64(key string) (float64, error) {
|
||||||
|
v := s.Get(key)
|
||||||
|
if vfloat32, ok := v.(float32); ok {
|
||||||
|
return float64(vfloat32), nil
|
||||||
|
} else if vfloat64, ok := v.(float64); ok {
|
||||||
|
return vfloat64, nil
|
||||||
|
} else if vint, ok := v.(int); ok {
|
||||||
|
return float64(vint), nil
|
||||||
|
} else if vstring, sok := v.(string); sok {
|
||||||
|
return strconv.ParseFloat(vstring, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, errFindParse.Format("float64", key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBoolean same as Get but returns as boolean, if not found then returns -1 and an error
|
||||||
|
func (s *session) GetBoolean(key string) (bool, error) {
|
||||||
|
v := s.Get(key)
|
||||||
|
// here we could check for "true", "false" and 0 for false and 1 for true
|
||||||
|
// but this may cause unexpected behavior from the developer if they expecting an error
|
||||||
|
// so we just check if bool, if yes then return that bool, otherwise return false and an error
|
||||||
|
if vb, ok := v.(bool); ok {
|
||||||
|
return vb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, errFindParse.Format("bool", key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns a copy of all session's values
|
||||||
|
func (s *session) GetAll() map[string]interface{} {
|
||||||
|
items := make(map[string]interface{}, len(s.values))
|
||||||
|
s.mu.RLock()
|
||||||
|
for key, v := range s.values {
|
||||||
|
items[key] = v
|
||||||
|
}
|
||||||
|
s.mu.RUnlock()
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFlashes returns all flash messages as map[string](key) and interface{} value
|
||||||
|
// NOTE: this will cause at remove all current flash messages on the next request of the same user
|
||||||
|
func (s *session) GetFlashes() map[string]interface{} {
|
||||||
|
flashes := make(map[string]interface{}, len(s.flashes))
|
||||||
|
s.mu.Lock()
|
||||||
|
for key, v := range s.flashes {
|
||||||
|
flashes[key] = v.value
|
||||||
|
v.shouldRemove = true
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
return flashes
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitAll loop each one entry and calls the callback function func(key,value)
|
||||||
|
func (s *session) VisitAll(cb func(k string, v interface{})) {
|
||||||
|
for key := range s.values {
|
||||||
|
cb(key, s.values[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set fills the session with an entry, it receives a key and a value
|
||||||
|
// returns an error, which is always nil
|
||||||
|
func (s *session) Set(key string, value interface{}) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.values[key] = value
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
s.updateDatabases()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFlash sets a flash message by its key.
|
||||||
|
//
|
||||||
|
// A flash message is used in order to keep a message in session through one or several requests of the same user.
|
||||||
|
// It is removed from session after it has been displayed to the user.
|
||||||
|
// Flash messages are usually used in combination with HTTP redirections,
|
||||||
|
// because in this case there is no view, so messages can only be displayed in the request that follows redirection.
|
||||||
|
//
|
||||||
|
// A flash message has a name and a content (AKA key and value).
|
||||||
|
// It is an entry of an associative array. The name is a string: often "notice", "success", or "error", but it can be anything.
|
||||||
|
// The content is usually a string. You can put HTML tags in your message if you display it raw.
|
||||||
|
// You can also set the message value to a number or an array: it will be serialized and kept in session like a string.
|
||||||
|
//
|
||||||
|
// Flash messages can be set using the SetFlash() Method
|
||||||
|
// For example, if you would like to inform the user that his changes were successfully saved,
|
||||||
|
// you could add the following line to your Handler:
|
||||||
|
//
|
||||||
|
// SetFlash("success", "Data saved!");
|
||||||
|
//
|
||||||
|
// In this example we used the key 'success'.
|
||||||
|
// If you want to define more than one flash messages, you will have to use different keys
|
||||||
|
func (s *session) SetFlash(key string, value interface{}) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.flashes[key] = &flashMessage{value: value}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes an entry by its key
|
||||||
|
func (s *session) Delete(key string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
delete(s.values, key)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
s.updateDatabases()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) updateDatabases() {
|
||||||
|
s.provider.updateDatabases(s.sid, s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFlash removes a flash message by its key
|
||||||
|
func (s *session) DeleteFlash(key string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
delete(s.flashes, key)
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all entries
|
||||||
|
func (s *session) Clear() {
|
||||||
|
s.mu.Lock()
|
||||||
|
for key := range s.values {
|
||||||
|
delete(s.values, key)
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
s.updateDatabases()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all flash messages
|
||||||
|
func (s *session) ClearFlashes() {
|
||||||
|
s.mu.Lock()
|
||||||
|
for key := range s.flashes {
|
||||||
|
delete(s.flashes, key)
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
29
adaptors/sessions/sessiondb/README.md
Normal file
29
adaptors/sessions/sessiondb/README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
## Session databases
|
||||||
|
|
||||||
|
Find more databases at [github.com/kataras/go-sessions/sessiondb](https://github.com/kataras/go-sessions/tree/master/sessiondb).
|
||||||
|
|
||||||
|
This folder contains only the redis database because the rest (two so far, 'file' and 'leveldb') were created by the Community.
|
||||||
|
So go [there](https://github.com/kataras/go-sessions/tree/master/sessiondb) and find more about them. `Database` is just an
|
||||||
|
interface so you're able to `UseDatabase(anyCompatibleDatabase)`. A Database should implement two functions, `Load` and `Update`.
|
||||||
|
|
||||||
|
**Database interface**
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Database interface {
|
||||||
|
Load(string) map[string]interface{}
|
||||||
|
Update(string, map[string]interface{})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"...myDatabase"
|
||||||
|
)
|
||||||
|
s := New(...)
|
||||||
|
s.UseDatabase(myDatabase) // <---
|
||||||
|
|
||||||
|
app := iris.New()
|
||||||
|
app.Adapt(s)
|
||||||
|
|
||||||
|
app.Listen(":8080")
|
||||||
|
```
|
85
adaptors/sessions/sessiondb/redis/database.go
Normal file
85
adaptors/sessions/sessiondb/redis/database.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
|
||||||
|
"gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database the redis database for q sessions
|
||||||
|
type Database struct {
|
||||||
|
redis *service.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new redis database
|
||||||
|
func New(cfg ...service.Config) *Database {
|
||||||
|
return &Database{redis: service.New(cfg...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config returns the configuration for the redis server bridge, you can change them
|
||||||
|
func (d *Database) Config() *service.Config {
|
||||||
|
return d.redis.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads the values to the underline
|
||||||
|
func (d *Database) Load(sid string) map[string]interface{} {
|
||||||
|
values := make(map[string]interface{})
|
||||||
|
|
||||||
|
if !d.redis.Connected { //yes, check every first time's session for valid redis connection
|
||||||
|
d.redis.Connect()
|
||||||
|
_, err := d.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 Connect: " + err.Error())
|
||||||
|
println("But don't panic, auto-switching to memory store right now!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//fetch the values from this session id and copy-> store them
|
||||||
|
val, err := d.redis.GetBytes(sid)
|
||||||
|
if err == nil {
|
||||||
|
err = DeserializeBytes(val, &values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize the values to be stored as strings inside the Redis, we panic at any serialization error here
|
||||||
|
func serialize(values map[string]interface{}) []byte {
|
||||||
|
val, err := SerializeBytes(values)
|
||||||
|
if err != nil {
|
||||||
|
println("On redisstore.serialize: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the real redis store
|
||||||
|
func (d *Database) Update(sid string, newValues map[string]interface{}) {
|
||||||
|
if len(newValues) == 0 {
|
||||||
|
go d.redis.Delete(sid)
|
||||||
|
} else {
|
||||||
|
go d.redis.Set(sid, serialize(newValues)) //set/update all the values
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeBytes serializa bytes using gob encoder and returns them
|
||||||
|
func SerializeBytes(m interface{}) ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
enc := gob.NewEncoder(buf)
|
||||||
|
err := enc.Encode(m)
|
||||||
|
if err == nil {
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeserializeBytes converts the bytes to an object using gob decoder
|
||||||
|
func DeserializeBytes(b []byte, m interface{}) error {
|
||||||
|
dec := gob.NewDecoder(bytes.NewBuffer(b))
|
||||||
|
return dec.Decode(m) //no reference here otherwise doesn't work because of go remote object
|
||||||
|
}
|
78
adaptors/sessions/sessiondb/redis/service/config.go
Normal file
78
adaptors/sessions/sessiondb/redis/service/config.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
// DefaultRedisMaxAgeSeconds the redis storage last parameter (SETEX), 31556926.0 (1 year)
|
||||||
|
DefaultRedisMaxAgeSeconds = 31556926.0 //1 year
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config the redis configuration used inside sessions
|
||||||
|
type Config struct {
|
||||||
|
// Network "tcp"
|
||||||
|
Network string
|
||||||
|
// Addr "127.0.0.1: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 time.Duration(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 31556926.0 (1 year)
|
||||||
|
MaxAgeSeconds int
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: "",
|
||||||
|
MaxAgeSeconds: DefaultRedisMaxAgeSeconds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges the default with the given config and returns the result
|
||||||
|
func (c Config) Merge(cfg []Config) (config Config) {
|
||||||
|
|
||||||
|
if len(cfg) > 0 {
|
||||||
|
config = cfg[0]
|
||||||
|
mergo.Merge(&config, c)
|
||||||
|
} else {
|
||||||
|
_default := c
|
||||||
|
config = _default
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeSingle merges the default with the given config and returns the result
|
||||||
|
func (c Config) MergeSingle(cfg Config) (config Config) {
|
||||||
|
|
||||||
|
config = cfg
|
||||||
|
mergo.Merge(&config, c)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
272
adaptors/sessions/sessiondb/redis/service/service.go
Normal file
272
adaptors/sessions/sessiondb/redis/service/service.go
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
"github.com/kataras/go-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 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) (err error) { // map[interface{}]interface{}) (err error) {
|
||||||
|
c := r.pool.Get()
|
||||||
|
defer c.Close()
|
||||||
|
if err = c.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = c.Do("SETEX", r.Config.Prefix+key, r.Config.MaxAgeSeconds, 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 = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.MaxAgeSeconds <= 0 {
|
||||||
|
c.MaxAgeSeconds = DefaultRedisMaxAgeSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
pool := &redis.Pool{IdleTimeout: 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) *Service {
|
||||||
|
c := DefaultConfig().Merge(cfg)
|
||||||
|
r := &Service{pool: &redis.Pool{}, Config: &c}
|
||||||
|
return r
|
||||||
|
}
|
188
adaptors/sessions/sessions.go
Normal file
188
adaptors/sessions/sessions.go
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
// Package sessions as originally written by me at https://github.com/kataras/go-sessions
|
||||||
|
// Based on kataras/go-sessions v1.0.0.
|
||||||
|
//
|
||||||
|
// Edited for Iris v6 (or iris vNext) and removed all fasthttp things in order to reduce the
|
||||||
|
// compiled and go getable size. The 'file' and 'leveldb' databases are missing
|
||||||
|
// because they written by community, not me, you can still adapt any database with
|
||||||
|
// .UseDatabase because it expects an interface,
|
||||||
|
// find more databases here: https://github.com/kataras/go-sessions/tree/master/sessiondb
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/kataras/iris.v6"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Sessions is the start point of this package
|
||||||
|
// contains all the registered sessions and manages them
|
||||||
|
Sessions interface {
|
||||||
|
// Adapt is used to adapt this sessions manager as an iris.SessionsPolicy
|
||||||
|
// to an Iris station.
|
||||||
|
// It's being used by the framework, developers should not actually call this function.
|
||||||
|
Adapt(*iris.Policies)
|
||||||
|
|
||||||
|
// UseDatabase ,optionally, adds a session database to the manager's provider,
|
||||||
|
// a session db doesn't have write access
|
||||||
|
// see https://github.com/kataras/go-sessions/tree/master/sessiondb
|
||||||
|
UseDatabase(Database)
|
||||||
|
|
||||||
|
// Start starts the session for the particular net/http request
|
||||||
|
Start(http.ResponseWriter, *http.Request) iris.Session
|
||||||
|
|
||||||
|
// Destroy kills the net/http session and remove the associated cookie
|
||||||
|
Destroy(http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
|
// DestroyByID removes the session entry
|
||||||
|
// from the server-side memory (and database if registered).
|
||||||
|
// Client's session cookie will still exist but it will be reseted on the next request.
|
||||||
|
//
|
||||||
|
// It's safe to use it even if you are not sure if a session with that id exists.
|
||||||
|
DestroyByID(string)
|
||||||
|
// DestroyAll removes all sessions
|
||||||
|
// from the server-side memory (and database if registered).
|
||||||
|
// Client's session cookie will still exist but it will be reseted on the next request.
|
||||||
|
DestroyAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessions contains the cookie's name, the provider and a duration for GC and cookie life expire
|
||||||
|
sessions struct {
|
||||||
|
config Config
|
||||||
|
provider *provider
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a new fast, feature-rich sessions manager
|
||||||
|
// it can be adapted to an Iris station
|
||||||
|
func New(cfg Config) Sessions {
|
||||||
|
return &sessions{
|
||||||
|
config: cfg.Validate(),
|
||||||
|
provider: newProvider(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sessions) Adapt(frame *iris.Policies) {
|
||||||
|
// for newcomers this maybe looks strange:
|
||||||
|
// Each policy is an adaptor too, so they all can contain an Adapt.
|
||||||
|
// If they contains an Adapt func then the policy is an adaptor too and this Adapt func is called
|
||||||
|
// by Iris on .Adapt(...)
|
||||||
|
policy := iris.SessionsPolicy{
|
||||||
|
Start: s.Start,
|
||||||
|
Destroy: s.Destroy,
|
||||||
|
}
|
||||||
|
|
||||||
|
policy.Adapt(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseDatabase adds a session database to the manager's provider,
|
||||||
|
// a session db doesn't have write access
|
||||||
|
func (s *sessions) UseDatabase(db Database) {
|
||||||
|
s.provider.RegisterDatabase(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the session for the particular net/http request
|
||||||
|
func (s *sessions) Start(res http.ResponseWriter, req *http.Request) iris.Session {
|
||||||
|
var sess iris.Session
|
||||||
|
|
||||||
|
cookieValue := GetCookie(s.config.Cookie, req)
|
||||||
|
if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
|
||||||
|
sid := SessionIDGenerator(s.config.CookieLength)
|
||||||
|
sess = s.provider.Init(sid, s.config.Expires)
|
||||||
|
cookie := &http.Cookie{}
|
||||||
|
|
||||||
|
// 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.Name = s.config.Cookie
|
||||||
|
cookie.Value = sid
|
||||||
|
cookie.Path = "/"
|
||||||
|
if !s.config.DisableSubdomainPersistence {
|
||||||
|
|
||||||
|
requestDomain := req.URL.Host
|
||||||
|
if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
|
||||||
|
requestDomain = requestDomain[0:portIdx]
|
||||||
|
}
|
||||||
|
if IsValidCookieDomain(requestDomain) {
|
||||||
|
|
||||||
|
// RFC2109, we allow level 1 subdomains, but no further
|
||||||
|
// if we have localhost.com , we want the localhost.cos.
|
||||||
|
// 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, '.'); 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, '.'); 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, '.')
|
||||||
|
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.Domain = "." + requestDomain // . to allow persistance
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
cookie.HttpOnly = true
|
||||||
|
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||||
|
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
|
||||||
|
// MaxAge>0 means Max-Age attribute present and given in seconds
|
||||||
|
if s.config.Expires >= 0 {
|
||||||
|
if s.config.Expires == 0 { // unlimited life
|
||||||
|
cookie.Expires = CookieExpireUnlimited
|
||||||
|
} else { // > 0
|
||||||
|
cookie.Expires = time.Now().Add(s.config.Expires)
|
||||||
|
}
|
||||||
|
cookie.MaxAge = int(cookie.Expires.Sub(time.Now()).Seconds())
|
||||||
|
} else {
|
||||||
|
// if it's -1 then the cookie is deleted when the browser closes
|
||||||
|
// so MaxAge = -1
|
||||||
|
cookie.MaxAge = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
AddCookie(cookie, res)
|
||||||
|
} else {
|
||||||
|
sess = s.provider.Read(cookieValue, s.config.Expires)
|
||||||
|
}
|
||||||
|
return sess
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy kills the net/http session and remove the associated cookie
|
||||||
|
func (s *sessions) Destroy(res http.ResponseWriter, req *http.Request) {
|
||||||
|
cookieValue := GetCookie(s.config.Cookie, req)
|
||||||
|
if cookieValue == "" { // nothing to destroy
|
||||||
|
return
|
||||||
|
}
|
||||||
|
RemoveCookie(s.config.Cookie, res, req)
|
||||||
|
s.provider.Destroy(cookieValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyByID removes the session entry
|
||||||
|
// from the server-side memory (and database if registered).
|
||||||
|
// Client's session cookie will still exist but it will be reseted on the next request.
|
||||||
|
//
|
||||||
|
// It's safe to use it even if you are not sure if a session with that id exists.
|
||||||
|
// Works for both net/http
|
||||||
|
func (s *sessions) DestroyByID(sid string) {
|
||||||
|
s.provider.Destroy(sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyAll removes all sessions
|
||||||
|
// from the server-side memory (and database if registered).
|
||||||
|
// Client's session cookie will still exist but it will be reseted on the next request.
|
||||||
|
// Works for both net/http
|
||||||
|
func (s *sessions) DestroyAll() {
|
||||||
|
s.provider.DestroyAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionIDGenerator returns a random string, used to set the session id
|
||||||
|
// you are able to override this to use your own method for generate session ids
|
||||||
|
var SessionIDGenerator = func(strLength int) string {
|
||||||
|
return base64.URLEncoding.EncodeToString(random(strLength))
|
||||||
|
}
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Package typescript provides a typescript compiler with hot-reloader
|
||||||
|
// and optionally a cloud-based editor, called 'alm-tools'.
|
||||||
|
// typescript (by microsoft) and alm-tools (by basarat) have their own (open-source) licenses
|
||||||
|
// the tools are not used directly by this adaptor, but it's good to know where you can find
|
||||||
|
// the software.
|
||||||
package typescript
|
package typescript
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Package view is the adaptor of the 5 template engines
|
||||||
|
// as written by me at https://github.com/kataras/go-template
|
||||||
package view
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Package websocket provides an easy way to setup server and client side rich websocket experience for Iris
|
// Package websocket provides an easy way to setup server and client side rich websocket experience for Iris
|
||||||
|
// As originally written by me at https://github.com/kataras/go-websocket
|
||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/kataras/go-options"
|
"github.com/kataras/go-options"
|
||||||
"github.com/kataras/go-sessions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -174,9 +173,6 @@ type Configuration struct {
|
||||||
// Defaults to false
|
// Defaults to false
|
||||||
Gzip bool
|
Gzip bool
|
||||||
|
|
||||||
// Sessions contains the configs for sessions
|
|
||||||
Sessions SessionsConfiguration
|
|
||||||
|
|
||||||
// Other are the custom, dynamic options, can be empty
|
// Other are the custom, dynamic options, can be empty
|
||||||
// this fill used only by you to set any app's options you want
|
// this fill used only by you to set any app's options you want
|
||||||
// for each of an Iris instance
|
// for each of an Iris instance
|
||||||
|
@ -439,92 +435,10 @@ func DefaultConfiguration() Configuration {
|
||||||
TimeFormat: DefaultTimeFormat,
|
TimeFormat: DefaultTimeFormat,
|
||||||
Charset: DefaultCharset,
|
Charset: DefaultCharset,
|
||||||
Gzip: false,
|
Gzip: false,
|
||||||
Sessions: DefaultSessionsConfiguration(),
|
|
||||||
Other: options.Options{},
|
Other: options.Options{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionsConfiguration the configuration for sessions
|
|
||||||
// has 6 fields
|
|
||||||
// first is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
|
|
||||||
// second enable if you want to decode the cookie's key also
|
|
||||||
// third is the time which the client's cookie expires
|
|
||||||
// forth is the cookie length (sessionid) int, Defaults to 32, do not change if you don't have any reason to do
|
|
||||||
// fifth is the gcDuration (time.Duration) when this time passes it removes the unused sessions from the memory until the user come back
|
|
||||||
// sixth is the DisableSubdomainPersistence which you can set it to true in order dissallow your q subdomains to have access to the session cook
|
|
||||||
type SessionsConfiguration sessions.Config
|
|
||||||
|
|
||||||
// Set implements the OptionSetter of the sessions package
|
|
||||||
func (s SessionsConfiguration) Set(c *sessions.Config) {
|
|
||||||
*c = sessions.Config(s).Validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// OptionSessionsCookie string, the session's client cookie name, for example: "qsessionid"
|
|
||||||
OptionSessionsCookie = func(val string) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.Sessions.Cookie = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionSessionsDecodeCookie set it to true to decode the cookie key with base64 URLEncoding
|
|
||||||
// Defaults to false
|
|
||||||
OptionSessionsDecodeCookie = func(val bool) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.Sessions.DecodeCookie = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionSessionsExpires the duration of which the cookie must expires (created_time.Add(Expires)).
|
|
||||||
// If you want to delete the cookie when the browser closes, set it to -1 but in this case, the server side's session duration is up to GcDuration
|
|
||||||
//
|
|
||||||
// Default infinitive/unlimited life duration(0)
|
|
||||||
OptionSessionsExpires = func(val time.Duration) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.Sessions.Expires = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionSessionsCookieLength the length of the sessionid's cookie's value, let it to 0 if you don't want to change it
|
|
||||||
// Defaults to 32
|
|
||||||
OptionSessionsCookieLength = func(val int) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.Sessions.CookieLength = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionSessionsDisableSubdomainPersistence set it to true in order dissallow your q subdomains to have access to the session cookie
|
|
||||||
// Defaults to false
|
|
||||||
OptionSessionsDisableSubdomainPersistence = func(val bool) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.Sessions.DisableSubdomainPersistence = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// CookieExpireNever the default cookie's life for sessions, unlimited (23 years)
|
|
||||||
CookieExpireNever = time.Now().AddDate(23, 0, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultCookieName the secret cookie's name for sessions
|
|
||||||
DefaultCookieName = "irissessionid"
|
|
||||||
// DefaultCookieLength is the default Session Manager's CookieLength, which is 32
|
|
||||||
DefaultCookieLength = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultSessionsConfiguration the default configs for Sessions
|
|
||||||
func DefaultSessionsConfiguration() SessionsConfiguration {
|
|
||||||
return SessionsConfiguration{
|
|
||||||
Cookie: DefaultCookieName,
|
|
||||||
CookieLength: DefaultCookieLength,
|
|
||||||
DecodeCookie: false,
|
|
||||||
Expires: 0,
|
|
||||||
DisableSubdomainPersistence: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default values for base Server conf
|
// Default values for base Server conf
|
||||||
const (
|
const (
|
||||||
// DefaultServerHostname returns the default hostname which is 0.0.0.0
|
// DefaultServerHostname returns the default hostname which is 0.0.0.0
|
||||||
|
|
73
context.go
73
context.go
|
@ -23,7 +23,6 @@ import (
|
||||||
"github.com/iris-contrib/formBinder"
|
"github.com/iris-contrib/formBinder"
|
||||||
"github.com/kataras/go-errors"
|
"github.com/kataras/go-errors"
|
||||||
"github.com/kataras/go-fs"
|
"github.com/kataras/go-fs"
|
||||||
"github.com/kataras/go-sessions"
|
|
||||||
"github.com/kataras/go-template"
|
"github.com/kataras/go-template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -215,7 +214,7 @@ type (
|
||||||
framework *Framework
|
framework *Framework
|
||||||
//keep track all registered middleware (handlers)
|
//keep track all registered middleware (handlers)
|
||||||
Middleware Middleware // exported because is useful for debugging
|
Middleware Middleware // exported because is useful for debugging
|
||||||
session sessions.Session
|
session Session
|
||||||
// Pos is the position number of the Context, look .Next to understand
|
// Pos is the position number of the Context, look .Next to understand
|
||||||
Pos int // exported because is useful for debugging
|
Pos int // exported because is useful for debugging
|
||||||
}
|
}
|
||||||
|
@ -1484,22 +1483,82 @@ func (ctx *Context) RemoveCookie(name string) {
|
||||||
ctx.Request.Header.Set("Cookie", "")
|
ctx.Request.Header.Set("Cookie", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session returns the current session ( && flash messages )
|
var errSessionsPolicyIsMissing = errors.New(
|
||||||
func (ctx *Context) Session() sessions.Session {
|
`
|
||||||
if ctx.framework.sessions == nil { // this should never return nil but FOR ANY CASE, on future changes.
|
manually call of context.Session() for client IP: '%s' without specified SessionsPolicy!
|
||||||
|
Please .Adapt one of the available session managers inside 'kataras/iris/adaptors'.
|
||||||
|
|
||||||
|
Edit your main .go source file to adapt one of these and restart your app.
|
||||||
|
i.e: lines (<---) were missing.
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris"
|
||||||
|
"github.com/kataras/iris/adaptors/httprouter" // or gorillamux
|
||||||
|
"github.com/kataras/iris/adaptors/sessions" // <--- this line
|
||||||
|
)
|
||||||
|
|
||||||
|
func main(){
|
||||||
|
app := iris.New()
|
||||||
|
// right below the iris.New()
|
||||||
|
app.Adapt(httprouter.New()) // or gorillamux.New()
|
||||||
|
|
||||||
|
mySessions := sessions.New(sessions.Config{
|
||||||
|
// Cookie string, the session's client cookie name, for example: "mysessionid"
|
||||||
|
//
|
||||||
|
// Defaults to "gosessionid"
|
||||||
|
Cookie: "mysessionid",
|
||||||
|
// base64 urlencoding,
|
||||||
|
// if you have strange name cookie name enable this
|
||||||
|
DecodeCookie: false,
|
||||||
|
// it's time.Duration, from the time cookie is created, how long it can be alive?
|
||||||
|
// 0 means no expire.
|
||||||
|
Expires: 0,
|
||||||
|
// the length of the sessionid's cookie's value
|
||||||
|
CookieLength: 32,
|
||||||
|
// if you want to invalid cookies on different subdomains
|
||||||
|
// of the same host, then enable it
|
||||||
|
DisableSubdomainPersistence: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// OPTIONALLY:
|
||||||
|
// import "gopkg.in/kataras/iris.v6/adaptors/sessions/sessiondb/redis"
|
||||||
|
// or import "github.com/kataras/go-sessions/sessiondb/$any_available_community_database"
|
||||||
|
// mySessions.UseDatabase(redis.New(...))
|
||||||
|
|
||||||
|
app.Adapt(mySessions) // <--- and this line were missing.
|
||||||
|
|
||||||
|
// the rest of your source code...
|
||||||
|
// ...
|
||||||
|
|
||||||
|
app.Listen("%s")
|
||||||
|
}
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Session returns the current Session.
|
||||||
|
//
|
||||||
|
// if SessionsPolicy is missing then a detailed how-to-fix message
|
||||||
|
// will be visible to the user (DevMode)
|
||||||
|
// and the return value will be NILL.
|
||||||
|
func (ctx *Context) Session() Session {
|
||||||
|
policy := ctx.framework.policies.SessionsPolicy
|
||||||
|
if policy.Start == nil {
|
||||||
|
ctx.framework.Log(DevMode,
|
||||||
|
errSessionsPolicyIsMissing.Format(ctx.RemoteAddr(), ctx.framework.Config.VHost).Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.session == nil {
|
if ctx.session == nil {
|
||||||
ctx.session = ctx.framework.sessions.Start(ctx.ResponseWriter, ctx.Request)
|
ctx.session = policy.Start(ctx.ResponseWriter, ctx.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.session
|
return ctx.session
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionDestroy destroys the whole session, calls the provider's destroy and remove the cookie
|
// SessionDestroy destroys the whole session, calls the provider's destroy and remove the cookie
|
||||||
func (ctx *Context) SessionDestroy() {
|
func (ctx *Context) SessionDestroy() {
|
||||||
if sess := ctx.Session(); sess != nil {
|
if sess := ctx.Session(); sess != nil {
|
||||||
ctx.framework.sessions.Destroy(ctx.ResponseWriter, ctx.Request)
|
ctx.framework.policies.SessionsPolicy.Destroy(ctx.ResponseWriter, ctx.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
50
iris.go
50
iris.go
|
@ -26,7 +26,6 @@ import (
|
||||||
"github.com/kataras/go-errors"
|
"github.com/kataras/go-errors"
|
||||||
"github.com/kataras/go-fs"
|
"github.com/kataras/go-fs"
|
||||||
"github.com/kataras/go-serializer"
|
"github.com/kataras/go-serializer"
|
||||||
"github.com/kataras/go-sessions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -72,7 +71,6 @@ type Framework struct {
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
Config *Configuration
|
Config *Configuration
|
||||||
sessions sessions.Sessions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultGlobalLoggerOuput = log.New(os.Stdout, "[iris] ", log.LstdFlags)
|
var defaultGlobalLoggerOuput = log.New(os.Stdout, "[iris] ", log.LstdFlags)
|
||||||
|
@ -236,22 +234,6 @@ func New(setters ...OptionSetter) *Framework {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
// +------------------------------------------------------------+
|
|
||||||
// | Module Name: Sessions |
|
|
||||||
// | On Init: Attach a session manager with empty config |
|
|
||||||
// | On Build: Set the configuration if allowed |
|
|
||||||
// +------------------------------------------------------------+
|
|
||||||
|
|
||||||
// set the sessions in order to UseSessionDB to work
|
|
||||||
s.sessions = sessions.New()
|
|
||||||
// On Build:
|
|
||||||
s.Adapt(EventPolicy{Build: func(*Framework) {
|
|
||||||
// re-set the configuration field to update users configuration
|
|
||||||
s.sessions.Set(s.Config.Sessions)
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// +------------------------------------------------------------+
|
// +------------------------------------------------------------+
|
||||||
// | Module Name: Router |
|
// | Module Name: Router |
|
||||||
|
@ -645,32 +627,6 @@ func (s *Framework) Adapt(policies ...Policy) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseSessionDB registers a session database, you can register more than one
|
|
||||||
// accepts a session database which implements a Load(sid string) map[string]interface{} and an Update(sid string, newValues map[string]interface{})
|
|
||||||
// the only reason that a session database will be useful for you is when you want to keep the session's values/data after the app restart
|
|
||||||
// a session database doesn't have write access to the session, it doesn't accept the context, so forget 'cookie database' for sessions, I will never allow that, for your protection.
|
|
||||||
//
|
|
||||||
// Note: Don't worry if no session database is registered, your context.Session will continue to work.
|
|
||||||
func (s *Framework) UseSessionDB(db sessions.Database) {
|
|
||||||
s.sessions.UseDatabase(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DestroySessionByID removes the session entry
|
|
||||||
// from the server-side memory (and database if registered).
|
|
||||||
// Client's session cookie will still exist but it will be reseted on the next request.
|
|
||||||
//
|
|
||||||
// It's safe to use it even if you are not sure if a session with that id exists.
|
|
||||||
func (s *Framework) DestroySessionByID(sid string) {
|
|
||||||
s.sessions.DestroyByID(sid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DestroyAllSessions removes all sessions
|
|
||||||
// from the server-side memory (and database if registered).
|
|
||||||
// Client's session cookie will still exist but it will be reseted on the next request.
|
|
||||||
func (s *Framework) DestroyAllSessions() {
|
|
||||||
s.sessions.DestroyAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// cachedMuxEntry is just a wrapper for the Cache functionality
|
// cachedMuxEntry is just a wrapper for the Cache functionality
|
||||||
// it seems useless but I prefer to keep the cached handler on its own memory stack,
|
// it seems useless but I prefer to keep the cached handler on its own memory stack,
|
||||||
// reason: no clojures hell in the Cache function
|
// reason: no clojures hell in the Cache function
|
||||||
|
@ -831,10 +787,14 @@ Edit your main .go source file to adapt one of these and restart your app.
|
||||||
|
|
||||||
func main(){
|
func main(){
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
// right below the iris.New():
|
||||||
app.Adapt(httprouter.New()) // or gorillamux.New()
|
app.Adapt(httprouter.New()) // or gorillamux.New()
|
||||||
// right below the iris.New()
|
|
||||||
app.Adapt(view.HTML("./templates", ".html")) // <--- and this line were missing.
|
app.Adapt(view.HTML("./templates", ".html")) // <--- and this line were missing.
|
||||||
|
|
||||||
|
// the rest of your source code...
|
||||||
|
// ...
|
||||||
|
|
||||||
app.Listen("%s")
|
app.Listen("%s")
|
||||||
}
|
}
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
|
|
95
policy.go
95
policy.go
|
@ -31,6 +31,7 @@ type (
|
||||||
RouterWrapperPolicy
|
RouterWrapperPolicy
|
||||||
RenderPolicy
|
RenderPolicy
|
||||||
TemplateFuncsPolicy
|
TemplateFuncsPolicy
|
||||||
|
SessionsPolicy
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,6 +72,8 @@ func (p Policies) Adapt(frame *Policies) {
|
||||||
p.TemplateFuncsPolicy.Adapt(frame)
|
p.TemplateFuncsPolicy.Adapt(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.SessionsPolicy.Adapt(frame)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogMode is the type for the LoggerPolicy write mode.
|
// LogMode is the type for the LoggerPolicy write mode.
|
||||||
|
@ -430,3 +433,95 @@ func (t TemplateFuncsPolicy) Adapt(frame *Policies) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Author's notes:
|
||||||
|
// session manager can work as a middleware too
|
||||||
|
// but we want an easy-api for the user
|
||||||
|
// as we did before with: context.Session().Set/Get...
|
||||||
|
// these things cannot be done with middleware and sessions is a critical part of an application
|
||||||
|
// which needs attention, so far we used the kataras/go-sessions which I spent many weeks to create
|
||||||
|
// and that time has not any known bugs or any other issues, it's fully featured.
|
||||||
|
// BUT user may want to use other session library and in the same time users should be able to use
|
||||||
|
// iris' api for sessions from context, so a policy is that we need, the policy will contains
|
||||||
|
// the Start(responsewriter, request) and the Destroy(responsewriter, request)
|
||||||
|
// (keep note that this Destroy is not called at the end of a handler, Start does its job without need to end something
|
||||||
|
// sessions are setting in real time, when the user calls .Set ),
|
||||||
|
// the Start(responsewriter, request) will return a 'Session' which will contain the API for context.Session() , it should be
|
||||||
|
// rich, as before, so the interface will be a clone of the kataras/go-sessions/Session.
|
||||||
|
// If the user wants to use other library and that library missing features that kataras/go-sesisons has
|
||||||
|
// then the user should make an empty implementation of these calls in order to work.
|
||||||
|
// That's no problem, before they couldn't adapt any session manager, now they will can.
|
||||||
|
//
|
||||||
|
// The databases or stores registration will be in the session manager's responsibility,
|
||||||
|
// as well the DestroyByID and DestroyAll (I'm calling these with these names because
|
||||||
|
// I take as base the kataras/go-sessions,
|
||||||
|
// I have no idea if other session managers
|
||||||
|
// supports these things, if not then no problem,
|
||||||
|
// these funcs will be not required by the sessions policy)
|
||||||
|
//
|
||||||
|
// ok let's begin.
|
||||||
|
|
||||||
|
// Session should expose the SessionsPolicy's end-user API.
|
||||||
|
// This will be returned at the sess := context.Session().
|
||||||
|
Session interface {
|
||||||
|
ID() string
|
||||||
|
Get(string) interface{}
|
||||||
|
HasFlash() bool
|
||||||
|
GetFlash(string) interface{}
|
||||||
|
GetString(key string) string
|
||||||
|
GetFlashString(string) string
|
||||||
|
GetInt(key string) (int, error)
|
||||||
|
GetInt64(key string) (int64, error)
|
||||||
|
GetFloat32(key string) (float32, error)
|
||||||
|
GetFloat64(key string) (float64, error)
|
||||||
|
GetBoolean(key string) (bool, error)
|
||||||
|
GetAll() map[string]interface{}
|
||||||
|
GetFlashes() map[string]interface{}
|
||||||
|
VisitAll(cb func(k string, v interface{}))
|
||||||
|
Set(string, interface{})
|
||||||
|
SetFlash(string, interface{})
|
||||||
|
Delete(string)
|
||||||
|
DeleteFlash(string)
|
||||||
|
Clear()
|
||||||
|
ClearFlashes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionsPolicy is the policy for a session manager.
|
||||||
|
//
|
||||||
|
// A SessionsPolicy should be responsible to Start a sesion based
|
||||||
|
// on raw http.ResponseWriter and http.Request, which should return
|
||||||
|
// a compatible iris.Session interface, type. If the external session manager
|
||||||
|
// doesn't qualifies, then the user should code the rest of the functions with empty implementation.
|
||||||
|
//
|
||||||
|
// A SessionsPolicy should be responsible to Destory a session based
|
||||||
|
// on the http.ResponseWriter and http.Request, this function should works individually.
|
||||||
|
//
|
||||||
|
// No iris.Context required from users. In order to be able to adapt any external session manager.
|
||||||
|
//
|
||||||
|
// The SessionsPolicy should be adapted once.
|
||||||
|
SessionsPolicy struct {
|
||||||
|
// Start should starts the session for the particular net/http request
|
||||||
|
Start func(http.ResponseWriter, *http.Request) Session
|
||||||
|
|
||||||
|
// Destroy should kills the net/http session and remove the associated cookie
|
||||||
|
// Keep note that: Destroy should not called at the end of any handler, it's an independent func.
|
||||||
|
// Start should set
|
||||||
|
// the values at realtime and if manager doesn't supports these
|
||||||
|
// then the user manually have to call its 'done' func inside the handler.
|
||||||
|
Destroy func(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Adapt adaps a SessionsPolicy object to the main *Policies.
|
||||||
|
//
|
||||||
|
// Remember: Each policy is an adaptor.
|
||||||
|
// An adaptor should contains one or more policies too.
|
||||||
|
func (s SessionsPolicy) Adapt(frame *Policies) {
|
||||||
|
if s.Start != nil {
|
||||||
|
frame.SessionsPolicy.Start = s.Start
|
||||||
|
}
|
||||||
|
if s.Destroy != nil {
|
||||||
|
frame.SessionsPolicy.Destroy = s.Destroy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -80,6 +80,9 @@ Edit your main .go source file to adapt one of these routers and restart your ap
|
||||||
// right below the iris.New()
|
// right below the iris.New()
|
||||||
app.Adapt(httprouter.New()) // <--- and this line were missing.
|
app.Adapt(httprouter.New()) // <--- and this line were missing.
|
||||||
|
|
||||||
|
// the rest of your source code...
|
||||||
|
// ...
|
||||||
|
|
||||||
app.Listen("%s")
|
app.Listen("%s")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user