1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-23 10:44:02 +00:00

📦 update session

This commit is contained in:
Fenny 2020-11-16 14:22:44 +01:00
parent 0d2eb334d6
commit de912755f7
13 changed files with 277 additions and 128 deletions

View File

@ -61,12 +61,12 @@ type Config struct {
// Default: func(c *fiber.Ctx) string {
// return c.Path()
// }
Key func(*fiber.Ctx) string
KeyGenerator func(*fiber.Ctx) string
// Store is used to store the state of the middleware
//
// Default: an in memory store for this process only
Store fiber.Storage
Storage fiber.Storage
}
```
@ -77,8 +77,9 @@ var ConfigDefault = Config{
Next: nil,
Expiration: 1 * time.Minute,
CacheControl: false,
Key: func(c *fiber.Ctx) string {
KeyGenerator: func(c *fiber.Ctx) string {
return c.Path()
},
Storage: nil,
}
```

View File

@ -29,16 +29,19 @@ type Config struct {
// Default: func(c *fiber.Ctx) string {
// return c.Path()
// }
Key func(*fiber.Ctx) string
// Deprecated, use Storage instead
Store fiber.Storage
KeyGenerator func(*fiber.Ctx) string
// Store is used to store the state of the middleware
//
// Default: an in memory store for this process only
Storage fiber.Storage
// Deprecated, use Storage instead
Store fiber.Storage
// Deprecated, use KeyGenerator instead
Key func(*fiber.Ctx) string
// Internally used - if true, the simpler method of two maps is used in order to keep
// execution time down.
defaultStore bool
@ -49,9 +52,10 @@ var ConfigDefault = Config{
Next: nil,
Expiration: 1 * time.Minute,
CacheControl: false,
Key: func(c *fiber.Ctx) string {
KeyGenerator: func(c *fiber.Ctx) string {
return c.Path()
},
Storage: nil,
defaultStore: true,
}
@ -66,21 +70,24 @@ func configDefault(config ...Config) Config {
cfg := config[0]
// Set default values
if cfg.Store != nil {
fmt.Println("[CACHE] Store is deprecated, please use Storage")
cfg.Storage = cfg.Store
}
if cfg.Key != nil {
fmt.Println("[CACHE] Key is deprecated, please use KeyGenerator")
cfg.KeyGenerator = cfg.Key
}
if cfg.Next == nil {
cfg.Next = ConfigDefault.Next
}
if int(cfg.Expiration.Seconds()) == 0 {
cfg.Expiration = ConfigDefault.Expiration
}
if cfg.Key == nil {
cfg.Key = ConfigDefault.Key
if cfg.KeyGenerator == nil {
cfg.KeyGenerator = ConfigDefault.KeyGenerator
}
if cfg.Storage == nil && cfg.Store == nil {
cfg.defaultStore = true
}
if cfg.Store != nil {
fmt.Println("cache: `Store` is deprecated, use `Storage` instead")
cfg.Storage = cfg.Store
if cfg.Storage != nil {
cfg.defaultStore = true
}
return cfg

View File

@ -1,4 +1,5 @@
# Compress
Compression middleware for [Fiber](https://github.com/gofiber/fiber) that will compress the response using `gzip`, `deflate` and `brotli` compression depending on the [Accept-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) header.
- [Signatures](#signatures)

View File

@ -1,4 +1,5 @@
# Cross-Origin Resource Sharing (CORS)
CORS middleware for [Fiber](https://github.com/gofiber/fiber) that that can be used to enable [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) with various options.
### Table of Contents

View File

@ -1,4 +1,5 @@
# ETag
ETag middleware for [Fiber](https://github.com/gofiber/fiber) that lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed.
### Table of Contents
@ -37,6 +38,11 @@ app.Get("/", func(c *fiber.Ctx) error {
```go
// Config defines the config for middleware.
type Config struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
// Weak indicates that a weak validator is used. Weak etags are easy
// to generate, but are far less useful for comparisons. Strong
// validators are ideal for comparisons but can be very difficult
@ -46,18 +52,13 @@ type Config struct {
// when byte range requests are used, but strong etags mean range
// requests can still be cached.
Weak bool
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
}
```
### Default Config
```go
var ConfigDefault = Config{
Weak: false,
Next: nil,
Weak: false,
}
```

44
middleware/etag/config.go Normal file
View File

@ -0,0 +1,44 @@
package etag
import (
"github.com/gofiber/fiber/v2"
)
// Config defines the config for middleware.
type Config struct {
// Weak indicates that a weak validator is used. Weak etags are easy
// to generate, but are far less useful for comparisons. Strong
// validators are ideal for comparisons but can be very difficult
// to generate efficiently. Weak ETag values of two representations
// of the same resources might be semantically equivalent, but not
// byte-for-byte identical. This means weak etags prevent caching
// when byte range requests are used, but strong etags mean range
// requests can still be cached.
Weak bool
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
}
// ConfigDefault is the default config
var ConfigDefault = Config{
Weak: false,
Next: nil,
}
// Helper function to set default values
func configDefault(config ...Config) Config {
// Return default config if nothing provided
if len(config) < 1 {
return ConfigDefault
}
// Override default config
cfg := config[0]
// Set default values
return cfg
}

View File

@ -8,42 +8,13 @@ import (
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
)
// Config defines the config for middleware.
type Config struct {
// Weak indicates that a weak validator is used. Weak etags are easy
// to generate, but are far less useful for comparisons. Strong
// validators are ideal for comparisons but can be very difficult
// to generate efficiently. Weak ETag values of two representations
// of the same resources might be semantically equivalent, but not
// byte-for-byte identical. This means weak etags prevent caching
// when byte range requests are used, but strong etags mean range
// requests can still be cached.
Weak bool
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
}
// ConfigDefault is the default config
var ConfigDefault = Config{
Weak: false,
Next: nil,
}
var normalizedHeaderETag = []byte("Etag")
var weakPrefix = []byte("W/")
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := ConfigDefault
// Override config if provided
if len(config) > 0 {
cfg = config[0]
}
cfg := configDefault(config...)
var crc32q = crc32.MakeTable(0xD5828281)

View File

@ -129,7 +129,7 @@ func New(config ...Config) fiber.Handler {
// Set error handler once
once.Do(func() {
errHandler = c.App().Config().ErrorHandler
// get longested possible path
stack := c.App().Stack()
for m := range stack {
for r := range stack[m] {
@ -139,7 +139,8 @@ func New(config ...Config) fiber.Handler {
}
}
}
// override error handler
errHandler = c.App().Config().ErrorHandler
})
// Set latency start time

View File

@ -1,10 +1,12 @@
package session
import "sync"
// go:generate msgp
// msgp -file="db.go" -o="db_msgp.go" -tests=false -unexported
// msgp -file="data.go" -o="data_msgp.go" -tests=false -unexported
// don't forget to replace the msgp import path to:
// "github.com/gofiber/fiber/v2/internal/msgp"
type db struct {
type data struct {
d []kv
}
@ -14,11 +16,26 @@ type kv struct {
v interface{}
}
func (d *db) Reset() {
var dataPool = sync.Pool{
New: func() interface{} {
return new(data)
},
}
func acquireData() *data {
return dataPool.Get().(*data)
}
func releaseData(d *data) {
d.Reset()
dataPool.Put(d)
}
func (d *data) Reset() {
d.d = d.d[:0]
}
func (d *db) Get(key string) interface{} {
func (d *data) Get(key string) interface{} {
idx := d.indexOf(key)
if idx > -1 {
return d.d[idx].v
@ -26,7 +43,7 @@ func (d *db) Get(key string) interface{} {
return nil
}
func (d *db) Set(key string, value interface{}) {
func (d *data) Set(key string, value interface{}) {
idx := d.indexOf(key)
if idx > -1 {
kv := &d.d[idx]
@ -36,7 +53,7 @@ func (d *db) Set(key string, value interface{}) {
}
}
func (d *db) Delete(key string) {
func (d *data) Delete(key string) {
idx := d.indexOf(key)
if idx > -1 {
n := len(d.d) - 1
@ -45,11 +62,11 @@ func (d *db) Delete(key string) {
}
}
func (d *db) Len() int {
func (d *data) Len() int {
return len(d.d)
}
func (d *db) swap(i, j int) {
func (d *data) swap(i, j int) {
iKey, iValue := d.d[i].k, d.d[i].v
jKey, jValue := d.d[j].k, d.d[j].v
@ -57,7 +74,7 @@ func (d *db) swap(i, j int) {
d.d[j].k, d.d[j].v = iKey, iValue
}
func (d *db) allocPage() *kv {
func (d *data) allocPage() *kv {
n := len(d.d)
if cap(d.d) > n {
d.d = d.d[:n+1]
@ -67,13 +84,13 @@ func (d *db) allocPage() *kv {
return &d.d[n]
}
func (d *db) append(key string, value interface{}) {
func (d *data) append(key string, value interface{}) {
kv := d.allocPage()
kv.k = key
kv.v = value
}
func (d *db) indexOf(key string) int {
func (d *data) indexOf(key string) int {
n := len(d.d)
for i := 0; i < n; i++ {
if d.d[i].k == key {

View File

@ -7,7 +7,7 @@ import (
)
// DecodeMsg implements msgp.Decodable
func (z *db) DecodeMsg(dc *msgp.Reader) (err error) {
func (z *data) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
@ -84,7 +84,7 @@ func (z *db) DecodeMsg(dc *msgp.Reader) (err error) {
}
// EncodeMsg implements msgp.Encodable
func (z *db) EncodeMsg(en *msgp.Writer) (err error) {
func (z *data) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 1
// write "d"
err = en.Append(0x81, 0xa1, 0x64)
@ -123,7 +123,7 @@ func (z *db) EncodeMsg(en *msgp.Writer) (err error) {
}
// MarshalMsg implements msgp.Marshaler
func (z *db) MarshalMsg(b []byte) (o []byte, err error) {
func (z *data) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 1
// string "d"
@ -146,7 +146,7 @@ func (z *db) MarshalMsg(b []byte) (o []byte, err error) {
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *db) UnmarshalMsg(bts []byte) (o []byte, err error) {
func (z *data) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
@ -224,7 +224,7 @@ func (z *db) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *db) Msgsize() (s int) {
func (z *data) Msgsize() (s int) {
s = 1 + 2 + msgp.ArrayHeaderSize
for za0001 := range z.d {
s += 1 + 2 + msgp.StringPrefixSize + len(z.d[za0001].k) + 2 + msgp.GuessSize(z.d[za0001].v)

View File

@ -10,11 +10,11 @@ import (
)
type Session struct {
ctx *fiber.Ctx
config *Store
db *db
id string
fresh bool
id string // session id
fresh bool // if new session
ctx *fiber.Ctx // fiber context
config *Store // store configuration
data *data // key value data
}
var sessionPool = sync.Pool{
@ -24,20 +24,10 @@ var sessionPool = sync.Pool{
}
func acquireSession() *Session {
s := sessionPool.Get().(*Session)
s.db = new(db)
s.fresh = true
return s
return sessionPool.Get().(*Session)
}
func releaseSession(s *Session) {
s.ctx = nil
s.config = nil
if s.db != nil {
s.db.Reset()
}
s.id = ""
s.fresh = true
sessionPool.Put(s)
}
@ -53,28 +43,51 @@ func (s *Session) ID() string {
// Get will return the value
func (s *Session) Get(key string) interface{} {
return s.db.Get(key)
// 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{}) {
s.db.Set(key, val)
// 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) {
s.db.Delete(key)
// 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 {
// Reset local data
s.db.Reset()
// Better safe than sorry
if s.data == nil {
return nil
}
// Delete data from storage
// Reset local data
s.data.Reset()
// Use external Storage if exist
if s.config.Storage != nil {
if err := s.config.Storage.Delete(s.id); err != nil {
return err
}
} else {
s.config.mux.Lock()
delete(s.config.sessions, s.id)
s.config.mux.Unlock()
}
// Expire cookie
s.delCookie()
@ -83,11 +96,18 @@ func (s *Session) Destroy() error {
// Regenerate generates a new session id and delete the old one from Storage
func (s *Session) Regenerate() error {
// Use external Storage if exist
if s.config.Storage != nil {
// Delete old id from storage
if err := s.config.Storage.Delete(s.id); err != nil {
return err
}
} else {
s.config.mux.Lock()
delete(s.config.sessions, s.id)
s.config.mux.Unlock()
}
// Create new ID
s.id = s.config.KeyGenerator()
@ -96,13 +116,20 @@ func (s *Session) Regenerate() error {
// Save will update the storage and client cookie
func (s *Session) Save() error {
// Don't save to Storage if no data is available
if s.db.Len() <= 0 {
// Better safe than sorry
if s.data == nil {
return nil
}
// Don't save to Storage if no data is available
if s.data.Len() <= 0 {
return nil
}
// Use external Storage if exist
if s.config.Storage != nil {
// Convert book to bytes
data, err := s.db.MarshalMsg(nil)
data, err := s.data.MarshalMsg(nil)
if err != nil {
return err
}
@ -111,11 +138,20 @@ func (s *Session) Save() error {
if err := s.config.Storage.Set(s.id, data, s.config.Expiration); err != nil {
return err
}
} else {
s.config.mux.Lock()
s.config.sessions[s.id] = s.data
s.config.mux.Unlock()
}
// Create cookie with the session ID
s.setCookie()
// release session to pool to be re-used on next request
// Release data if we use a Storage
if s.config.Storage != nil {
releaseData(s.data)
}
releaseSession(s)
return nil

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/internal/storage/memory"
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/fasthttp"
)
@ -170,3 +171,34 @@ func Test_Session_Cookie(t *testing.T) {
// cookie should not be set if empty data
utils.AssertEqual(t, 0, len(ctx.Response().Header.PeekCookie(store.CookieName)))
}
// go test -v -run=^$ -bench=Benchmark_Session -benchmem -count=4
func Benchmark_Session(b *testing.B) {
app, store := fiber.New(), New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
c.Request().Header.SetCookie(store.CookieName, "12356789")
b.Run("default", func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
sess, _ := store.Get(c)
sess.Set("john", "doe")
_ = sess.Save()
}
})
b.Run("storage", func(b *testing.B) {
store = New(Config{
Storage: memory.New(),
})
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
sess, _ := store.Get(c)
sess.Set("john", "doe")
_ = sess.Save()
}
})
}

View File

@ -1,12 +1,15 @@
package session
import (
"sync"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/internal/storage/memory"
)
type Store struct {
Config
mux *sync.RWMutex
sessions map[string]*data
}
// Storage ErrNotExist
@ -16,46 +19,74 @@ func New(config ...Config) *Store {
// Set default config
cfg := configDefault(config...)
if cfg.Storage == nil {
cfg.Storage = memory.New()
// Create Store object
store := &Store{
Config: cfg,
}
return &Store{
cfg,
// Default store logic (if no Storage is provided)
if cfg.Storage == nil {
store.mux = &sync.RWMutex{}
store.sessions = make(map[string]*data)
}
return store
}
func (s *Store) Get(c *fiber.Ctx) (*Session, error) {
var fresh bool
// Get key from cookie
// Get session id from cookie
id := c.Cookies(s.CookieName)
// If no key exist, create new one
// Create key if not exist
if len(id) == 0 {
id = s.KeyGenerator()
fresh = true
}
// Create session object
// Get session object from pool
sess := acquireSession()
sess.id = id
sess.fresh = fresh
sess.ctx = c
sess.config = s
sess.id = id
// Fetch existing data
if !fresh {
// Get session data if not fresh
if !sess.fresh {
// Use external Storage if exist
if s.Storage != nil {
raw, err := s.Storage.Get(id)
// Unmashal if we found data
if err == nil {
if _, err = sess.db.UnmarshalMsg(raw); err != nil {
sess.data = acquireData()
if _, err = sess.data.UnmarshalMsg(raw); err != nil {
return nil, err
}
sess.fresh = false
} else if err.Error() != errNotExist {
// Only return error if it's not ErrNotExist
return nil, err
} else {
// No data was found, this is now a fresh session
sess.fresh = true
}
} else {
// Find data in local memory map
s.mux.RLock()
data, ok := s.sessions[id]
s.mux.RUnlock()
if ok && data != nil {
sess.data = data
} else {
// No data was found, this is now a fresh session
sess.fresh = true
}
}
}
// Get new kv store if nil
if sess.data == nil {
sess.data = acquireData()
}
return sess, nil
@ -63,5 +94,11 @@ func (s *Store) Get(c *fiber.Ctx) (*Session, error) {
// Reset will delete all session from the storage
func (s *Store) Reset() error {
if s.Storage != nil {
return s.Storage.Reset()
}
s.mux.Lock()
s.sessions = make(map[string]*data)
s.mux.Unlock()
return nil
}