mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
Update to 4.1.4. Users & Devs can ignore this update. Read HISTORY.md for more
This commit is contained in:
parent
c38a9b2459
commit
b8b52ad46e
|
@ -2,6 +2,15 @@
|
||||||
|
|
||||||
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras/iris` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
||||||
|
|
||||||
|
## 4.1.3 -> 4.1.4
|
||||||
|
|
||||||
|
Zero front-end changes. No real improvements, developers can ignore this update.
|
||||||
|
|
||||||
|
- Replace the iris sessions with a new cross-framework package, [go-sessions](https://github.com/kataras/go-sessions). Same front-end API, sessions examples are compatible, configuration of `kataras/iris/config/sessions.go` is compatible. `kataras/context.SessionStore` is now `kataras/go-sessions.Session` (normally you, as user, never used it before, because of automatically session getting by `context.Session()`)
|
||||||
|
|
||||||
|
- `GzipWriter` is taken, now, from the `kataras/go-fs` package which has improvements versus the previous implementation.
|
||||||
|
|
||||||
|
|
||||||
## 4.1.2 -> 4.1.3
|
## 4.1.2 -> 4.1.3
|
||||||
|
|
||||||
Zero front-end changes. No real improvements, developers can ignore this update.
|
Zero front-end changes. No real improvements, developers can ignore this update.
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<a href="https://github.com/kataras/iris/blob/master/LICENSE"><img src="https://img.shields.io/badge/%20license-MIT%20%20License%20-E91E63.svg?style=flat-square" alt="License"></a>
|
<a href="https://github.com/kataras/iris/blob/master/LICENSE"><img src="https://img.shields.io/badge/%20license-MIT%20%20License%20-E91E63.svg?style=flat-square" alt="License"></a>
|
||||||
|
|
||||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20release%20-%20v4.1.3%20-blue.svg?style=flat-square" alt="Releases"></a>
|
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20release%20-%20v4.1.4%20-blue.svg?style=flat-square" alt="Releases"></a>
|
||||||
|
|
||||||
<a href="https://www.gitbook.com/book/kataras/iris/details"><img src="https://img.shields.io/badge/%20docs-reference-5272B4.svg?style=flat-square" alt="Practical Guide/Docs"></a><br/>
|
<a href="https://www.gitbook.com/book/kataras/iris/details"><img src="https://img.shields.io/badge/%20docs-reference-5272B4.svg?style=flat-square" alt="Practical Guide/Docs"></a><br/>
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
|
||||||
Versioning
|
Versioning
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Current: **v4.1.3**
|
Current: **v4.1.4**
|
||||||
|
|
||||||
> Iris is an active project
|
> Iris is an active project
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ License can be found [here](LICENSE).
|
||||||
[Travis]: http://travis-ci.org/kataras/iris
|
[Travis]: http://travis-ci.org/kataras/iris
|
||||||
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
|
[License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square
|
||||||
[License]: https://github.com/kataras/iris/blob/master/LICENSE
|
[License]: https://github.com/kataras/iris/blob/master/LICENSE
|
||||||
[Release Widget]: https://img.shields.io/badge/release-v4.1.3-blue.svg?style=flat-square
|
[Release Widget]: https://img.shields.io/badge/release-v4.1.4-blue.svg?style=flat-square
|
||||||
[Release]: https://github.com/kataras/iris/releases
|
[Release]: https://github.com/kataras/iris/releases
|
||||||
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
|
[Chat Widget]: https://img.shields.io/badge/community-chat-00BCD4.svg?style=flat-square
|
||||||
[Chat]: https://kataras.rocket.chat/channel/iris
|
[Chat]: https://kataras.rocket.chat/channel/iris
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
|
"github.com/kataras/go-sessions/fasthttp"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -21,42 +21,16 @@ const (
|
||||||
DefaultCookieLength = 32
|
DefaultCookieLength = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
// Sessions the configuration for sessions
|
||||||
|
// has 6 fields
|
||||||
// Sessions the configuration for sessions
|
// first is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
|
||||||
// has 5 fields
|
// second enable if you want to decode the cookie's key also
|
||||||
// first is the cookieName, the session's name (string) ["mysessionsecretcookieid"]
|
// third is the time which the client's cookie expires
|
||||||
// second enable if you want to decode the cookie's key also
|
// forth is the cookie length (sessionid) int, defaults to 32, do not change if you don't have any reason to do
|
||||||
// third is the time which the client's cookie expires
|
// fifth is the gcDuration (time.Duration) when this time passes it removes the unused sessions from the memory until the user come back
|
||||||
// forth 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
|
||||||
// fifth is the DisableSubdomainPersistence which you can set it to true in order dissallow your iris subdomains to have access to the session cook
|
//
|
||||||
Sessions struct {
|
type Sessions sessions.Config
|
||||||
// Cookie string, the session's client cookie name, for example: "irissessionid"
|
|
||||||
Cookie string
|
|
||||||
// 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
|
|
||||||
// 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 but in this case, the server side's session duration is up to GcDuration
|
|
||||||
//
|
|
||||||
// Default infinitive/unlimited life duration(0)
|
|
||||||
|
|
||||||
Expires time.Duration
|
|
||||||
// GcDuration every how much duration(GcDuration) the memory should be clear for unused cookies (GcDuration)
|
|
||||||
// for example: time.Duration(2)*time.Hour. it will check every 2 hours if cookie hasn't be used for 2 hours,
|
|
||||||
// deletes it from backend memory until the user comes back, then the session continue to work as it was
|
|
||||||
//
|
|
||||||
// Default 2 hours
|
|
||||||
GcDuration time.Duration
|
|
||||||
|
|
||||||
// DisableSubdomainPersistence set it to true in order dissallow your iris subdomains to have access to the session cookie
|
|
||||||
// defaults to false
|
|
||||||
DisableSubdomainPersistence bool
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultSessions the default configs for Sessions
|
// DefaultSessions the default configs for Sessions
|
||||||
func DefaultSessions() Sessions {
|
func DefaultSessions() Sessions {
|
||||||
|
|
12
context.go
12
context.go
|
@ -21,10 +21,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/go-fs"
|
|
||||||
|
|
||||||
"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-sessions"
|
||||||
"github.com/kataras/iris/config"
|
"github.com/kataras/iris/config"
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/utils"
|
"github.com/kataras/iris/utils"
|
||||||
|
@ -109,7 +109,7 @@ type (
|
||||||
framework *Framework
|
framework *Framework
|
||||||
//keep track all registed middleware (handlers)
|
//keep track all registed middleware (handlers)
|
||||||
middleware Middleware
|
middleware Middleware
|
||||||
session *session
|
session sessions.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 uint8
|
pos uint8
|
||||||
}
|
}
|
||||||
|
@ -955,13 +955,13 @@ func (ctx *Context) SetFlash(key string, value string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session returns the current session
|
// Session returns the current session
|
||||||
func (ctx *Context) Session() context.Session {
|
func (ctx *Context) Session() sessions.Session {
|
||||||
if ctx.framework.sessions == nil { // this should never return nil but FOR ANY CASE, on future changes.
|
if ctx.framework.sessions == nil { // this should never return nil but FOR ANY CASE, on future changes.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.session == nil {
|
if ctx.session == nil {
|
||||||
ctx.session = ctx.framework.sessions.start(ctx)
|
ctx.session = ctx.framework.sessions.Start(ctx.RequestCtx)
|
||||||
}
|
}
|
||||||
return ctx.session
|
return ctx.session
|
||||||
}
|
}
|
||||||
|
@ -969,7 +969,7 @@ func (ctx *Context) Session() context.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)
|
ctx.framework.sessions.Destroy(ctx.RequestCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,13 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"github.com/kataras/go-sessions"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Session is the domain-level session's store interface
|
|
||||||
// it's synced with the iris/sessions.go:session
|
|
||||||
Session interface {
|
|
||||||
ID() string
|
|
||||||
Get(string) interface{}
|
|
||||||
GetString(key string) string
|
|
||||||
GetInt(key string) int
|
|
||||||
GetAll() map[string]interface{}
|
|
||||||
VisitAll(cb func(k string, v interface{}))
|
|
||||||
Set(string, interface{})
|
|
||||||
Delete(string)
|
|
||||||
Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IContext the interface for the iris/context
|
// IContext the interface for the iris/context
|
||||||
// Used mostly inside packages which shouldn't be import ,directly, the kataras/iris.
|
// Used mostly inside packages which shouldn't be import ,directly, the kataras/iris.
|
||||||
|
@ -87,7 +74,7 @@ type (
|
||||||
GetFlashes() map[string]string
|
GetFlashes() map[string]string
|
||||||
GetFlash(string) (string, error)
|
GetFlash(string) (string, error)
|
||||||
SetFlash(string, string)
|
SetFlash(string, string)
|
||||||
Session() Session
|
Session() sessions.Session
|
||||||
SessionDestroy()
|
SessionDestroy()
|
||||||
Log(string, ...interface{})
|
Log(string, ...interface{})
|
||||||
GetRequestCtx() *fasthttp.RequestCtx
|
GetRequestCtx() *fasthttp.RequestCtx
|
||||||
|
|
81
iris.go
81
iris.go
|
@ -52,21 +52,6 @@ package iris // import "github.com/kataras/iris"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/klauspost/compress/gzip"
|
|
||||||
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/gavv/httpexpect"
|
"github.com/gavv/httpexpect"
|
||||||
"github.com/iris-contrib/logger"
|
"github.com/iris-contrib/logger"
|
||||||
"github.com/iris-contrib/response/data"
|
"github.com/iris-contrib/response/data"
|
||||||
|
@ -75,19 +60,31 @@ import (
|
||||||
"github.com/iris-contrib/response/markdown"
|
"github.com/iris-contrib/response/markdown"
|
||||||
"github.com/iris-contrib/response/text"
|
"github.com/iris-contrib/response/text"
|
||||||
"github.com/iris-contrib/response/xml"
|
"github.com/iris-contrib/response/xml"
|
||||||
"github.com/iris-contrib/template/html"
|
|
||||||
"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"
|
||||||
|
fasthttpSessions "github.com/kataras/go-sessions/fasthttp"
|
||||||
"github.com/kataras/go-template"
|
"github.com/kataras/go-template"
|
||||||
|
"github.com/kataras/go-template/html"
|
||||||
"github.com/kataras/iris/config"
|
"github.com/kataras/iris/config"
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
"github.com/kataras/iris/utils"
|
"github.com/kataras/iris/utils"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version of the iris
|
// Version of the iris
|
||||||
Version = "4.1.3"
|
Version = "4.1.4"
|
||||||
|
|
||||||
banner = ` _____ _
|
banner = ` _____ _
|
||||||
|_ _| (_)
|
|_ _| (_)
|
||||||
|
@ -154,7 +151,7 @@ type (
|
||||||
ListenVirtual(...string) *Server
|
ListenVirtual(...string) *Server
|
||||||
Go() error
|
Go() error
|
||||||
Close() error
|
Close() error
|
||||||
UseSessionDB(SessionDatabase)
|
UseSessionDB(sessions.Database)
|
||||||
UseResponse(ResponseEngine, ...string) func(string)
|
UseResponse(ResponseEngine, ...string) func(string)
|
||||||
UseTemplate(template.Engine) *template.Loader
|
UseTemplate(template.Engine) *template.Loader
|
||||||
UseGlobal(...Handler)
|
UseGlobal(...Handler)
|
||||||
|
@ -175,8 +172,7 @@ type (
|
||||||
*muxAPI
|
*muxAPI
|
||||||
contextPool sync.Pool
|
contextPool sync.Pool
|
||||||
Config *config.Iris
|
Config *config.Iris
|
||||||
gzipWriterPool sync.Pool // used for several methods, usually inside context
|
sessions fasthttpSessions.Sessions
|
||||||
sessions *sessionsManager
|
|
||||||
responses *responseEngines
|
responses *responseEngines
|
||||||
templates *templateEngines
|
templates *templateEngines
|
||||||
// fields which are useful to the user/dev
|
// fields which are useful to the user/dev
|
||||||
|
@ -221,11 +217,7 @@ func New(cfg ...config.Iris) *Framework {
|
||||||
"url": s.URL,
|
"url": s.URL,
|
||||||
"urlpath": s.Path,
|
"urlpath": s.Path,
|
||||||
})
|
})
|
||||||
// set the sessions
|
|
||||||
if s.Config.Sessions.Cookie != "" {
|
|
||||||
//set the session manager
|
|
||||||
s.sessions = newSessionsManager(&s.Config.Sessions)
|
|
||||||
}
|
|
||||||
// set the websocket server
|
// set the websocket server
|
||||||
s.Websocket = NewWebsocketServer(s.Config.Websocket)
|
s.Websocket = NewWebsocketServer(s.Config.Websocket)
|
||||||
// set the servemux, which will provide us the public API also, with its context pool
|
// set the servemux, which will provide us the public API also, with its context pool
|
||||||
|
@ -242,6 +234,12 @@ func New(cfg ...config.Iris) *Framework {
|
||||||
|
|
||||||
func (s *Framework) initialize() {
|
func (s *Framework) initialize() {
|
||||||
|
|
||||||
|
// set the sessions
|
||||||
|
if s.Config.Sessions.Cookie != "" {
|
||||||
|
//set the session manager
|
||||||
|
s.sessions = fasthttpSessions.New(fasthttpSessions.Config(s.Config.Sessions))
|
||||||
|
}
|
||||||
|
|
||||||
// prepare the response engines, if no response engines setted for the default content-types
|
// prepare the response engines, if no response engines setted for the default content-types
|
||||||
// then add them
|
// then add them
|
||||||
|
|
||||||
|
@ -591,7 +589,7 @@ func (s *Framework) Close() error {
|
||||||
// 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.
|
// 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.
|
// Note: Don't worry if no session database is registered, your context.Session will continue to work.
|
||||||
func UseSessionDB(db SessionDatabase) {
|
func UseSessionDB(db sessions.Database) {
|
||||||
Default.UseSessionDB(db)
|
Default.UseSessionDB(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,8 +599,8 @@ func UseSessionDB(db SessionDatabase) {
|
||||||
// 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.
|
// 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.
|
// Note: Don't worry if no session database is registered, your context.Session will continue to work.
|
||||||
func (s *Framework) UseSessionDB(db SessionDatabase) {
|
func (s *Framework) UseSessionDB(db sessions.Database) {
|
||||||
s.sessions.registerDatabase(db)
|
s.sessions.UseDatabase(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseResponse accepts a ResponseEngine and the key or content type on which the developer wants to register this response engine
|
// UseResponse accepts a ResponseEngine and the key or content type on which the developer wants to register this response engine
|
||||||
|
@ -897,33 +895,6 @@ func (s *Framework) URL(routeName string, args ...interface{}) (url string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcquireGzip prepares a gzip writer and returns it
|
|
||||||
//
|
|
||||||
// Note that: each iris station has its own pool
|
|
||||||
// see ReleaseGzip
|
|
||||||
func (s *Framework) AcquireGzip(w io.Writer) *gzip.Writer {
|
|
||||||
v := s.gzipWriterPool.Get()
|
|
||||||
if v == nil {
|
|
||||||
gzipWriter, err := gzip.NewWriterLevel(w, gzip.DefaultCompression)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return gzipWriter
|
|
||||||
}
|
|
||||||
gzipWriter := v.(*gzip.Writer)
|
|
||||||
gzipWriter.Reset(w)
|
|
||||||
return gzipWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseGzip called when flush/close and put the gzip writer back to the pool
|
|
||||||
//
|
|
||||||
// Note that: each iris station has its own pool
|
|
||||||
// see AcquireGzip
|
|
||||||
func (s *Framework) ReleaseGzip(gzipWriter *gzip.Writer) {
|
|
||||||
gzipWriter.Close()
|
|
||||||
s.gzipWriterPool.Put(gzipWriter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemplateString executes a template from the default template engine and returns its result as string, useful when you want it for sending rich e-mails
|
// TemplateString executes a template from the default template engine and returns its result as string, useful when you want it for sending rich e-mails
|
||||||
// returns empty string on error
|
// returns empty string on error
|
||||||
func TemplateString(templateFile string, pageContext interface{}, options ...map[string]interface{}) string {
|
func TemplateString(templateFile string, pageContext interface{}, options ...map[string]interface{}) string {
|
||||||
|
|
412
sessions.go
412
sessions.go
|
@ -1,412 +0,0 @@
|
||||||
package iris
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/list"
|
|
||||||
"encoding/base64"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/config"
|
|
||||||
"github.com/kataras/iris/utils"
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// ----------------------------------SessionDatabase implementation---------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// SessionDatabase is the interface which all session databases should implement
|
|
||||||
// By design it doesn't support any type of cookie store like other frameworks, I want to protect you, believe me, no context access (although we could)
|
|
||||||
// The scope of the database is to store somewhere the sessions in order to keep them after restarting the server, nothing more.
|
|
||||||
// the values are stored by the underline session, the check for new sessions, or 'this session value should added' are made automatically by Iris, 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 SessionDatabase interface {
|
|
||||||
Load(string) map[string]interface{}
|
|
||||||
Update(string, map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// ----------------------------------Session implementation-----------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// session is an 'object' which wraps the session provider with its session databases, only frontend user has access to this session object.
|
|
||||||
// this is really used on context and everywhere inside Iris
|
|
||||||
type session struct {
|
|
||||||
sid string
|
|
||||||
values map[string]interface{} // here is the real values
|
|
||||||
mu sync.Mutex
|
|
||||||
lastAccessedTime time.Time
|
|
||||||
createdAt time.Time
|
|
||||||
provider *sessionProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.Lock() // for any-case.
|
|
||||||
if value, found := s.values[key]; found {
|
|
||||||
s.mu.Unlock()
|
|
||||||
s.provider.update(s.sid)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt same as Get but returns as int, if nil then returns -1
|
|
||||||
func (s *session) GetInt(key string) int {
|
|
||||||
if value := s.Get(key); value != nil {
|
|
||||||
if v, ok := value.(int); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll returns all session's values
|
|
||||||
func (s *session) GetAll() map[string]interface{} {
|
|
||||||
return s.values
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.provider.update(s.sid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes an entry by its key
|
|
||||||
// returns an error, which is always nil
|
|
||||||
func (s *session) Delete(key string) {
|
|
||||||
s.mu.Lock()
|
|
||||||
delete(s.values, key)
|
|
||||||
s.mu.Unlock()
|
|
||||||
s.provider.update(s.sid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all entries
|
|
||||||
func (s *session) Clear() {
|
|
||||||
s.mu.Lock()
|
|
||||||
for key := range s.values {
|
|
||||||
delete(s.values, key)
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
|
||||||
s.provider.update(s.sid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// ----------------------------------sessionProvider implementation---------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type (
|
|
||||||
// sessionProvider contains the temp sessions memory and the databases
|
|
||||||
sessionProvider struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
sessions map[string]*list.Element // underline TEMPORARY memory store used to give advantage on sessions used more times than others
|
|
||||||
list *list.List // for GC
|
|
||||||
databases []SessionDatabase
|
|
||||||
expires time.Duration
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *sessionProvider) registerDatabase(db SessionDatabase) {
|
|
||||||
p.mu.Lock() // for any case
|
|
||||||
p.databases = append(p.databases, db)
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sessionProvider) newSession(sid string) *session {
|
|
||||||
|
|
||||||
sess := &session{
|
|
||||||
sid: sid,
|
|
||||||
provider: p,
|
|
||||||
lastAccessedTime: time.Now(),
|
|
||||||
values: p.loadSessionValues(sid),
|
|
||||||
}
|
|
||||||
if p.expires > 0 { // if not unlimited life duration and no -1 (cookie remove action is based on browser's session)
|
|
||||||
time.AfterFunc(p.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 *sessionProvider) 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 *sessionProvider) 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 *sessionProvider) init(sid string) *session {
|
|
||||||
newSession := p.newSession(sid)
|
|
||||||
elem := p.list.PushBack(newSession)
|
|
||||||
p.mu.Lock()
|
|
||||||
p.sessions[sid] = elem
|
|
||||||
p.mu.Unlock()
|
|
||||||
return newSession
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read returns the store which sid parameter is belongs
|
|
||||||
func (p *sessionProvider) read(sid string) *session {
|
|
||||||
p.mu.Lock()
|
|
||||||
if elem, found := p.sessions[sid]; found {
|
|
||||||
p.mu.Unlock() // yes defer is slow
|
|
||||||
elem.Value.(*session).lastAccessedTime = time.Now()
|
|
||||||
return elem.Value.(*session)
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
// if not found create new
|
|
||||||
sess := p.init(sid)
|
|
||||||
return sess
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy destroys the session, removes all sessions values, the session itself and updates the registered session databases, this called from sessionManager which removes the client's cookie also.
|
|
||||||
func (p *sessionProvider) destroy(sid string) {
|
|
||||||
p.mu.Lock()
|
|
||||||
if elem, found := p.sessions[sid]; found {
|
|
||||||
sess := elem.Value.(*session)
|
|
||||||
sess.values = nil
|
|
||||||
p.updateDatabases(sid, nil)
|
|
||||||
delete(p.sessions, sid)
|
|
||||||
p.list.Remove(elem)
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates the lastAccessedTime, and moves the memory place element to the front
|
|
||||||
// always returns a nil error, for now
|
|
||||||
func (p *sessionProvider) update(sid string) {
|
|
||||||
p.mu.Lock()
|
|
||||||
if elem, found := p.sessions[sid]; found {
|
|
||||||
sess := elem.Value.(*session)
|
|
||||||
sess.lastAccessedTime = time.Now()
|
|
||||||
p.list.MoveToFront(elem)
|
|
||||||
p.updateDatabases(sid, sess.values)
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GC clears the memory
|
|
||||||
func (p *sessionProvider) gc(duration time.Duration) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
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
|
|
||||||
// we are not destroy the session completely for the case this is re-used after
|
|
||||||
sess := elem.Value.(*session)
|
|
||||||
if time.Now().After(sess.lastAccessedTime.Add(duration)) {
|
|
||||||
p.list.Remove(elem)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// ----------------------------------sessionsManager implementation---------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
// -------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type (
|
|
||||||
// sessionsManager implements the ISessionsManager interface
|
|
||||||
// contains the cookie's name, the provider and a duration for GC and cookie life expire
|
|
||||||
sessionsManager struct {
|
|
||||||
config *config.Sessions
|
|
||||||
provider *sessionProvider
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// newSessionsManager creates & returns a new SessionsManager and start its GC
|
|
||||||
func newSessionsManager(c *config.Sessions) *sessionsManager {
|
|
||||||
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)))
|
|
||||||
}
|
|
||||||
if c.CookieLength <= 0 {
|
|
||||||
c.CookieLength = config.DefaultCookieLength
|
|
||||||
}
|
|
||||||
|
|
||||||
manager := &sessionsManager{config: c, provider: &sessionProvider{list: list.New(), sessions: make(map[string]*list.Element, 0), databases: make([]SessionDatabase, 0), expires: c.Expires}}
|
|
||||||
//run the GC here
|
|
||||||
go manager.gc()
|
|
||||||
return manager
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *sessionsManager) registerDatabase(db SessionDatabase) {
|
|
||||||
m.provider.expires = m.config.Expires // updae the expires confiuration field for any case
|
|
||||||
m.provider.registerDatabase(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *sessionsManager) generateSessionID() string {
|
|
||||||
return base64.URLEncoding.EncodeToString(utils.Random(m.config.CookieLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
func domainCanPersistence(requestDomain string) bool {
|
|
||||||
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 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(requestDomain, ".")
|
|
||||||
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(requestDomain, '.'); lastDotIdx != -1 {
|
|
||||||
// chekc the last part, if it's number then propably it's ip
|
|
||||||
if len(requestDomain) > lastDotIdx+1 {
|
|
||||||
_, err := strconv.Atoi(requestDomain[lastDotIdx+1:])
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the session
|
|
||||||
func (m *sessionsManager) start(ctx *Context) *session {
|
|
||||||
var session *session
|
|
||||||
|
|
||||||
cookieValue := ctx.GetCookie(m.config.Cookie)
|
|
||||||
|
|
||||||
if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
|
|
||||||
sid := m.generateSessionID()
|
|
||||||
session = m.provider.init(sid)
|
|
||||||
cookie := fasthttp.AcquireCookie()
|
|
||||||
//cookie := &fasthttp.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.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 domainCanPersistence(requestDomain) {
|
|
||||||
|
|
||||||
// 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, '.'); 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.SetDomain("." + requestDomain) // . to allow persistance
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
cookie.SetHTTPOnly(true)
|
|
||||||
if m.config.Expires == 0 {
|
|
||||||
// unlimited life
|
|
||||||
cookie.SetExpire(config.CookieExpireNever)
|
|
||||||
} else if m.config.Expires > 0 {
|
|
||||||
cookie.SetExpire(time.Now().Add(m.config.Expires))
|
|
||||||
} // if it's -1 then the cookie is deleted when the browser closes
|
|
||||||
|
|
||||||
ctx.SetCookie(cookie)
|
|
||||||
fasthttp.ReleaseCookie(cookie)
|
|
||||||
} else {
|
|
||||||
session = m.provider.read(cookieValue)
|
|
||||||
}
|
|
||||||
return session
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy kills the session and remove the associated cookie
|
|
||||||
func (m *sessionsManager) destroy(ctx *Context) {
|
|
||||||
cookieValue := ctx.GetCookie(m.config.Cookie)
|
|
||||||
if cookieValue == "" { // nothing to destroy
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.RemoveCookie(m.config.Cookie)
|
|
||||||
m.provider.destroy(cookieValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GC tick-tock for the store cleanup
|
|
||||||
// it's a blocking function, so run it with go routine, it's totally safe
|
|
||||||
func (m *sessionsManager) gc() {
|
|
||||||
m.provider.gc(m.config.GcDuration)
|
|
||||||
// set a timer for the next GC
|
|
||||||
time.AfterFunc(m.config.GcDuration, func() {
|
|
||||||
m.gc()
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
package iris
|
package iris
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kataras/go-fs"
|
||||||
"github.com/kataras/go-template"
|
"github.com/kataras/go-template"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
@ -62,8 +63,8 @@ func (t *templateEngines) render(ctx *Context, filename string, binding interfac
|
||||||
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
|
ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
|
||||||
ctx.SetHeader(contentEncodingHeader, "gzip")
|
ctx.SetHeader(contentEncodingHeader, "gzip")
|
||||||
|
|
||||||
gzipWriter := ctx.framework.AcquireGzip(ctx.Response.BodyWriter())
|
gzipWriter := fs.AcquireGzipWriter(ctx.Response.BodyWriter())
|
||||||
defer ctx.framework.ReleaseGzip(gzipWriter)
|
defer fs.ReleaseGzipWriter(gzipWriter)
|
||||||
out = gzipWriter
|
out = gzipWriter
|
||||||
} else {
|
} else {
|
||||||
out = ctx.Response.BodyWriter()
|
out = ctx.Response.BodyWriter()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user