mirror of
https://github.com/gofiber/fiber.git
synced 2025-02-21 19:32:58 +00:00
248 lines
5.6 KiB
Go
248 lines
5.6 KiB
Go
package session
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/gofiber/fiber/v3/utils"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
type Session struct {
|
|
id string // session id
|
|
fresh bool // if new session
|
|
ctx *fiber.Ctx // fiber context
|
|
config *Store // store configuration
|
|
data *data // key value data
|
|
byteBuffer *bytes.Buffer // byte buffer for the en- and decode
|
|
exp time.Duration // expiration of this session
|
|
}
|
|
|
|
var sessionPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return new(Session)
|
|
},
|
|
}
|
|
|
|
func acquireSession() *Session {
|
|
s := sessionPool.Get().(*Session)
|
|
if s.data == nil {
|
|
s.data = acquireData()
|
|
}
|
|
if s.byteBuffer == nil {
|
|
s.byteBuffer = new(bytes.Buffer)
|
|
}
|
|
s.fresh = true
|
|
return s
|
|
}
|
|
|
|
func releaseSession(s *Session) {
|
|
s.id = ""
|
|
s.exp = 0
|
|
s.ctx = nil
|
|
s.config = nil
|
|
if s.data != nil {
|
|
s.data.Reset()
|
|
}
|
|
if s.byteBuffer != nil {
|
|
s.byteBuffer.Reset()
|
|
}
|
|
sessionPool.Put(s)
|
|
}
|
|
|
|
// Fresh is true if the current session is new
|
|
func (s *Session) Fresh() bool {
|
|
return s.fresh
|
|
}
|
|
|
|
// ID returns the session id
|
|
func (s *Session) ID() string {
|
|
return s.id
|
|
}
|
|
|
|
// Get will return the value
|
|
func (s *Session) Get(key string) interface{} {
|
|
// Better safe than sorry
|
|
if s.data == nil {
|
|
return nil
|
|
}
|
|
return s.data.Get(key)
|
|
}
|
|
|
|
// Set will update or create a new key value
|
|
func (s *Session) Set(key string, val interface{}) {
|
|
// Better safe than sorry
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.Set(key, val)
|
|
}
|
|
|
|
// Delete will delete the value
|
|
func (s *Session) Delete(key string) {
|
|
// Better safe than sorry
|
|
if s.data == nil {
|
|
return
|
|
}
|
|
s.data.Delete(key)
|
|
}
|
|
|
|
// Destroy will delete the session from Storage and expire session cookie
|
|
func (s *Session) Destroy() error {
|
|
// Better safe than sorry
|
|
if s.data == nil {
|
|
return nil
|
|
}
|
|
|
|
// Reset local data
|
|
s.data.Reset()
|
|
|
|
// Use external Storage if exist
|
|
if err := s.config.Storage.Delete(s.id); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Expire session
|
|
s.delSession()
|
|
return nil
|
|
}
|
|
|
|
// Regenerate generates a new session id and delete the old one from Storage
|
|
func (s *Session) Regenerate() error {
|
|
// Delete old id from storage
|
|
if err := s.config.Storage.Delete(s.id); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate a new session, and set session.fresh to true
|
|
s.refresh()
|
|
|
|
return nil
|
|
}
|
|
|
|
// refresh generates a new session, and set session.fresh to be true
|
|
func (s *Session) refresh() {
|
|
// Create a new id
|
|
s.id = s.config.KeyGenerator()
|
|
|
|
// We assign a new id to the session, so the session must be fresh
|
|
s.fresh = true
|
|
}
|
|
|
|
// Save will update the storage and client cookie
|
|
func (s *Session) Save() error {
|
|
// Better safe than sorry
|
|
if s.data == nil {
|
|
return nil
|
|
}
|
|
|
|
// Check if session has your own expiration, otherwise use default value
|
|
if s.exp <= 0 {
|
|
s.exp = s.config.Expiration
|
|
}
|
|
|
|
// Create session with the session ID if fresh
|
|
if s.fresh {
|
|
s.setSession()
|
|
}
|
|
|
|
// Convert data to bytes
|
|
mux.Lock()
|
|
defer mux.Unlock()
|
|
encCache := gob.NewEncoder(s.byteBuffer)
|
|
err := encCache.Encode(&s.data.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// copy the data in buffer
|
|
encodedBytes := make([]byte, s.byteBuffer.Len())
|
|
copy(encodedBytes, s.byteBuffer.Bytes())
|
|
|
|
// pass copied bytes with session id to provider
|
|
if err := s.config.Storage.Set(s.id, encodedBytes, s.exp); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Release session
|
|
// TODO: It's not safe to use the Session after called Save()
|
|
releaseSession(s)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Keys will retrieve all keys in current session
|
|
func (s *Session) Keys() []string {
|
|
if s.data == nil {
|
|
return []string{}
|
|
}
|
|
return s.data.Keys()
|
|
}
|
|
|
|
// SetExpiry sets a specific expiration for this session
|
|
func (s *Session) SetExpiry(exp time.Duration) {
|
|
s.exp = exp
|
|
}
|
|
|
|
func (s *Session) setSession() {
|
|
if s.config.source == SourceHeader {
|
|
s.ctx.Request().Header.SetBytesV(s.config.sessionName, []byte(s.id))
|
|
s.ctx.Response().Header.SetBytesV(s.config.sessionName, []byte(s.id))
|
|
} else {
|
|
fcookie := fasthttp.AcquireCookie()
|
|
fcookie.SetKey(s.config.sessionName)
|
|
fcookie.SetValue(s.id)
|
|
fcookie.SetPath(s.config.CookiePath)
|
|
fcookie.SetDomain(s.config.CookieDomain)
|
|
fcookie.SetMaxAge(int(s.exp.Seconds()))
|
|
fcookie.SetExpire(time.Now().Add(s.exp))
|
|
fcookie.SetSecure(s.config.CookieSecure)
|
|
fcookie.SetHTTPOnly(s.config.CookieHTTPOnly)
|
|
|
|
switch utils.ToLower(s.config.CookieSameSite) {
|
|
case "strict":
|
|
fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
|
|
case "none":
|
|
fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
|
|
default:
|
|
fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
|
|
}
|
|
s.ctx.Response().Header.SetCookie(fcookie)
|
|
fasthttp.ReleaseCookie(fcookie)
|
|
}
|
|
}
|
|
|
|
func (s *Session) delSession() {
|
|
if s.config.source == SourceHeader {
|
|
s.ctx.Request().Header.Del(s.config.sessionName)
|
|
s.ctx.Response().Header.Del(s.config.sessionName)
|
|
} else {
|
|
s.ctx.Request().Header.DelCookie(s.config.sessionName)
|
|
s.ctx.Response().Header.DelCookie(s.config.sessionName)
|
|
|
|
fcookie := fasthttp.AcquireCookie()
|
|
fcookie.SetKey(s.config.sessionName)
|
|
fcookie.SetPath(s.config.CookiePath)
|
|
fcookie.SetDomain(s.config.CookieDomain)
|
|
fcookie.SetMaxAge(-1)
|
|
fcookie.SetExpire(time.Now().Add(-1 * time.Minute))
|
|
fcookie.SetSecure(s.config.CookieSecure)
|
|
fcookie.SetHTTPOnly(s.config.CookieHTTPOnly)
|
|
|
|
switch utils.ToLower(s.config.CookieSameSite) {
|
|
case "strict":
|
|
fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
|
|
case "none":
|
|
fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
|
|
default:
|
|
fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
|
|
}
|
|
|
|
s.ctx.Response().Header.SetCookie(fcookie)
|
|
fasthttp.ReleaseCookie(fcookie)
|
|
}
|
|
}
|