v3: Use Named Fields Instead of Positional and Align Structures to Reduce Memory Usage (#3079)

* Use composites for internal structures. Fix alignment of structures across Fiber

* Update struct alignment in test files

* Enable alignment check with govet

* Fix ctx autoformat unit-test

* Revert app Config struct. Add betteralign to Makefile

* Disable comment on alert since it wont work for forks

* Update benchmark.yml

* Update benchmark.yml

* Remove warning from using positional fields

* Update router.go
pull/3076/head
Juan Calderon-Perez 2024-07-23 02:37:45 -04:00 committed by Juan Calderon-Perez
parent 25c9fa2530
commit 0f046ccf9d
73 changed files with 716 additions and 710 deletions

View File

@ -14,6 +14,8 @@ permissions:
deployments: write deployments: write
# contents permission to update benchmark contents in gh-pages branch # contents permission to update benchmark contents in gh-pages branch
contents: write contents: write
# allow posting comments to pull request
pull-requests: write
name: Benchmark name: Benchmark
jobs: jobs:

View File

@ -101,7 +101,6 @@ linters-settings:
govet: govet:
enable-all: true enable-all: true
disable: disable:
- fieldalignment
- shadow - shadow
grouper: grouper:

View File

@ -51,3 +51,8 @@ longtest:
.PHONY: tidy .PHONY: tidy
tidy: tidy:
go mod tidy -v go mod tidy -v
## betteralign: 📐 Optimize alignment of fields in structs
.PHONY: betteralign
betteralign:
go run github.com/dkorunic/betteralign/cmd/betteralign@latest -test_files -generated_files -apply ./...

View File

@ -11,10 +11,10 @@ import (
func Test_ExponentialBackoff_Retry(t *testing.T) { func Test_ExponentialBackoff_Retry(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
name string expErr error
expBackoff *ExponentialBackoff expBackoff *ExponentialBackoff
f func() error f func() error
expErr error name string
}{ }{
{ {
name: "With default values - successful", name: "With default values - successful",

38
app.go
View File

@ -80,29 +80,16 @@ type ErrorHandler = func(Ctx, error) error
// Error represents an error that occurred while handling a request. // Error represents an error that occurred while handling a request.
type Error struct { type Error struct {
Code int `json:"code"`
Message string `json:"message"` Message string `json:"message"`
Code int `json:"code"`
} }
// App denotes the Fiber application. // App denotes the Fiber application.
type App struct { type App struct {
mutex sync.Mutex
// Route stack divided by HTTP methods
stack [][]*Route
// Route stack divided by HTTP methods and route prefixes
treeStack []map[string][]*Route
// contains the information if the route stack has been changed to build the optimized tree
routesRefreshed bool
// Amount of registered routes
routesCount uint32
// Amount of registered handlers
handlersCount uint32
// Ctx pool // Ctx pool
pool sync.Pool pool sync.Pool
// Fasthttp server // Fasthttp server
server *fasthttp.Server server *fasthttp.Server
// App config
config Config
// Converts string to a byte slice // Converts string to a byte slice
getBytes func(s string) (b []byte) getBytes func(s string) (b []byte)
// Converts byte slice to a string // Converts byte slice to a string
@ -113,24 +100,37 @@ type App struct {
latestRoute *Route latestRoute *Route
// newCtxFunc // newCtxFunc
newCtxFunc func(app *App) CustomCtx newCtxFunc func(app *App) CustomCtx
// custom binders
customBinders []CustomBinder
// TLS handler // TLS handler
tlsHandler *TLSHandler tlsHandler *TLSHandler
// Mount fields // Mount fields
mountFields *mountFields mountFields *mountFields
// Indicates if the value was explicitly configured // Route stack divided by HTTP methods
configured Config stack [][]*Route
// Route stack divided by HTTP methods and route prefixes
treeStack []map[string][]*Route
// custom binders
customBinders []CustomBinder
// customConstraints is a list of external constraints // customConstraints is a list of external constraints
customConstraints []CustomConstraint customConstraints []CustomConstraint
// sendfiles stores configurations for handling ctx.SendFile operations // sendfiles stores configurations for handling ctx.SendFile operations
sendfiles []*sendFileStore sendfiles []*sendFileStore
// App config
config Config
// Indicates if the value was explicitly configured
configured Config
// sendfilesMutex is a mutex used for sendfile operations // sendfilesMutex is a mutex used for sendfile operations
sendfilesMutex sync.RWMutex sendfilesMutex sync.RWMutex
mutex sync.Mutex
// Amount of registered routes
routesCount uint32
// Amount of registered handlers
handlersCount uint32
// contains the information if the route stack has been changed to build the optimized tree
routesRefreshed bool
} }
// Config is a struct holding the server settings. // Config is a struct holding the server settings.
type Config struct { type Config struct { //nolint:govet // Aligning the struct fields is not necessary. betteralign:ignore
// Enables the "Server: value" HTTP header. // Enables the "Server: value" HTTP header.
// //
// Default: "" // Default: ""

View File

@ -26,9 +26,9 @@ func Test_Bind_Query(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct { type Query struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")
@ -53,14 +53,14 @@ func Test_Bind_Query(t *testing.T) {
require.Empty(t, empty.Hobby) require.Empty(t, empty.Hobby)
type Query2 struct { type Query2 struct {
Bool bool
ID int
Name string Name string
Hobby string Hobby string
FavouriteDrinks []string FavouriteDrinks []string
Empty []string Empty []string
Alloc []string Alloc []string
No []int64 No []int64
ID int
Bool bool
} }
c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1") c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1")
@ -237,8 +237,8 @@ func Test_Bind_Query_Schema(t *testing.T) {
require.Equal(t, "nested.age is empty", c.Bind().Query(q2).Error()) require.Equal(t, "nested.age is empty", c.Bind().Query(q2).Error())
type Node struct { type Node struct {
Value int `query:"val,required"`
Next *Node `query:"next,required"` Next *Node `query:"next,required"`
Value int `query:"val,required"`
} }
c.Request().URI().SetQueryString("val=1&next.val=3") c.Request().URI().SetQueryString("val=1&next.val=3")
n := new(Node) n := new(Node)
@ -292,9 +292,9 @@ func Test_Bind_Header(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Header struct { type Header struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")
@ -318,14 +318,14 @@ func Test_Bind_Header(t *testing.T) {
require.Empty(t, empty.Hobby) require.Empty(t, empty.Hobby)
type Header2 struct { type Header2 struct {
Bool bool
ID int
Name string Name string
Hobby string Hobby string
FavouriteDrinks []string FavouriteDrinks []string
Empty []string Empty []string
Alloc []string Alloc []string
No []int64 No []int64
ID int
Bool bool
} }
c.Request().Header.Add("id", "2") c.Request().Header.Add("id", "2")
@ -502,8 +502,8 @@ func Test_Bind_Header_Schema(t *testing.T) {
require.Equal(t, "Nested.age is empty", c.Bind().Header(h2).Error()) require.Equal(t, "Nested.age is empty", c.Bind().Header(h2).Error())
type Node struct { type Node struct {
Value int `header:"Val,required"`
Next *Node `header:"Next,required"` Next *Node `header:"Next,required"`
Value int `header:"Val,required"`
} }
c.Request().Header.Add("Val", "1") c.Request().Header.Add("Val", "1")
c.Request().Header.Add("Next.Val", "3") c.Request().Header.Add("Next.Val", "3")
@ -533,9 +533,9 @@ func Test_Bind_RespHeader(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Header struct { type Header struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")
@ -559,14 +559,14 @@ func Test_Bind_RespHeader(t *testing.T) {
require.Empty(t, empty.Hobby) require.Empty(t, empty.Hobby)
type Header2 struct { type Header2 struct {
Bool bool
ID int
Name string Name string
Hobby string Hobby string
FavouriteDrinks []string FavouriteDrinks []string
Empty []string Empty []string
Alloc []string Alloc []string
No []int64 No []int64
ID int
Bool bool
} }
c.Response().Header.Add("id", "2") c.Response().Header.Add("id", "2")
@ -635,9 +635,9 @@ func Benchmark_Bind_Query(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct { type Query struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")
@ -708,9 +708,9 @@ func Benchmark_Bind_Query_Comma(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct { type Query struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")
@ -732,9 +732,9 @@ func Benchmark_Bind_Header(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type ReqHeader struct { type ReqHeader struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")
@ -782,9 +782,9 @@ func Benchmark_Bind_RespHeader(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type ReqHeader struct { type ReqHeader struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")
@ -1252,9 +1252,9 @@ func Test_Bind_Cookie(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Cookie struct { type Cookie struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")
@ -1278,14 +1278,14 @@ func Test_Bind_Cookie(t *testing.T) {
require.Empty(t, empty.Hobby) require.Empty(t, empty.Hobby)
type Cookie2 struct { type Cookie2 struct {
Bool bool
ID int
Name string Name string
Hobby string Hobby string
FavouriteDrinks []string FavouriteDrinks []string
Empty []string Empty []string
Alloc []string Alloc []string
No []int64 No []int64
ID int
Bool bool
} }
c.Request().Header.SetCookie("id", "2") c.Request().Header.SetCookie("id", "2")
@ -1463,8 +1463,8 @@ func Test_Bind_Cookie_Schema(t *testing.T) {
require.Equal(t, "Nested.Age is empty", c.Bind().Cookie(h2).Error()) require.Equal(t, "Nested.Age is empty", c.Bind().Cookie(h2).Error())
type Node struct { type Node struct {
Value int `cookie:"Val,required"`
Next *Node `cookie:"Next,required"` Next *Node `cookie:"Next,required"`
Value int `cookie:"Val,required"`
} }
c.Request().Header.SetCookie("Val", "1") c.Request().Header.SetCookie("Val", "1")
c.Request().Header.SetCookie("Next.Val", "3") c.Request().Header.SetCookie("Next.Val", "3")
@ -1495,9 +1495,9 @@ func Benchmark_Bind_Cookie(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Cookie struct { type Cookie struct {
ID int
Name string Name string
Hobby []string Hobby []string
ID int
} }
c.Request().SetBody([]byte(``)) c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("") c.Request().Header.SetContentType("")

View File

@ -12,9 +12,9 @@ import (
// ParserConfig form decoder config for SetParserDecoder // ParserConfig form decoder config for SetParserDecoder
type ParserConfig struct { type ParserConfig struct {
IgnoreUnknownKeys bool
SetAliasTag string SetAliasTag string
ParserType []ParserType ParserType []ParserType
IgnoreUnknownKeys bool
ZeroEmpty bool ZeroEmpty bool
} }

View File

@ -34,21 +34,32 @@ var (
// Fiber Client also provides an option to override // Fiber Client also provides an option to override
// or merge most of the client settings at the request. // or merge most of the client settings at the request.
type Client struct { type Client struct {
mu sync.RWMutex // logger
logger log.CommonLogger
fasthttp *fasthttp.Client fasthttp *fasthttp.Client
baseURL string
userAgent string
referer string
header *Header header *Header
params *QueryParam params *QueryParam
cookies *Cookie cookies *Cookie
path *PathParam path *PathParam
debug bool jsonMarshal utils.JSONMarshal
jsonUnmarshal utils.JSONUnmarshal
xmlMarshal utils.XMLMarshal
xmlUnmarshal utils.XMLUnmarshal
timeout time.Duration cookieJar *CookieJar
// retry
retryConfig *RetryConfig
baseURL string
userAgent string
referer string
// proxy
proxyURL string
// user defined request hooks // user defined request hooks
userRequestHooks []RequestHook userRequestHooks []RequestHook
@ -62,21 +73,11 @@ type Client struct {
// client package defined response hooks // client package defined response hooks
builtinResponseHooks []ResponseHook builtinResponseHooks []ResponseHook
jsonMarshal utils.JSONMarshal timeout time.Duration
jsonUnmarshal utils.JSONUnmarshal
xmlMarshal utils.XMLMarshal
xmlUnmarshal utils.XMLUnmarshal
cookieJar *CookieJar mu sync.RWMutex
// proxy debug bool
proxyURL string
// retry
retryConfig *RetryConfig
// logger
logger log.CommonLogger
} }
// R raise a request from the client. // R raise a request from the client.
@ -604,19 +605,20 @@ func (c *Client) Reset() {
type Config struct { type Config struct {
Ctx context.Context //nolint:containedctx // It's needed to be stored in the config. Ctx context.Context //nolint:containedctx // It's needed to be stored in the config.
UserAgent string Body any
Referer string
Header map[string]string Header map[string]string
Param map[string]string Param map[string]string
Cookie map[string]string Cookie map[string]string
PathParam map[string]string PathParam map[string]string
FormData map[string]string
UserAgent string
Referer string
File []*File
Timeout time.Duration Timeout time.Duration
MaxRedirects int MaxRedirects int
Body any
FormData map[string]string
File []*File
} }
// setConfigToRequest Set the parameters passed via Config to Request. // setConfigToRequest Set the parameters passed via Config to Request.

View File

@ -835,8 +835,8 @@ func Test_Client_Cookie(t *testing.T) {
t.Run("set cookies with struct", func(t *testing.T) { t.Run("set cookies with struct", func(t *testing.T) {
t.Parallel() t.Parallel()
type args struct { type args struct {
CookieInt int `cookie:"int"`
CookieString string `cookie:"string"` CookieString string `cookie:"string"`
CookieInt int `cookie:"int"`
} }
req := New().SetCookiesWithStruct(&args{ req := New().SetCookiesWithStruct(&args{
@ -1087,12 +1087,12 @@ func Test_Client_QueryParam(t *testing.T) {
t.Parallel() t.Parallel()
type args struct { type args struct {
TInt int
TString string TString string
TFloat float64
TBool bool
TSlice []string TSlice []string
TIntSlice []int `param:"int_slice"` TIntSlice []int `param:"int_slice"`
TInt int
TFloat float64
TBool bool
} }
p := New() p := New()
@ -1195,8 +1195,8 @@ func Test_Client_PathParam(t *testing.T) {
t.Run("set path params with struct", func(t *testing.T) { t.Run("set path params with struct", func(t *testing.T) {
t.Parallel() t.Parallel()
type args struct { type args struct {
CookieInt int `path:"int"`
CookieString string `path:"string"` CookieString string `path:"string"`
CookieInt int `path:"int"`
} }
req := New().SetPathParamsWithStruct(&args{ req := New().SetPathParamsWithStruct(&args{

View File

@ -36,8 +36,8 @@ func ReleaseCookieJar(c *CookieJar) {
// CookieJar manages cookie storage. It is used by the client to store cookies. // CookieJar manages cookie storage. It is used by the client to store cookies.
type CookieJar struct { type CookieJar struct {
mu sync.Mutex
hostCookies map[string][]*fasthttp.Cookie hostCookies map[string][]*fasthttp.Cookie
mu sync.Mutex
} }
// Get returns the cookies stored from a specific domain. // Get returns the cookies stored from a specific domain.

View File

@ -22,8 +22,8 @@ func Test_AddMissing_Port(t *testing.T) {
} }
tests := []struct { tests := []struct {
name string name string
args args
want string want string
args args
}{ }{
{ {
name: "do anything", name: "do anything",

View File

@ -40,28 +40,30 @@ var ErrClientNil = errors.New("client can not be nil")
// Request is a struct which contains the request data. // Request is a struct which contains the request data.
type Request struct { type Request struct {
url string
method string
userAgent string
boundary string
referer string
ctx context.Context //nolint:containedctx // It's needed to be stored in the request. ctx context.Context //nolint:containedctx // It's needed to be stored in the request.
body any
header *Header header *Header
params *QueryParam params *QueryParam
cookies *Cookie cookies *Cookie
path *PathParam path *PathParam
client *Client
formData *FormData
RawRequest *fasthttp.Request
url string
method string
userAgent string
boundary string
referer string
files []*File
timeout time.Duration timeout time.Duration
maxRedirects int maxRedirects int
client *Client
body any
formData *FormData
files []*File
bodyType bodyType bodyType bodyType
RawRequest *fasthttp.Request
} }
// Method returns http method in request. // Method returns http method in request.
@ -782,10 +784,10 @@ func (f *FormData) Reset() {
// File is a struct which support send files via request. // File is a struct which support send files via request.
type File struct { type File struct {
reader io.ReadCloser
name string name string
fieldName string fieldName string
path string path string
reader io.ReadCloser
} }
// SetName method sets file name. // SetName method sets file name.

View File

@ -222,12 +222,12 @@ func Test_Request_QueryParam(t *testing.T) {
t.Parallel() t.Parallel()
type args struct { type args struct {
TInt int
TString string TString string
TFloat float64
TBool bool
TSlice []string TSlice []string
TIntSlice []int `param:"int_slice"` TIntSlice []int `param:"int_slice"`
TInt int
TFloat float64
TBool bool
} }
p := AcquireRequest() p := AcquireRequest()
@ -334,8 +334,8 @@ func Test_Request_Cookie(t *testing.T) {
t.Run("set cookies with struct", func(t *testing.T) { t.Run("set cookies with struct", func(t *testing.T) {
t.Parallel() t.Parallel()
type args struct { type args struct {
CookieInt int `cookie:"int"`
CookieString string `cookie:"string"` CookieString string `cookie:"string"`
CookieInt int `cookie:"int"`
} }
req := AcquireRequest().SetCookiesWithStruct(&args{ req := AcquireRequest().SetCookiesWithStruct(&args{
@ -396,8 +396,8 @@ func Test_Request_PathParam(t *testing.T) {
t.Run("set path params with struct", func(t *testing.T) { t.Run("set path params with struct", func(t *testing.T) {
t.Parallel() t.Parallel()
type args struct { type args struct {
CookieInt int `path:"int"`
CookieString string `path:"string"` CookieString string `path:"string"`
CookieInt int `path:"int"`
} }
req := AcquireRequest().SetPathParamsWithStruct(&args{ req := AcquireRequest().SetPathParamsWithStruct(&args{
@ -510,12 +510,12 @@ func Test_Request_FormData(t *testing.T) {
t.Parallel() t.Parallel()
type args struct { type args struct {
TInt int
TString string TString string
TFloat float64
TBool bool
TSlice []string TSlice []string
TIntSlice []int `form:"int_slice"` TIntSlice []int `form:"int_slice"`
TInt int
TFloat float64
TBool bool
} }
p := AcquireRequest() p := AcquireRequest()
@ -1299,13 +1299,13 @@ func Test_SetValWithStruct(t *testing.T) {
// test SetValWithStruct vai QueryParam struct. // test SetValWithStruct vai QueryParam struct.
type args struct { type args struct {
unexport int
TInt int
TString string TString string
TFloat float64
TBool bool
TSlice []string TSlice []string
TIntSlice []int `param:"int_slice"` TIntSlice []int `param:"int_slice"`
unexport int
TInt int
TFloat float64
TBool bool
} }
t.Run("the struct should be applied", func(t *testing.T) { t.Run("the struct should be applied", func(t *testing.T) {
@ -1453,13 +1453,13 @@ func Test_SetValWithStruct(t *testing.T) {
func Benchmark_SetValWithStruct(b *testing.B) { func Benchmark_SetValWithStruct(b *testing.B) {
// test SetValWithStruct vai QueryParam struct. // test SetValWithStruct vai QueryParam struct.
type args struct { type args struct {
unexport int
TInt int
TString string TString string
TFloat float64
TBool bool
TSlice []string TSlice []string
TIntSlice []int `param:"int_slice"` TIntSlice []int `param:"int_slice"`
unexport int
TInt int
TFloat float64
TBool bool
} }
b.Run("the struct should be applied", func(b *testing.B) { b.Run("the struct should be applied", func(b *testing.B) {

View File

@ -19,9 +19,9 @@ import (
type Response struct { type Response struct {
client *Client client *Client
request *Request request *Request
cookie []*fasthttp.Cookie
RawResponse *fasthttp.Response RawResponse *fasthttp.Response
cookie []*fasthttp.Cookie
} }
// setClient method sets client object in response instance. // setClient method sets client object in response instance.

40
ctx.go
View File

@ -50,24 +50,24 @@ const userContextKey contextKey = 0 // __local_user_context__
type DefaultCtx struct { type DefaultCtx struct {
app *App // Reference to *App app *App // Reference to *App
route *Route // Reference to *Route route *Route // Reference to *Route
indexRoute int // Index of the current route
indexHandler int // Index of the current handler
method string // HTTP method
methodINT int // HTTP method INT equivalent
baseURI string // HTTP base uri
path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer
pathBuffer []byte // HTTP path buffer
detectionPath string // Route detection path -> string copy from detectionPathBuffer
detectionPathBuffer []byte // HTTP detectionPath buffer
treePath string // Path for the search in the tree
pathOriginal string // Original HTTP path
values [maxParams]string // Route parameter values
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
matched bool // Non use route matched
viewBindMap sync.Map // Default view map to bind template engine
bind *Bind // Default bind reference bind *Bind // Default bind reference
redirect *Redirect // Default redirect reference redirect *Redirect // Default redirect reference
values [maxParams]string // Route parameter values
viewBindMap sync.Map // Default view map to bind template engine
method string // HTTP method
baseURI string // HTTP base uri
path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer
detectionPath string // Route detection path -> string copy from detectionPathBuffer
treePath string // Path for the search in the tree
pathOriginal string // Original HTTP path
pathBuffer []byte // HTTP path buffer
detectionPathBuffer []byte // HTTP detectionPath buffer
redirectionMessages []string // Messages of the previous redirect redirectionMessages []string // Messages of the previous redirect
indexRoute int // Index of the current route
indexHandler int // Index of the current handler
methodINT int // HTTP method INT equivalent
matched bool // Non use route matched
} }
// SendFile defines configuration options when to transfer file with SendFile. // SendFile defines configuration options when to transfer file with SendFile.
@ -112,8 +112,8 @@ type SendFile struct {
// sendFileStore is used to keep the SendFile configuration and the handler. // sendFileStore is used to keep the SendFile configuration and the handler.
type sendFileStore struct { type sendFileStore struct {
handler fasthttp.RequestHandler handler fasthttp.RequestHandler
config SendFile
cacheControlValue string cacheControlValue string
config SendFile
} }
// compareConfig compares the current SendFile config with the new one // compareConfig compares the current SendFile config with the new one
@ -175,15 +175,15 @@ type RangeSet struct {
// Cookie data for c.Cookie // Cookie data for c.Cookie
type Cookie struct { type Cookie struct {
Expires time.Time `json:"expires"` // The expiration date of the cookie
Name string `json:"name"` // The name of the cookie Name string `json:"name"` // The name of the cookie
Value string `json:"value"` // The value of the cookie Value string `json:"value"` // The value of the cookie
Path string `json:"path"` // Specifies a URL path which is allowed to receive the cookie Path string `json:"path"` // Specifies a URL path which is allowed to receive the cookie
Domain string `json:"domain"` // Specifies the domain which is allowed to receive the cookie Domain string `json:"domain"` // Specifies the domain which is allowed to receive the cookie
SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
MaxAge int `json:"max_age"` // The maximum age (in seconds) of the cookie MaxAge int `json:"max_age"` // The maximum age (in seconds) of the cookie
Expires time.Time `json:"expires"` // The expiration date of the cookie
Secure bool `json:"secure"` // Indicates that the cookie should only be transmitted over a secure HTTPS connection Secure bool `json:"secure"` // Indicates that the cookie should only be transmitted over a secure HTTPS connection
HTTPOnly bool `json:"http_only"` // Indicates that the cookie is accessible only through the HTTP protocol HTTPOnly bool `json:"http_only"` // Indicates that the cookie is accessible only through the HTTP protocol
SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
Partitioned bool `json:"partitioned"` // Indicates if the cookie is stored in a partitioned cookie jar Partitioned bool `json:"partitioned"` // Indicates if the cookie is stored in a partitioned cookie jar
SessionOnly bool `json:"session_only"` // Indicates if the cookie is a session-only cookie SessionOnly bool `json:"session_only"` // Indicates if the cookie is a session-only cookie
} }
@ -196,8 +196,8 @@ type Views interface {
// ResFmt associates a Content Type to a fiber.Handler for c.Format // ResFmt associates a Content Type to a fiber.Handler for c.Format
type ResFmt struct { type ResFmt struct {
MediaType string
Handler func(Ctx) error Handler func(Ctx) error
MediaType string
} }
// Accepts checks if the specified extensions or content types are acceptable. // Accepts checks if the specified extensions or content types are acceptable.
@ -1285,8 +1285,8 @@ func (c *DefaultCtx) Range(size int) (Range, error) {
Start int Start int
End int End int
}{ }{
start, Start: start,
end, End: end,
}) })
} }
if len(rangeData.Ranges) < 1 { if len(rangeData.Ranges) < 1 {

View File

@ -509,8 +509,8 @@ func Benchmark_Ctx_Body_With_Compression(b *testing.B) {
} }
) )
compressionTests := []struct { compressionTests := []struct {
contentEncoding string
compressWriter func([]byte) ([]byte, error) compressWriter func([]byte) ([]byte, error)
contentEncoding string
}{ }{
{ {
contentEncoding: "gzip", contentEncoding: "gzip",
@ -702,8 +702,8 @@ func Benchmark_Ctx_Body_With_Compression_Immutable(b *testing.B) {
} }
) )
compressionTests := []struct { compressionTests := []struct {
contentEncoding string
compressWriter func([]byte) ([]byte, error) compressWriter func([]byte) ([]byte, error)
contentEncoding string
}{ }{
{ {
contentEncoding: "gzip", contentEncoding: "gzip",
@ -966,7 +966,7 @@ func Test_Ctx_Format(t *testing.T) {
fmts := []ResFmt{} fmts := []ResFmt{}
for _, t := range types { for _, t := range types {
t := utils.CopyString(t) t := utils.CopyString(t)
fmts = append(fmts, ResFmt{t, func(_ Ctx) error { fmts = append(fmts, ResFmt{MediaType: t, Handler: func(_ Ctx) error {
accepted = t accepted = t
return nil return nil
}}) }})
@ -988,11 +988,11 @@ func Test_Ctx_Format(t *testing.T) {
require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode()) require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode())
myError := errors.New("this is an error") myError := errors.New("this is an error")
err = c.Format(ResFmt{"text/html", func(_ Ctx) error { return myError }}) err = c.Format(ResFmt{MediaType: "text/html", Handler: func(_ Ctx) error { return myError }})
require.ErrorIs(t, err, myError) require.ErrorIs(t, err, myError)
c.Request().Header.Set(HeaderAccept, "application/json") c.Request().Header.Set(HeaderAccept, "application/json")
err = c.Format(ResFmt{"text/html", func(c Ctx) error { return c.SendStatus(StatusOK) }}) err = c.Format(ResFmt{MediaType: "text/html", Handler: func(c Ctx) error { return c.SendStatus(StatusOK) }})
require.Equal(t, StatusNotAcceptable, c.Response().StatusCode()) require.Equal(t, StatusNotAcceptable, c.Response().StatusCode())
require.NoError(t, err) require.NoError(t, err)
@ -1022,10 +1022,10 @@ func Benchmark_Ctx_Format(b *testing.B) {
b.Run("with arg allocation", func(b *testing.B) { b.Run("with arg allocation", func(b *testing.B) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
err = c.Format( err = c.Format(
ResFmt{"application/xml", fail}, ResFmt{MediaType: "application/xml", Handler: fail},
ResFmt{"text/html", fail}, ResFmt{MediaType: "text/html", Handler: fail},
ResFmt{"text/plain;format=fixed", fail}, ResFmt{MediaType: "text/plain;format=fixed", Handler: fail},
ResFmt{"text/plain;format=flowed", ok}, ResFmt{MediaType: "text/plain;format=flowed", Handler: ok},
) )
} }
require.NoError(b, err) require.NoError(b, err)
@ -1033,10 +1033,10 @@ func Benchmark_Ctx_Format(b *testing.B) {
b.Run("pre-allocated args", func(b *testing.B) { b.Run("pre-allocated args", func(b *testing.B) {
offers := []ResFmt{ offers := []ResFmt{
{"application/xml", fail}, {MediaType: "application/xml", Handler: fail},
{"text/html", fail}, {MediaType: "text/html", Handler: fail},
{"text/plain;format=fixed", fail}, {MediaType: "text/plain;format=fixed", Handler: fail},
{"text/plain;format=flowed", ok}, {MediaType: "text/plain;format=flowed", Handler: ok},
} }
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
err = c.Format(offers...) err = c.Format(offers...)
@ -1047,8 +1047,8 @@ func Benchmark_Ctx_Format(b *testing.B) {
c.Request().Header.Set("Accept", "text/plain") c.Request().Header.Set("Accept", "text/plain")
b.Run("text/plain", func(b *testing.B) { b.Run("text/plain", func(b *testing.B) {
offers := []ResFmt{ offers := []ResFmt{
{"application/xml", fail}, {MediaType: "application/xml", Handler: fail},
{"text/plain", ok}, {MediaType: "text/plain", Handler: ok},
} }
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
err = c.Format(offers...) err = c.Format(offers...)
@ -1059,9 +1059,9 @@ func Benchmark_Ctx_Format(b *testing.B) {
c.Request().Header.Set("Accept", "json") c.Request().Header.Set("Accept", "json")
b.Run("json", func(b *testing.B) { b.Run("json", func(b *testing.B) {
offers := []ResFmt{ offers := []ResFmt{
{"xml", fail}, {MediaType: "xml", Handler: fail},
{"html", fail}, {MediaType: "html", Handler: fail},
{"json", ok}, {MediaType: "json", Handler: ok},
} }
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
err = c.Format(offers...) err = c.Format(offers...)
@ -1123,8 +1123,8 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Message struct { type Message struct {
Recipients []string
Sender string `xml:"sender,attr"` Sender string `xml:"sender,attr"`
Recipients []string
Urgency int `xml:"urgency,attr"` Urgency int `xml:"urgency,attr"`
} }
data := Message{ data := Message{
@ -1137,7 +1137,7 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) {
err := c.AutoFormat(data) err := c.AutoFormat(data)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, require.Equal(t,
`{"Recipients":["Alice","Bob"],"Sender":"Carol","Urgency":3}`, `{"Sender":"Carol","Recipients":["Alice","Bob"],"Urgency":3}`,
string(c.Response().Body()), string(c.Response().Body()),
) )
@ -1370,10 +1370,10 @@ func Test_Ctx_Binders(t *testing.T) {
} }
type TestStruct struct { type TestStruct struct {
TestEmbeddedStruct
Name string Name string
Class int
NameWithDefault string `json:"name2" xml:"Name2" form:"name2" cookie:"name2" query:"name2" params:"name2" header:"Name2"` NameWithDefault string `json:"name2" xml:"Name2" form:"name2" cookie:"name2" query:"name2" params:"name2" header:"Name2"`
TestEmbeddedStruct
Class int
ClassWithDefault int `json:"class2" xml:"Class2" form:"class2" cookie:"class2" query:"class2" params:"class2" header:"Class2"` ClassWithDefault int `json:"class2" xml:"Class2" form:"class2" cookie:"class2" query:"class2" params:"class2" header:"Class2"`
} }
@ -2141,11 +2141,11 @@ func Test_Ctx_Locals_GenericCustomStruct(t *testing.T) {
app := New() app := New()
app.Use(func(c Ctx) error { app.Use(func(c Ctx) error {
Locals[User](c, "user", User{"john", 18}) Locals[User](c, "user", User{name: "john", age: 18})
return c.Next() return c.Next()
}) })
app.Use("/test", func(c Ctx) error { app.Use("/test", func(c Ctx) error {
require.Equal(t, User{"john", 18}, Locals[User](c, "user")) require.Equal(t, User{name: "john", age: 18}, Locals[User](c, "user"))
return nil return nil
}) })
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil)) resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
@ -2697,13 +2697,13 @@ func Test_Ctx_Range(t *testing.T) {
testRange("bytes=") testRange("bytes=")
testRange("bytes=500=") testRange("bytes=500=")
testRange("bytes=500-300") testRange("bytes=500-300")
testRange("bytes=a-700", RangeSet{300, 999}) testRange("bytes=a-700", RangeSet{Start: 300, End: 999})
testRange("bytes=500-b", RangeSet{500, 999}) testRange("bytes=500-b", RangeSet{Start: 500, End: 999})
testRange("bytes=500-1000", RangeSet{500, 999}) testRange("bytes=500-1000", RangeSet{Start: 500, End: 999})
testRange("bytes=500-700", RangeSet{500, 700}) testRange("bytes=500-700", RangeSet{Start: 500, End: 700})
testRange("bytes=0-0,2-1000", RangeSet{0, 0}, RangeSet{2, 999}) testRange("bytes=0-0,2-1000", RangeSet{Start: 0, End: 0}, RangeSet{Start: 2, End: 999})
testRange("bytes=0-99,450-549,-100", RangeSet{0, 99}, RangeSet{450, 549}, RangeSet{900, 999}) testRange("bytes=0-99,450-549,-100", RangeSet{Start: 0, End: 99}, RangeSet{Start: 450, End: 549}, RangeSet{Start: 900, End: 999})
testRange("bytes=500-700,601-999", RangeSet{500, 700}, RangeSet{601, 999}) testRange("bytes=500-700,601-999", RangeSet{Start: 500, End: 700}, RangeSet{Start: 601, End: 999})
} }
// go test -v -run=^$ -bench=Benchmark_Ctx_Range -benchmem -count=4 // go test -v -run=^$ -bench=Benchmark_Ctx_Range -benchmem -count=4
@ -2717,10 +2717,10 @@ func Benchmark_Ctx_Range(b *testing.B) {
start int start int
end int end int
}{ }{
{"bytes=-700", 300, 999}, {str: "bytes=-700", start: 300, end: 999},
{"bytes=500-", 500, 999}, {str: "bytes=500-", start: 500, end: 999},
{"bytes=500-1000", 500, 999}, {str: "bytes=500-1000", start: 500, end: 999},
{"bytes=0-700,800-1000", 0, 700}, {str: "bytes=0-700,800-1000", start: 0, end: 700},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -3229,12 +3229,12 @@ func Test_Ctx_SendFile_Multiple(t *testing.T) {
body string body string
contentDisposition string contentDisposition string
}{ }{
{"/test?file=1", "type DefaultCtx struct", ""}, {url: "/test?file=1", body: "type DefaultCtx struct", contentDisposition: ""},
{"/test?file=2", "type App struct", ""}, {url: "/test?file=2", body: "type App struct", contentDisposition: ""},
{"/test?file=3", "type DefaultCtx struct", "attachment"}, {url: "/test?file=3", body: "type DefaultCtx struct", contentDisposition: "attachment"},
{"/test?file=4", "Test_App_MethodNotAllowed", ""}, {url: "/test?file=4", body: "Test_App_MethodNotAllowed", contentDisposition: ""},
{"/test2", "type DefaultCtx struct", "attachment"}, {url: "/test2", body: "type DefaultCtx struct", contentDisposition: "attachment"},
{"/test2", "type DefaultCtx struct", "attachment"}, {url: "/test2", body: "type DefaultCtx struct", contentDisposition: "attachment"},
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -14,9 +14,9 @@ type Group struct {
app *App app *App
parentGroup *Group parentGroup *Group
name string name string
anyRouteDefined bool
Prefix string Prefix string
anyRouteDefined bool
} }
// Name Assign name to specific route or group itself. // Name Assign name to specific route or group itself.

View File

@ -31,11 +31,11 @@ import (
// along with quality, specificity, parameters, and order. // along with quality, specificity, parameters, and order.
// Used for sorting accept headers. // Used for sorting accept headers.
type acceptedType struct { type acceptedType struct {
params headerParams
spec string spec string
quality float64 quality float64
specificity int specificity int
order int order int
params headerParams
} }
type headerParams map[string][]byte type headerParams map[string][]byte
@ -474,7 +474,7 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
} }
// Add to accepted types // Add to accepted types
acceptedTypes = append(acceptedTypes, acceptedType{utils.UnsafeString(spec), quality, specificity, order, params}) acceptedTypes = append(acceptedTypes, acceptedType{spec: utils.UnsafeString(spec), quality: quality, specificity: specificity, order: order, params: params})
}) })
if len(acceptedTypes) > 1 { if len(acceptedTypes) > 1 {

View File

@ -483,9 +483,9 @@ func Test_Utils_Parse_Address(t *testing.T) {
testCases := []struct { testCases := []struct {
addr, host, port string addr, host, port string
}{ }{
{"[::1]:3000", "[::1]", "3000"}, {addr: "[::1]:3000", host: "[::1]", port: "3000"},
{"127.0.0.1:3000", "127.0.0.1", "3000"}, {addr: "127.0.0.1:3000", host: "127.0.0.1", port: "3000"},
{"/path/to/unix/socket", "/path/to/unix/socket", ""}, {addr: "/path/to/unix/socket", host: "/path/to/unix/socket", port: ""},
} }
for _, c := range testCases { for _, c := range testCases {
@ -509,14 +509,14 @@ func Test_Utils_IsNoCache(t *testing.T) {
string string
bool bool
}{ }{
{"public", false}, {string: "public", bool: false},
{"no-cache", true}, {string: "no-cache", bool: true},
{"public, no-cache, max-age=30", true}, {string: "public, no-cache, max-age=30", bool: true},
{"public,no-cache", true}, {string: "public,no-cache", bool: true},
{"public,no-cacheX", false}, {string: "public,no-cacheX", bool: false},
{"no-cache, public", true}, {string: "no-cache, public", bool: true},
{"Xno-cache, public", false}, {string: "Xno-cache, public", bool: false},
{"max-age=30, no-cache,public", true}, {string: "max-age=30, no-cache,public", bool: true},
} }
for _, c := range testCases { for _, c := range testCases {

View File

@ -10,14 +10,14 @@ import (
) )
type Storage struct { type Storage struct {
sync.RWMutex
data map[string]item // data data map[string]item // data
sync.RWMutex
} }
type item struct { type item struct {
v any // val
// max value is 4294967295 -> Sun Feb 07 2106 06:28:15 GMT+0000 // max value is 4294967295 -> Sun Feb 07 2106 06:28:15 GMT+0000
e uint32 // exp e uint32 // exp
v any // val
} }
func New() *Storage { func New() *Storage {
@ -46,7 +46,7 @@ func (s *Storage) Set(key string, val any, ttl time.Duration) {
if ttl > 0 { if ttl > 0 {
exp = uint32(ttl.Seconds()) + utils.Timestamp() exp = uint32(ttl.Seconds()) + utils.Timestamp()
} }
i := item{exp, val} i := item{e: exp, v: val}
s.Lock() s.Lock()
s.data[key] = i s.data[key] = i
s.Unlock() s.Unlock()

View File

@ -26,10 +26,10 @@ func newCache() *cache {
// cache caches meta-data about a struct. // cache caches meta-data about a struct.
type cache struct { type cache struct {
l sync.RWMutex
m map[reflect.Type]*structInfo m map[reflect.Type]*structInfo
regconv map[reflect.Type]Converter regconv map[reflect.Type]Converter
tag string tag string
l sync.RWMutex
} }
// registerConverter registers a converter function for a custom type. // registerConverter registers a converter function for a custom type.

View File

@ -462,10 +462,10 @@ type unmarshaler struct {
// ConversionError stores information about a failed conversion. // ConversionError stores information about a failed conversion.
type ConversionError struct { type ConversionError struct {
Key string // key from the source map.
Type reflect.Type // expected type of elem Type reflect.Type // expected type of elem
Index int // index for multi-value fields; -1 for single-value fields.
Err error // low-level error (when it exists) Err error // low-level error (when it exists)
Key string // key from the source map.
Index int // index for multi-value fields; -1 for single-value fields.
} }
func (e ConversionError) Error() string { func (e ConversionError) Error() string {

View File

@ -11,10 +11,10 @@ import (
// Storage interface that is implemented by storage providers // Storage interface that is implemented by storage providers
type Storage struct { type Storage struct {
mux sync.RWMutex
db map[string]entry db map[string]entry
gcInterval time.Duration
done chan struct{} done chan struct{}
gcInterval time.Duration
mux sync.RWMutex
} }
type entry struct { type entry struct {
@ -69,7 +69,7 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
expire = uint32(exp.Seconds()) + utils.Timestamp() expire = uint32(exp.Seconds()) + utils.Timestamp()
} }
e := entry{val, expire} e := entry{data: val, expiry: expire}
s.mux.Lock() s.mux.Lock()
s.db[key] = e s.db[key] = e
s.mux.Unlock() s.mux.Unlock()

View File

@ -40,6 +40,36 @@ const (
// //
// TODO: Add timeout for graceful shutdown. // TODO: Add timeout for graceful shutdown.
type ListenConfig struct { type ListenConfig struct {
// GracefulContext is a field to shutdown Fiber by given context gracefully.
//
// Default: nil
GracefulContext context.Context `json:"graceful_context"` //nolint:containedctx // It's needed to set context inside Listen.
// TLSConfigFunc allows customizing tls.Config as you want.
//
// Default: nil
TLSConfigFunc func(tlsConfig *tls.Config) `json:"tls_config_func"`
// ListenerFunc allows accessing and customizing net.Listener.
//
// Default: nil
ListenerAddrFunc func(addr net.Addr) `json:"listener_addr_func"`
// BeforeServeFunc allows customizing and accessing fiber app before serving the app.
//
// Default: nil
BeforeServeFunc func(app *App) error `json:"before_serve_func"`
// OnShutdownError allows to customize error behavior when to graceful shutdown server by given signal.
//
// Print error with log.Fatalf() by default.
// Default: nil
OnShutdownError func(err error)
// OnShutdownSuccess allows to customize success behavior when to graceful shutdown server by given signal.
//
// Default: nil
OnShutdownSuccess func()
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only) // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only)
// WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen. // WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen.
// //
@ -64,26 +94,6 @@ type ListenConfig struct {
// Default : "" // Default : ""
CertClientFile string `json:"cert_client_file"` CertClientFile string `json:"cert_client_file"`
// GracefulContext is a field to shutdown Fiber by given context gracefully.
//
// Default: nil
GracefulContext context.Context `json:"graceful_context"` //nolint:containedctx // It's needed to set context inside Listen.
// TLSConfigFunc allows customizing tls.Config as you want.
//
// Default: nil
TLSConfigFunc func(tlsConfig *tls.Config) `json:"tls_config_func"`
// ListenerFunc allows accessing and customizing net.Listener.
//
// Default: nil
ListenerAddrFunc func(addr net.Addr) `json:"listener_addr_func"`
// BeforeServeFunc allows customizing and accessing fiber app before serving the app.
//
// Default: nil
BeforeServeFunc func(app *App) error `json:"before_serve_func"`
// When set to true, it will not print out the «Fiber» ASCII art and listening address. // When set to true, it will not print out the «Fiber» ASCII art and listening address.
// //
// Default: false // Default: false
@ -98,17 +108,6 @@ type ListenConfig struct {
// //
// Default: false // Default: false
EnablePrintRoutes bool `json:"enable_print_routes"` EnablePrintRoutes bool `json:"enable_print_routes"`
// OnShutdownError allows to customize error behavior when to graceful shutdown server by given signal.
//
// Print error with log.Fatalf() by default.
// Default: nil
OnShutdownError func(err error)
// OnShutdownSuccess allows to customize success behavior when to graceful shutdown server by given signal.
//
// Default: nil
OnShutdownSuccess func()
} }
// listenConfigDefault is a function to set default values of ListenConfig. // listenConfigDefault is a function to set default values of ListenConfig.

View File

@ -79,10 +79,10 @@ func Test_Listen_Graceful_Shutdown(t *testing.T) {
} }
testCases := []struct { testCases := []struct {
Time time.Duration
ExpectedBody string
ExpectedStatusCode int
ExpectedErr error ExpectedErr error
ExpectedBody string
Time time.Duration
ExpectedStatusCode int
}{ }{
{Time: 500 * time.Millisecond, ExpectedBody: "example.com", ExpectedStatusCode: StatusOK, ExpectedErr: nil}, {Time: 500 * time.Millisecond, ExpectedBody: "example.com", ExpectedStatusCode: StatusOK, ExpectedErr: nil},
{Time: 3 * time.Second, ExpectedBody: "", ExpectedStatusCode: StatusOK, ExpectedErr: errors.New("InmemoryListener is already closed: use of closed network connection")}, {Time: 3 * time.Second, ExpectedBody: "", ExpectedStatusCode: StatusOK, ExpectedErr: errors.New("InmemoryListener is already closed: use of closed network connection")},

View File

@ -121,11 +121,11 @@ func Test_CtxLogger(t *testing.T) {
func Test_LogfKeyAndValues(t *testing.T) { func Test_LogfKeyAndValues(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
level Level
format string format string
wantOutput string
fmtArgs []any fmtArgs []any
keysAndValues []any keysAndValues []any
wantOutput string level Level
}{ }{
{ {
name: "test logf with debug level and key-values", name: "test logf with debug level and key-values",
@ -310,9 +310,9 @@ func Test_Tracew(t *testing.T) {
func Benchmark_LogfKeyAndValues(b *testing.B) { func Benchmark_LogfKeyAndValues(b *testing.B) {
tests := []struct { tests := []struct {
name string name string
level Level
format string format string
keysAndValues []any keysAndValues []any
level Level
}{ }{
{ {
name: "test logf with debug level and key-values", name: "test logf with debug level and key-values",
@ -368,9 +368,9 @@ func Benchmark_LogfKeyAndValues(b *testing.B) {
func Benchmark_LogfKeyAndValues_Parallel(b *testing.B) { func Benchmark_LogfKeyAndValues_Parallel(b *testing.B) {
tests := []struct { tests := []struct {
name string name string
level Level
format string format string
keysAndValues []any keysAndValues []any
level Level
}{ }{
{ {
name: "debug level with key-values", name: "debug level with key-values",

View File

@ -274,7 +274,7 @@ func testFiberToHandlerFunc(t *testing.T, checkDefaultPort bool, app ...*fiber.A
var r http.Request var r http.Request
r.Method = expectedMethod r.Method = expectedMethod
r.Body = &netHTTPBody{[]byte(expectedBody)} r.Body = &netHTTPBody{b: []byte(expectedBody)}
r.RequestURI = expectedRequestURI r.RequestURI = expectedRequestURI
r.ContentLength = int64(expectedContentLength) r.ContentLength = int64(expectedContentLength)
r.Host = expectedHost r.Host = expectedHost
@ -355,9 +355,9 @@ func (r *netHTTPBody) Close() error {
} }
type netHTTPResponseWriter struct { type netHTTPResponseWriter struct {
statusCode int
h http.Header h http.Header
body []byte body []byte
statusCode int
} }
func (w *netHTTPResponseWriter) StatusCode() int { func (w *netHTTPResponseWriter) StatusCode() int {

View File

@ -47,9 +47,9 @@ func Test_Middleware_BasicAuth(t *testing.T) {
tests := []struct { tests := []struct {
url string url string
statusCode int
username string username string
password string password string
statusCode int
}{ }{
{ {
url: "/testauth", url: "/testauth",

View File

@ -19,13 +19,6 @@ type Config struct {
// Required. Default: map[string]string{} // Required. Default: map[string]string{}
Users map[string]string Users map[string]string
// Realm is a string to define realm attribute of BasicAuth.
// the realm identifies the system to authenticate against
// and can be used by clients to save credentials
//
// Optional. Default: "Restricted".
Realm string
// Authorizer defines a function you can pass // Authorizer defines a function you can pass
// to check the credentials however you want. // to check the credentials however you want.
// It will be called with a username and password // It will be called with a username and password
@ -40,6 +33,13 @@ type Config struct {
// //
// Optional. Default: nil // Optional. Default: nil
Unauthorized fiber.Handler Unauthorized fiber.Handler
// Realm is a string to define realm attribute of BasicAuth.
// the realm identifies the system to authenticate against
// and can be used by clients to save credentials
//
// Optional. Default: "Restricted".
Realm string
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -9,28 +9,16 @@ import (
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { type Config struct {
// Store is used to store the state of the middleware
//
// Default: an in memory store for this process only
Storage fiber.Storage
// Next defines a function to skip this middleware when returned true. // Next defines a function to skip this middleware when returned true.
// //
// Optional. Default: nil // Optional. Default: nil
Next func(c fiber.Ctx) bool Next func(c fiber.Ctx) bool
// Expiration is the time that an cached response will live
//
// Optional. Default: 1 * time.Minute
Expiration time.Duration
// CacheHeader header on response header, indicate cache status, with the following possible return value
//
// hit, miss, unreachable
//
// Optional. Default: X-Cache
CacheHeader string
// CacheControl enables client side caching if set to true
//
// Optional. Default: false
CacheControl bool
// CacheInvalidator defines a function to invalidate the cache when returned true // CacheInvalidator defines a function to invalidate the cache when returned true
// //
// Optional. Default: nil // Optional. Default: nil
@ -48,15 +36,23 @@ type Config struct {
// Default: nil // Default: nil
ExpirationGenerator func(fiber.Ctx, *Config) time.Duration ExpirationGenerator func(fiber.Ctx, *Config) time.Duration
// Store is used to store the state of the middleware // CacheHeader header on response header, indicate cache status, with the following possible return value
// //
// Default: an in memory store for this process only // hit, miss, unreachable
Storage fiber.Storage //
// Optional. Default: X-Cache
CacheHeader string
// allows you to store additional headers generated by next middlewares & handler // You can specify HTTP methods to cache.
// The middleware just caches the routes of its methods in this slice.
// //
// Default: false // Default: []string{fiber.MethodGet, fiber.MethodHead}
StoreResponseHeaders bool Methods []string
// Expiration is the time that an cached response will live
//
// Optional. Default: 1 * time.Minute
Expiration time.Duration
// Max number of bytes of response bodies simultaneously stored in cache. When limit is reached, // Max number of bytes of response bodies simultaneously stored in cache. When limit is reached,
// entries with the nearest expiration are deleted to make room for new. // entries with the nearest expiration are deleted to make room for new.
@ -65,11 +61,15 @@ type Config struct {
// Default: 0 // Default: 0
MaxBytes uint MaxBytes uint
// You can specify HTTP methods to cache. // CacheControl enables client side caching if set to true
// The middleware just caches the routes of its methods in this slice.
// //
// Default: []string{fiber.MethodGet, fiber.MethodHead} // Optional. Default: false
Methods []string CacheControl bool
// allows you to store additional headers generated by next middlewares & handler
//
// Default: false
StoreResponseHeaders bool
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -11,12 +11,12 @@ import (
// go:generate msgp // go:generate msgp
// msgp -file="manager.go" -o="manager_msgp.go" -tests=false -unexported // msgp -file="manager.go" -o="manager_msgp.go" -tests=false -unexported
type item struct { type item struct {
headers map[string][]byte
body []byte body []byte
ctype []byte ctype []byte
cencoding []byte cencoding []byte
status int status int
exp uint64 exp uint64
headers map[string][]byte
// used for finding the item in an indexed heap // used for finding the item in an indexed heap
heapidx int heapidx int
} }

View File

@ -228,10 +228,10 @@ func Benchmark_Compress(b *testing.B) {
name string name string
acceptEncoding string acceptEncoding string
}{ }{
{"Gzip", "gzip"}, {name: "Gzip", acceptEncoding: "gzip"},
{"Deflate", "deflate"}, {name: "Deflate", acceptEncoding: "deflate"},
{"Brotli", "br"}, {name: "Brotli", acceptEncoding: "br"},
{"Zstd", "zstd"}, {name: "Zstd", acceptEncoding: "zstd"},
} }
for _, tt := range tests { for _, tt := range tests {
@ -268,20 +268,20 @@ func Benchmark_Compress_Levels(b *testing.B) {
name string name string
acceptEncoding string acceptEncoding string
}{ }{
{"Gzip", "gzip"}, {name: "Gzip", acceptEncoding: "gzip"},
{"Deflate", "deflate"}, {name: "Deflate", acceptEncoding: "deflate"},
{"Brotli", "br"}, {name: "Brotli", acceptEncoding: "br"},
{"Zstd", "zstd"}, {name: "Zstd", acceptEncoding: "zstd"},
} }
levels := []struct { levels := []struct {
name string name string
level Level level Level
}{ }{
{"LevelDisabled", LevelDisabled}, {name: "LevelDisabled", level: LevelDisabled},
{"LevelDefault", LevelDefault}, {name: "LevelDefault", level: LevelDefault},
{"LevelBestSpeed", LevelBestSpeed}, {name: "LevelBestSpeed", level: LevelBestSpeed},
{"LevelBestCompression", LevelBestCompression}, {name: "LevelBestCompression", level: LevelBestCompression},
} }
for _, tt := range tests { for _, tt := range tests {
@ -320,10 +320,10 @@ func Benchmark_Compress_Parallel(b *testing.B) {
name string name string
acceptEncoding string acceptEncoding string
}{ }{
{"Gzip", "gzip"}, {name: "Gzip", acceptEncoding: "gzip"},
{"Deflate", "deflate"}, {name: "Deflate", acceptEncoding: "deflate"},
{"Brotli", "br"}, {name: "Brotli", acceptEncoding: "br"},
{"Zstd", "zstd"}, {name: "Zstd", acceptEncoding: "zstd"},
} }
for _, tt := range tests { for _, tt := range tests {
@ -363,20 +363,20 @@ func Benchmark_Compress_Levels_Parallel(b *testing.B) {
name string name string
acceptEncoding string acceptEncoding string
}{ }{
{"Gzip", "gzip"}, {name: "Gzip", acceptEncoding: "gzip"},
{"Deflate", "deflate"}, {name: "Deflate", acceptEncoding: "deflate"},
{"Brotli", "br"}, {name: "Brotli", acceptEncoding: "br"},
{"Zstd", "zstd"}, {name: "Zstd", acceptEncoding: "zstd"},
} }
levels := []struct { levels := []struct {
name string name string
level Level level Level
}{ }{
{"LevelDisabled", LevelDisabled}, {name: "LevelDisabled", level: LevelDisabled},
{"LevelDefault", LevelDefault}, {name: "LevelDefault", level: LevelDefault},
{"LevelBestSpeed", LevelBestSpeed}, {name: "LevelBestSpeed", level: LevelBestSpeed},
{"LevelBestCompression", LevelBestCompression}, {name: "LevelBestCompression", level: LevelBestCompression},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -41,15 +41,6 @@ type Config struct {
// Optional. Default value []string{} // Optional. Default value []string{}
AllowHeaders []string AllowHeaders []string
// AllowCredentials indicates whether or not the response to the request
// can be exposed when the credentials flag is true. When used as part of
// a response to a preflight request, this indicates whether or not the
// actual request can be made using credentials. Note: If true, AllowOrigins
// cannot be set to true to prevent security vulnerabilities.
//
// Optional. Default value false.
AllowCredentials bool
// ExposeHeaders defines a whitelist headers that clients are allowed to // ExposeHeaders defines a whitelist headers that clients are allowed to
// access. // access.
// //
@ -65,6 +56,15 @@ type Config struct {
// Optional. Default value 0. // Optional. Default value 0.
MaxAge int MaxAge int
// AllowCredentials indicates whether or not the response to the request
// can be exposed when the credentials flag is true. When used as part of
// a response to a preflight request, this indicates whether or not the
// actual request can be made using credentials. Note: If true, AllowOrigins
// cannot be set to true to prevent security vulnerabilities.
//
// Optional. Default value false.
AllowCredentials bool
// AllowPrivateNetwork indicates whether the Access-Control-Allow-Private-Network // AllowPrivateNetwork indicates whether the Access-Control-Allow-Private-Network
// response header should be set to true, allowing requests from private networks. // response header should be set to true, allowing requests from private networks.
// //

View File

@ -326,8 +326,8 @@ func Test_CORS_Subdomain(t *testing.T) {
func Test_CORS_AllowOriginScheme(t *testing.T) { func Test_CORS_AllowOriginScheme(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
pattern []string
reqOrigin string reqOrigin string
pattern []string
shouldAllowOrigin bool shouldAllowOrigin bool
}{ }{
{ {
@ -682,9 +682,9 @@ func Test_CORS_AllowOriginsFunc(t *testing.T) {
func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) { func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) {
testCases := []struct { testCases := []struct {
Name string Name string
Config Config
RequestOrigin string RequestOrigin string
ResponseOrigin string ResponseOrigin string
Config Config
}{ }{
{ {
Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/OriginAllowed", Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/OriginAllowed",
@ -829,10 +829,10 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) {
func Test_CORS_AllowCredentials(t *testing.T) { func Test_CORS_AllowCredentials(t *testing.T) {
testCases := []struct { testCases := []struct {
Name string Name string
Config Config
RequestOrigin string RequestOrigin string
ResponseOrigin string ResponseOrigin string
ResponseCredentials string ResponseCredentials string
Config Config
}{ }{
{ {
Name: "AllowOriginsFuncDefined", Name: "AllowOriginsFuncDefined",

View File

@ -10,33 +10,33 @@ import (
func Test_NormalizeOrigin(t *testing.T) { func Test_NormalizeOrigin(t *testing.T) {
testCases := []struct { testCases := []struct {
origin string origin string
expectedValid bool
expectedOrigin string expectedOrigin string
expectedValid bool
}{ }{
{"http://example.com", true, "http://example.com"}, // Simple case should work. {origin: "http://example.com", expectedValid: true, expectedOrigin: "http://example.com"}, // Simple case should work.
{"http://example.com/", true, "http://example.com"}, // Trailing slash should be removed. {origin: "http://example.com/", expectedValid: true, expectedOrigin: "http://example.com"}, // Trailing slash should be removed.
{"http://example.com:3000", true, "http://example.com:3000"}, // Port should be preserved. {origin: "http://example.com:3000", expectedValid: true, expectedOrigin: "http://example.com:3000"}, // Port should be preserved.
{"http://example.com:3000/", true, "http://example.com:3000"}, // Trailing slash should be removed. {origin: "http://example.com:3000/", expectedValid: true, expectedOrigin: "http://example.com:3000"}, // Trailing slash should be removed.
{"http://", false, ""}, // Invalid origin should not be accepted. {origin: "http://", expectedValid: false, expectedOrigin: ""}, // Invalid origin should not be accepted.
{"file:///etc/passwd", false, ""}, // File scheme should not be accepted. {origin: "file:///etc/passwd", expectedValid: false, expectedOrigin: ""}, // File scheme should not be accepted.
{"https://*example.com", false, ""}, // Wildcard domain should not be accepted. {origin: "https://*example.com", expectedValid: false, expectedOrigin: ""}, // Wildcard domain should not be accepted.
{"http://*.example.com", false, ""}, // Wildcard subdomain should not be accepted. {origin: "http://*.example.com", expectedValid: false, expectedOrigin: ""}, // Wildcard subdomain should not be accepted.
{"http://example.com/path", false, ""}, // Path should not be accepted. {origin: "http://example.com/path", expectedValid: false, expectedOrigin: ""}, // Path should not be accepted.
{"http://example.com?query=123", false, ""}, // Query should not be accepted. {origin: "http://example.com?query=123", expectedValid: false, expectedOrigin: ""}, // Query should not be accepted.
{"http://example.com#fragment", false, ""}, // Fragment should not be accepted. {origin: "http://example.com#fragment", expectedValid: false, expectedOrigin: ""}, // Fragment should not be accepted.
{"http://localhost", true, "http://localhost"}, // Localhost should be accepted. {origin: "http://localhost", expectedValid: true, expectedOrigin: "http://localhost"}, // Localhost should be accepted.
{"http://127.0.0.1", true, "http://127.0.0.1"}, // IPv4 address should be accepted. {origin: "http://127.0.0.1", expectedValid: true, expectedOrigin: "http://127.0.0.1"}, // IPv4 address should be accepted.
{"http://[::1]", true, "http://[::1]"}, // IPv6 address should be accepted. {origin: "http://[::1]", expectedValid: true, expectedOrigin: "http://[::1]"}, // IPv6 address should be accepted.
{"http://[::1]:8080", true, "http://[::1]:8080"}, // IPv6 address with port should be accepted. {origin: "http://[::1]:8080", expectedValid: true, expectedOrigin: "http://[::1]:8080"}, // IPv6 address with port should be accepted.
{"http://[::1]:8080/", true, "http://[::1]:8080"}, // IPv6 address with port and trailing slash should be accepted. {origin: "http://[::1]:8080/", expectedValid: true, expectedOrigin: "http://[::1]:8080"}, // IPv6 address with port and trailing slash should be accepted.
{"http://[::1]:8080/path", false, ""}, // IPv6 address with port and path should not be accepted. {origin: "http://[::1]:8080/path", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and path should not be accepted.
{"http://[::1]:8080?query=123", false, ""}, // IPv6 address with port and query should not be accepted. {origin: "http://[::1]:8080?query=123", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and query should not be accepted.
{"http://[::1]:8080#fragment", false, ""}, // IPv6 address with port and fragment should not be accepted. {origin: "http://[::1]:8080#fragment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and fragment should not be accepted.
{"http://[::1]:8080/path?query=123#fragment", false, ""}, // IPv6 address with port, path, query, and fragment should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, and fragment should not be accepted.
{"http://[::1]:8080/path?query=123#fragment/", false, ""}, // IPv6 address with port, path, query, fragment, and trailing slash should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment/", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, and trailing slash should not be accepted.
{"http://[::1]:8080/path?query=123#fragment/invalid", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment/invalid", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment should not be accepted.
{"http://[::1]:8080/path?query=123#fragment/invalid/", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with trailing slash should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment/invalid/", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with trailing slash should not be accepted.
{"http://[::1]:8080/path?query=123#fragment/invalid/segment", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with additional segment should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment/invalid/segment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with additional segment should not be accepted.
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -59,16 +59,16 @@ func Test_MatchScheme(t *testing.T) {
pattern string pattern string
expected bool expected bool
}{ }{
{"http://example.com", "http://example.com", true}, // Exact match should work. {domain: "http://example.com", pattern: "http://example.com", expected: true}, // Exact match should work.
{"https://example.com", "http://example.com", false}, // Scheme mismatch should matter. {domain: "https://example.com", pattern: "http://example.com", expected: false}, // Scheme mismatch should matter.
{"http://example.com", "https://example.com", false}, // Scheme mismatch should matter. {domain: "http://example.com", pattern: "https://example.com", expected: false}, // Scheme mismatch should matter.
{"http://example.com", "http://example.org", true}, // Different domains should not matter. {domain: "http://example.com", pattern: "http://example.org", expected: true}, // Different domains should not matter.
{"http://example.com", "http://example.com:8080", true}, // Port should not matter. {domain: "http://example.com", pattern: "http://example.com:8080", expected: true}, // Port should not matter.
{"http://example.com:8080", "http://example.com", true}, // Port should not matter. {domain: "http://example.com:8080", pattern: "http://example.com", expected: true}, // Port should not matter.
{"http://example.com:8080", "http://example.com:8081", true}, // Different ports should not matter. {domain: "http://example.com:8080", pattern: "http://example.com:8081", expected: true}, // Different ports should not matter.
{"http://localhost", "http://localhost", true}, // Localhost should match. {domain: "http://localhost", pattern: "http://localhost", expected: true}, // Localhost should match.
{"http://127.0.0.1", "http://127.0.0.1", true}, // IPv4 address should match. {domain: "http://127.0.0.1", pattern: "http://127.0.0.1", expected: true}, // IPv4 address should match.
{"http://[::1]", "http://[::1]", true}, // IPv6 address should match. {domain: "http://[::1]", pattern: "http://[::1]", expected: true}, // IPv6 address should match.
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -86,20 +86,20 @@ func Test_NormalizeDomain(t *testing.T) {
input string input string
expectedOutput string expectedOutput string
}{ }{
{"http://example.com", "example.com"}, // Simple case with http scheme. {input: "http://example.com", expectedOutput: "example.com"}, // Simple case with http scheme.
{"https://example.com", "example.com"}, // Simple case with https scheme. {input: "https://example.com", expectedOutput: "example.com"}, // Simple case with https scheme.
{"http://example.com:3000", "example.com"}, // Case with port. {input: "http://example.com:3000", expectedOutput: "example.com"}, // Case with port.
{"https://example.com:3000", "example.com"}, // Case with port and https scheme. {input: "https://example.com:3000", expectedOutput: "example.com"}, // Case with port and https scheme.
{"http://example.com/path", "example.com/path"}, // Case with path. {input: "http://example.com/path", expectedOutput: "example.com/path"}, // Case with path.
{"http://example.com?query=123", "example.com?query=123"}, // Case with query. {input: "http://example.com?query=123", expectedOutput: "example.com?query=123"}, // Case with query.
{"http://example.com#fragment", "example.com#fragment"}, // Case with fragment. {input: "http://example.com#fragment", expectedOutput: "example.com#fragment"}, // Case with fragment.
{"example.com", "example.com"}, // Case without scheme. {input: "example.com", expectedOutput: "example.com"}, // Case without scheme.
{"example.com:8080", "example.com"}, // Case without scheme but with port. {input: "example.com:8080", expectedOutput: "example.com"}, // Case without scheme but with port.
{"sub.example.com", "sub.example.com"}, // Case with subdomain. {input: "sub.example.com", expectedOutput: "sub.example.com"}, // Case with subdomain.
{"sub.sub.example.com", "sub.sub.example.com"}, // Case with nested subdomain. {input: "sub.sub.example.com", expectedOutput: "sub.sub.example.com"}, // Case with nested subdomain.
{"http://localhost", "localhost"}, // Case with localhost. {input: "http://localhost", expectedOutput: "localhost"}, // Case with localhost.
{"http://127.0.0.1", "127.0.0.1"}, // Case with IPv4 address. {input: "http://127.0.0.1", expectedOutput: "127.0.0.1"}, // Case with IPv4 address.
{"http://[::1]", "[::1]"}, // Case with IPv6 address. {input: "http://[::1]", expectedOutput: "[::1]"}, // Case with IPv6 address.
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -13,11 +13,40 @@ import (
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { type Config struct {
// Store is used to store the state of the middleware
//
// Optional. Default: memory.New()
// Ignored if Session is set.
Storage fiber.Storage
// Next defines a function to skip this middleware when returned true. // Next defines a function to skip this middleware when returned true.
// //
// Optional. Default: nil // Optional. Default: nil
Next func(c fiber.Ctx) bool Next func(c fiber.Ctx) bool
// Session is used to store the state of the middleware
//
// Optional. Default: nil
// If set, the middleware will use the session store instead of the storage
Session *session.Store
// KeyGenerator creates a new CSRF token
//
// Optional. Default: utils.UUID
KeyGenerator func() string
// ErrorHandler is executed when an error is returned from fiber.Handler.
//
// Optional. Default: DefaultErrorHandler
ErrorHandler fiber.ErrorHandler
// Extractor returns the csrf token
//
// If set this will be used in place of an Extractor based on KeyLookup.
//
// Optional. Default will create an Extractor based on KeyLookup.
Extractor func(c fiber.Ctx) (string, error)
// KeyLookup is a string in the form of "<source>:<key>" that is used // KeyLookup is a string in the form of "<source>:<key>" that is used
// to create an Extractor that extracts the token from the request. // to create an Extractor that extracts the token from the request.
// Possible values: // Possible values:
@ -45,45 +74,10 @@ type Config struct {
// Optional. Default value "". // Optional. Default value "".
CookiePath string CookiePath string
// Indicates if CSRF cookie is secure.
// Optional. Default value false.
CookieSecure bool
// Indicates if CSRF cookie is HTTP only.
// Optional. Default value false.
CookieHTTPOnly bool
// Value of SameSite cookie. // Value of SameSite cookie.
// Optional. Default value "Lax". // Optional. Default value "Lax".
CookieSameSite string CookieSameSite string
// Decides whether cookie should last for only the browser sesison.
// Ignores Expiration if set to true
CookieSessionOnly bool
// Expiration is the duration before csrf token will expire
//
// Optional. Default: 1 * time.Hour
Expiration time.Duration
// SingleUseToken indicates if the CSRF token be destroyed
// and a new one generated on each use.
//
// Optional. Default: false
SingleUseToken bool
// Store is used to store the state of the middleware
//
// Optional. Default: memory.New()
// Ignored if Session is set.
Storage fiber.Storage
// Session is used to store the state of the middleware
//
// Optional. Default: nil
// If set, the middleware will use the session store instead of the storage
Session *session.Store
// SessionKey is the key used to store the token in the session // SessionKey is the key used to store the token in the session
// //
// Default: "csrfToken" // Default: "csrfToken"
@ -102,22 +96,28 @@ type Config struct {
// Optional. Default: [] // Optional. Default: []
TrustedOrigins []string TrustedOrigins []string
// KeyGenerator creates a new CSRF token // Expiration is the duration before csrf token will expire
// //
// Optional. Default: utils.UUID // Optional. Default: 1 * time.Hour
KeyGenerator func() string Expiration time.Duration
// ErrorHandler is executed when an error is returned from fiber.Handler. // Indicates if CSRF cookie is secure.
// // Optional. Default value false.
// Optional. Default: DefaultErrorHandler CookieSecure bool
ErrorHandler fiber.ErrorHandler
// Extractor returns the csrf token // Indicates if CSRF cookie is HTTP only.
// Optional. Default value false.
CookieHTTPOnly bool
// Decides whether cookie should last for only the browser sesison.
// Ignores Expiration if set to true
CookieSessionOnly bool
// SingleUseToken indicates if the CSRF token be destroyed
// and a new one generated on each use.
// //
// If set this will be used in place of an Extractor based on KeyLookup. // Optional. Default: false
// SingleUseToken bool
// Optional. Default will create an Extractor based on KeyLookup.
Extractor func(c fiber.Ctx) (string, error)
} }
const HeaderName = "X-Csrf-Token" const HeaderName = "X-Csrf-Token"

View File

@ -24,9 +24,9 @@ var (
// Handler for CSRF middleware // Handler for CSRF middleware
type Handler struct { type Handler struct {
config Config
sessionManager *sessionManager sessionManager *sessionManager
storageManager *storageManager storageManager *storageManager
config Config
} }
// The contextKey type is unexported to prevent collisions with context keys defined in // The contextKey type is unexported to prevent collisions with context keys defined in

View File

@ -900,13 +900,13 @@ func Test_CSRF_TrustedOrigins_InvalidOrigins(t *testing.T) {
name string name string
origin string origin string
}{ }{
{"No Scheme", "localhost"}, {name: "No Scheme", origin: "localhost"},
{"Wildcard", "https://*"}, {name: "Wildcard", origin: "https://*"},
{"Wildcard domain", "https://*example.com"}, {name: "Wildcard domain", origin: "https://*example.com"},
{"File Scheme", "file://example.com"}, {name: "File Scheme", origin: "file://example.com"},
{"FTP Scheme", "ftp://example.com"}, {name: "FTP Scheme", origin: "ftp://example.com"},
{"Port Wildcard", "http://example.com:*"}, {name: "Port Wildcard", origin: "http://example.com:*"},
{"Multiple Wildcards", "https://*.*.com"}, {name: "Multiple Wildcards", origin: "https://*.*.com"},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -10,34 +10,34 @@ import (
func Test_normalizeOrigin(t *testing.T) { func Test_normalizeOrigin(t *testing.T) {
testCases := []struct { testCases := []struct {
origin string origin string
expectedValid bool
expectedOrigin string expectedOrigin string
expectedValid bool
}{ }{
{"http://example.com", true, "http://example.com"}, // Simple case should work. {origin: "http://example.com", expectedValid: true, expectedOrigin: "http://example.com"}, // Simple case should work.
{"HTTP://EXAMPLE.COM", true, "http://example.com"}, // Case should be normalized. {origin: "HTTP://EXAMPLE.COM", expectedValid: true, expectedOrigin: "http://example.com"}, // Case should be normalized.
{"http://example.com/", true, "http://example.com"}, // Trailing slash should be removed. {origin: "http://example.com/", expectedValid: true, expectedOrigin: "http://example.com"}, // Trailing slash should be removed.
{"http://example.com:3000", true, "http://example.com:3000"}, // Port should be preserved. {origin: "http://example.com:3000", expectedValid: true, expectedOrigin: "http://example.com:3000"}, // Port should be preserved.
{"http://example.com:3000/", true, "http://example.com:3000"}, // Trailing slash should be removed. {origin: "http://example.com:3000/", expectedValid: true, expectedOrigin: "http://example.com:3000"}, // Trailing slash should be removed.
{"http://", false, ""}, // Invalid origin should not be accepted. {origin: "http://", expectedValid: false, expectedOrigin: ""}, // Invalid origin should not be accepted.
{"file:///etc/passwd", false, ""}, // File scheme should not be accepted. {origin: "file:///etc/passwd", expectedValid: false, expectedOrigin: ""}, // File scheme should not be accepted.
{"https://*example.com", false, ""}, // Wildcard domain should not be accepted. {origin: "https://*example.com", expectedValid: false, expectedOrigin: ""}, // Wildcard domain should not be accepted.
{"http://*.example.com", false, ""}, // Wildcard subdomain should not be accepted. {origin: "http://*.example.com", expectedValid: false, expectedOrigin: ""}, // Wildcard subdomain should not be accepted.
{"http://example.com/path", false, ""}, // Path should not be accepted. {origin: "http://example.com/path", expectedValid: false, expectedOrigin: ""}, // Path should not be accepted.
{"http://example.com?query=123", false, ""}, // Query should not be accepted. {origin: "http://example.com?query=123", expectedValid: false, expectedOrigin: ""}, // Query should not be accepted.
{"http://example.com#fragment", false, ""}, // Fragment should not be accepted. {origin: "http://example.com#fragment", expectedValid: false, expectedOrigin: ""}, // Fragment should not be accepted.
{"http://localhost", true, "http://localhost"}, // Localhost should be accepted. {origin: "http://localhost", expectedValid: true, expectedOrigin: "http://localhost"}, // Localhost should be accepted.
{"http://127.0.0.1", true, "http://127.0.0.1"}, // IPv4 address should be accepted. {origin: "http://127.0.0.1", expectedValid: true, expectedOrigin: "http://127.0.0.1"}, // IPv4 address should be accepted.
{"http://[::1]", true, "http://[::1]"}, // IPv6 address should be accepted. {origin: "http://[::1]", expectedValid: true, expectedOrigin: "http://[::1]"}, // IPv6 address should be accepted.
{"http://[::1]:8080", true, "http://[::1]:8080"}, // IPv6 address with port should be accepted. {origin: "http://[::1]:8080", expectedValid: true, expectedOrigin: "http://[::1]:8080"}, // IPv6 address with port should be accepted.
{"http://[::1]:8080/", true, "http://[::1]:8080"}, // IPv6 address with port and trailing slash should be accepted. {origin: "http://[::1]:8080/", expectedValid: true, expectedOrigin: "http://[::1]:8080"}, // IPv6 address with port and trailing slash should be accepted.
{"http://[::1]:8080/path", false, ""}, // IPv6 address with port and path should not be accepted. {origin: "http://[::1]:8080/path", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and path should not be accepted.
{"http://[::1]:8080?query=123", false, ""}, // IPv6 address with port and query should not be accepted. {origin: "http://[::1]:8080?query=123", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and query should not be accepted.
{"http://[::1]:8080#fragment", false, ""}, // IPv6 address with port and fragment should not be accepted. {origin: "http://[::1]:8080#fragment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and fragment should not be accepted.
{"http://[::1]:8080/path?query=123#fragment", false, ""}, // IPv6 address with port, path, query, and fragment should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, and fragment should not be accepted.
{"http://[::1]:8080/path?query=123#fragment/", false, ""}, // IPv6 address with port, path, query, fragment, and trailing slash should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment/", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, and trailing slash should not be accepted.
{"http://[::1]:8080/path?query=123#fragment/invalid", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment/invalid", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment should not be accepted.
{"http://[::1]:8080/path?query=123#fragment/invalid/", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with trailing slash should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment/invalid/", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with trailing slash should not be accepted.
{"http://[::1]:8080/path?query=123#fragment/invalid/segment", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with additional segment should not be accepted. {origin: "http://[::1]:8080/path?query=123#fragment/invalid/segment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with additional segment should not be accepted.
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -9,8 +9,8 @@ import (
) )
type sessionManager struct { type sessionManager struct {
key string
session *session.Store session *session.Store
key string
} }
func newSessionManager(s *session.Store, k string) *sessionManager { func newSessionManager(s *session.Store, k string) *sessionManager {
@ -49,7 +49,7 @@ func (m *sessionManager) setRaw(c fiber.Ctx, key string, raw []byte, exp time.Du
return return
} }
// the key is crucial in crsf and sometimes a reference to another value which can be reused later(pool/unsafe values concept), so a copy is made here // the key is crucial in crsf and sometimes a reference to another value which can be reused later(pool/unsafe values concept), so a copy is made here
sess.Set(m.key, &Token{key, raw, time.Now().Add(exp)}) sess.Set(m.key, &Token{Key: key, Raw: raw, Expiration: time.Now().Add(exp)})
if err := sess.Save(); err != nil { if err := sess.Save(); err != nil {
log.Warn("csrf: failed to save session: ", err) log.Warn("csrf: failed to save session: ", err)
} }

View File

@ -5,7 +5,7 @@ import (
) )
type Token struct { type Token struct {
Expiration time.Time `json:"expiration"`
Key string `json:"key"` Key string `json:"key"`
Raw []byte `json:"raw"` Raw []byte `json:"raw"`
Expiration time.Time `json:"expiration"`
} }

View File

@ -11,18 +11,6 @@ type Config struct {
// Optional. Default: nil // Optional. Default: nil
Next func(c fiber.Ctx) bool Next func(c fiber.Ctx) bool
// Array of cookie keys that should not be encrypted.
//
// Optional. Default: []
Except []string
// Base64 encoded unique key to encode & decode cookies.
//
// Required. Key length should be 16, 24, or 32 bytes when decoded
// if using the default EncryptCookie and DecryptCookie functions.
// You may use `encryptcookie.GenerateKey(length)` to generate a new key.
Key string
// Custom function to encrypt cookies. // Custom function to encrypt cookies.
// //
// Optional. Default: EncryptCookie (using AES-GCM) // Optional. Default: EncryptCookie (using AES-GCM)
@ -32,6 +20,18 @@ type Config struct {
// //
// Optional. Default: DecryptCookie (using AES-GCM) // Optional. Default: DecryptCookie (using AES-GCM)
Decryptor func(encryptedString, key string) (string, error) Decryptor func(encryptedString, key string) (string, error)
// Base64 encoded unique key to encode & decode cookies.
//
// Required. Key length should be 16, 24, or 32 bytes when decoded
// if using the default EncryptCookie and DecryptCookie functions.
// You may use `encryptcookie.GenerateKey(length)` to generate a new key.
Key string
// Array of cookie keys that should not be encrypted.
//
// Optional. Default: []
Except []string
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -38,9 +38,9 @@ func Test_Middleware_InvalidKeys(t *testing.T) {
tests := []struct { tests := []struct {
length int length int
}{ }{
{11}, {length: 11},
{25}, {length: 25},
{60}, {length: 60},
} }
for _, tt := range tests { for _, tt := range tests {
@ -283,9 +283,9 @@ func Test_GenerateKey(t *testing.T) {
tests := []struct { tests := []struct {
length int length int
}{ }{
{16}, {length: 16},
{24}, {length: 24},
{32}, {length: 32},
} }
decodeBase64 := func(t *testing.T, s string) []byte { decodeBase64 := func(t *testing.T, s string) []byte {
@ -649,9 +649,9 @@ func Benchmark_GenerateKey(b *testing.B) {
tests := []struct { tests := []struct {
length int length int
}{ }{
{16}, {length: 16},
{24}, {length: 24},
{32}, {length: 32},
} }
for _, tt := range tests { for _, tt := range tests {
@ -667,9 +667,9 @@ func Benchmark_GenerateKey_Parallel(b *testing.B) {
tests := []struct { tests := []struct {
length int length int
}{ }{
{16}, {length: 16},
{24}, {length: 24},
{32}, {length: 32},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -34,7 +34,7 @@ func Test_EnvVarHandler(t *testing.T) {
struct { struct {
Vars map[string]string `json:"vars"` Vars map[string]string `json:"vars"`
}{ }{
map[string]string{"testKey": "testVal"}, Vars: map[string]string{"testKey": "testVal"},
}) })
require.NoError(t, err) require.NoError(t, err)

View File

@ -6,6 +6,10 @@ import (
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { 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 // Weak indicates that a weak validator is used. Weak etags are easy
// to generate, but are far less useful for comparisons. Strong // to generate, but are far less useful for comparisons. Strong
// validators are ideal for comparisons but can be very difficult // validators are ideal for comparisons but can be very difficult
@ -15,11 +19,6 @@ type Config struct {
// when byte range requests are used, but strong etags mean range // when byte range requests are used, but strong etags mean range
// requests can still be cached. // requests can still be cached.
Weak bool 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 // ConfigDefault is the default config

View File

@ -11,16 +11,17 @@ import (
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { type Config struct {
// FileSystem is an optional alternate filesystem to search for the favicon in.
// An example of this could be an embedded or network filesystem
//
// Optional. Default: nil
FileSystem fs.FS `json:"-"`
// Next defines a function to skip this middleware when returned true. // Next defines a function to skip this middleware when returned true.
// //
// Optional. Default: nil // Optional. Default: nil
Next func(c fiber.Ctx) bool Next func(c fiber.Ctx) bool
// Raw data of the favicon file
//
// Optional. Default: nil
Data []byte `json:"-"`
// File holds the path to an actual favicon that will be cached // File holds the path to an actual favicon that will be cached
// //
// Optional. Default: "" // Optional. Default: ""
@ -31,16 +32,15 @@ type Config struct {
// Optional. Default: "/favicon.ico" // Optional. Default: "/favicon.ico"
URL string `json:"url"` URL string `json:"url"`
// FileSystem is an optional alternate filesystem to search for the favicon in.
// An example of this could be an embedded or network filesystem
//
// Optional. Default: nil
FileSystem fs.FS `json:"-"`
// CacheControl defines how the Cache-Control header in the response should be set // CacheControl defines how the Cache-Control header in the response should be set
// //
// Optional. Default: "public, max-age=31536000" // Optional. Default: "public, max-age=31536000"
CacheControl string `json:"cache_control"` CacheControl string `json:"cache_control"`
// Raw data of the favicon file
//
// Optional. Default: nil
Data []byte `json:"-"`
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -23,26 +23,10 @@ type Config struct {
// Possible values: "SAMEORIGIN", "DENY", "ALLOW-FROM uri" // Possible values: "SAMEORIGIN", "DENY", "ALLOW-FROM uri"
XFrameOptions string XFrameOptions string
// HSTSMaxAge
// Optional. Default value 0.
HSTSMaxAge int
// HSTSExcludeSubdomains
// Optional. Default value false.
HSTSExcludeSubdomains bool
// ContentSecurityPolicy // ContentSecurityPolicy
// Optional. Default value "". // Optional. Default value "".
ContentSecurityPolicy string ContentSecurityPolicy string
// CSPReportOnly
// Optional. Default value false.
CSPReportOnly bool
// HSTSPreloadEnabled
// Optional. Default value false.
HSTSPreloadEnabled bool
// ReferrerPolicy // ReferrerPolicy
// Optional. Default value "ReferrerPolicy". // Optional. Default value "ReferrerPolicy".
ReferrerPolicy string ReferrerPolicy string
@ -78,6 +62,22 @@ type Config struct {
// X-Permitted-Cross-Domain-Policies // X-Permitted-Cross-Domain-Policies
// Optional. Default value "none". // Optional. Default value "none".
XPermittedCrossDomain string XPermittedCrossDomain string
// HSTSMaxAge
// Optional. Default value 0.
HSTSMaxAge int
// HSTSExcludeSubdomains
// Optional. Default value false.
HSTSExcludeSubdomains bool
// CSPReportOnly
// Optional. Default value false.
CSPReportOnly bool
// HSTSPreloadEnabled
// Optional. Default value false.
HSTSPreloadEnabled bool
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -13,30 +13,6 @@ var ErrInvalidIdempotencyKey = errors.New("invalid idempotency key")
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { type Config struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: a function which skips the middleware on safe HTTP request method.
Next func(c fiber.Ctx) bool
// Lifetime is the maximum lifetime of an idempotency key.
//
// Optional. Default: 30 * time.Minute
Lifetime time.Duration
// KeyHeader is the name of the header that contains the idempotency key.
//
// Optional. Default: X-Idempotency-Key
KeyHeader string
// KeyHeaderValidate defines a function to validate the syntax of the idempotency header.
//
// Optional. Default: a function which ensures the header is 36 characters long (the size of an UUID).
KeyHeaderValidate func(string) error
// KeepResponseHeaders is a list of headers that should be kept from the original response.
//
// Optional. Default: nil (to keep all headers)
KeepResponseHeaders []string
// Lock locks an idempotency key. // Lock locks an idempotency key.
// //
// Optional. Default: an in-memory locker for this process only. // Optional. Default: an in-memory locker for this process only.
@ -46,6 +22,30 @@ type Config struct {
// //
// Optional. Default: an in-memory storage for this process only. // Optional. Default: an in-memory storage for this process only.
Storage fiber.Storage Storage fiber.Storage
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: a function which skips the middleware on safe HTTP request method.
Next func(c fiber.Ctx) bool
// KeyHeaderValidate defines a function to validate the syntax of the idempotency header.
//
// Optional. Default: a function which ensures the header is 36 characters long (the size of an UUID).
KeyHeaderValidate func(string) error
// KeyHeader is the name of the header that contains the idempotency key.
//
// Optional. Default: X-Idempotency-Key
KeyHeader string
// KeepResponseHeaders is a list of headers that should be kept from the original response.
//
// Optional. Default: nil (to keep all headers)
KeepResponseHeaders []string
// Lifetime is the maximum lifetime of an idempotency key.
//
// Optional. Default: 30 * time.Minute
Lifetime time.Duration
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -11,9 +11,8 @@ type Locker interface {
} }
type MemoryLock struct { type MemoryLock struct {
mu sync.Mutex
keys map[string]*sync.Mutex keys map[string]*sync.Mutex
mu sync.Mutex
} }
func (l *MemoryLock) Lock(key string) error { func (l *MemoryLock) Lock(key string) error {

View File

@ -5,9 +5,8 @@ package idempotency
// //
//go:generate msgp -o=response_msgp.go -io=false -unexported //go:generate msgp -o=response_msgp.go -io=false -unexported
type response struct { type response struct {
StatusCode int `msg:"sc"`
Headers map[string][]string `msg:"hs"` Headers map[string][]string `msg:"hs"`
Body []byte `msg:"b"` Body []byte `msg:"b"`
StatusCode int `msg:"sc"`
} }

View File

@ -23,6 +23,11 @@ type Config struct {
// Optional. Default: 401 Invalid or expired key // Optional. Default: 401 Invalid or expired key
ErrorHandler fiber.ErrorHandler ErrorHandler fiber.ErrorHandler
CustomKeyLookup KeyLookupFunc
// Validator is a function to validate key.
Validator func(fiber.Ctx, string) (bool, error)
// KeyLookup is a string in the form of "<source>:<name>" that is used // KeyLookup is a string in the form of "<source>:<name>" that is used
// to extract key from the request. // to extract key from the request.
// Optional. Default value "header:Authorization". // Optional. Default value "header:Authorization".
@ -34,14 +39,9 @@ type Config struct {
// - "cookie:<name>" // - "cookie:<name>"
KeyLookup string KeyLookup string
CustomKeyLookup KeyLookupFunc
// AuthScheme to be used in the Authorization header. // AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer". // Optional. Default value "Bearer".
AuthScheme string AuthScheme string
// Validator is a function to validate key.
Validator func(fiber.Ctx, string) (bool, error)
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -23,8 +23,8 @@ func Test_AuthSources(t *testing.T) {
authTokenName string authTokenName string
description string description string
APIKey string APIKey string
expectedCode int
expectedBody string expectedBody string
expectedCode int
}{ }{
{ {
route: "/", route: "/",
@ -282,8 +282,8 @@ func Test_MultipleKeyAuth(t *testing.T) {
route string route string
description string description string
APIKey string APIKey string
expectedCode int
expectedBody string expectedBody string
expectedCode int
}{ }{
// No auth needed for / // No auth needed for /
{ {

View File

@ -8,16 +8,20 @@ import (
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { type Config struct {
// Store is used to store the state of the middleware
//
// Default: an in memory store for this process only
Storage fiber.Storage
// LimiterMiddleware is the struct that implements a limiter middleware.
//
// Default: a new Fixed Window Rate Limiter
LimiterMiddleware Handler
// Next defines a function to skip this middleware when returned true. // Next defines a function to skip this middleware when returned true.
// //
// Optional. Default: nil // Optional. Default: nil
Next func(c fiber.Ctx) bool Next func(c fiber.Ctx) bool
// Max number of recent connections during `Expiration` seconds before sending a 429 response
//
// Default: 5
Max int
// KeyGenerator allows you to generate custom keys, by default c.IP() is used // KeyGenerator allows you to generate custom keys, by default c.IP() is used
// //
// Default: func(c fiber.Ctx) string { // Default: func(c fiber.Ctx) string {
@ -25,11 +29,6 @@ type Config struct {
// } // }
KeyGenerator func(fiber.Ctx) string KeyGenerator func(fiber.Ctx) string
// Expiration is the time on how long to keep records of requests in memory
//
// Default: 1 * time.Minute
Expiration time.Duration
// LimitReached is called when a request hits the limit // LimitReached is called when a request hits the limit
// //
// Default: func(c fiber.Ctx) error { // Default: func(c fiber.Ctx) error {
@ -37,6 +36,16 @@ type Config struct {
// } // }
LimitReached fiber.Handler LimitReached fiber.Handler
// Max number of recent connections during `Expiration` seconds before sending a 429 response
//
// Default: 5
Max int
// Expiration is the time on how long to keep records of requests in memory
//
// Default: 1 * time.Minute
Expiration time.Duration
// When set to true, requests with StatusCode >= 400 won't be counted. // When set to true, requests with StatusCode >= 400 won't be counted.
// //
// Default: false // Default: false
@ -46,16 +55,6 @@ type Config struct {
// //
// Default: false // Default: false
SkipSuccessfulRequests bool SkipSuccessfulRequests bool
// Store is used to store the state of the middleware
//
// Default: an in memory store for this process only
Storage fiber.Storage
// LimiterMiddleware is the struct that implements a limiter middleware.
//
// Default: a new Fixed Window Rate Limiter
LimiterMiddleware Handler
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -10,6 +10,11 @@ import (
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { type Config struct {
// Output is a writer where logs are written
//
// Default: os.Stdout
Output io.Writer
// Next defines a function to skip this middleware when returned true. // Next defines a function to skip this middleware when returned true.
// //
// Optional. Default: nil // Optional. Default: nil
@ -26,6 +31,20 @@ type Config struct {
// Optional. Default: map[string]LogFunc // Optional. Default: map[string]LogFunc
CustomTags map[string]LogFunc CustomTags map[string]LogFunc
// You can define specific things before the returning the handler: colors, template, etc.
//
// Optional. Default: beforeHandlerFunc
BeforeHandlerFunc func(Config)
// You can use custom loggers with Fiber by using this field.
// This field is really useful if you're using Zerolog, Zap, Logrus, apex/log etc.
// If you don't define anything for this field, it'll use default logger of Fiber.
//
// Optional. Default: defaultLogger
LoggerFunc func(c fiber.Ctx, data *Data, cfg Config) error
timeZoneLocation *time.Location
// Format defines the logging tags // Format defines the logging tags
// //
// Optional. Default: [${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error} // Optional. Default: [${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}
@ -46,23 +65,6 @@ type Config struct {
// Optional. Default: 500 * time.Millisecond // Optional. Default: 500 * time.Millisecond
TimeInterval time.Duration TimeInterval time.Duration
// Output is a writer where logs are written
//
// Default: os.Stdout
Output io.Writer
// You can define specific things before the returning the handler: colors, template, etc.
//
// Optional. Default: beforeHandlerFunc
BeforeHandlerFunc func(Config)
// You can use custom loggers with Fiber by using this field.
// This field is really useful if you're using Zerolog, Zap, Logrus, apex/log etc.
// If you don't define anything for this field, it'll use default logger of Fiber.
//
// Optional. Default: defaultLogger
LoggerFunc func(c fiber.Ctx, data *Data, cfg Config) error
// DisableColors defines if the logs output should be colorized // DisableColors defines if the logs output should be colorized
// //
// Default: false // Default: false
@ -70,7 +72,6 @@ type Config struct {
enableColors bool enableColors bool
enableLatency bool enableLatency bool
timeZoneLocation *time.Location
} }
const ( const (

View File

@ -7,12 +7,12 @@ import (
// Data is a struct to define some variables to use in custom logger function. // Data is a struct to define some variables to use in custom logger function.
type Data struct { type Data struct {
Pid string
ErrPaddingStr string
ChainErr error
TemplateChain [][]byte
LogFuncChain []LogFunc
Start time.Time Start time.Time
Stop time.Time Stop time.Time
ChainErr error
Timestamp atomic.Value Timestamp atomic.Value
Pid string
ErrPaddingStr string
TemplateChain [][]byte
LogFuncChain []LogFunc
} }

View File

@ -256,17 +256,17 @@ func getLatencyTimeUnits() []struct {
unit string unit string
div time.Duration div time.Duration
}{ }{
{"ms", time.Millisecond}, {unit: "ms", div: time.Millisecond},
{"s", time.Second}, {unit: "s", div: time.Second},
} }
} }
return []struct { return []struct {
unit string unit string
div time.Duration div time.Duration
}{ }{
{"µs", time.Microsecond}, {unit: "µs", div: time.Microsecond},
{"ms", time.Millisecond}, {unit: "ms", div: time.Millisecond},
{"s", time.Second}, {unit: "s", div: time.Second},
} }
} }

View File

@ -15,14 +15,6 @@ type Config struct {
// Optional. Default: nil // Optional. Default: nil
Next func(c fiber.Ctx) bool Next func(c fiber.Ctx) bool
// Servers defines a list of <scheme>://<host> HTTP servers,
//
// which are used in a round-robin manner.
// i.e.: "https://foobar.com, http://www.foobar.com"
//
// Required
Servers []string
// ModifyRequest allows you to alter the request // ModifyRequest allows you to alter the request
// //
// Optional. Default: nil // Optional. Default: nil
@ -33,6 +25,22 @@ type Config struct {
// Optional. Default: nil // Optional. Default: nil
ModifyResponse fiber.Handler ModifyResponse fiber.Handler
// tls config for the http client.
TlsConfig *tls.Config //nolint:stylecheck,revive // TODO: Rename to "TLSConfig" in v3
// Client is custom client when client config is complex.
// Note that Servers, Timeout, WriteBufferSize, ReadBufferSize, TlsConfig
// and DialDualStack will not be used if the client are set.
Client *fasthttp.LBClient
// Servers defines a list of <scheme>://<host> HTTP servers,
//
// which are used in a round-robin manner.
// i.e.: "https://foobar.com, http://www.foobar.com"
//
// Required
Servers []string
// Timeout is the request timeout used when calling the proxy client // Timeout is the request timeout used when calling the proxy client
// //
// Optional. Default: 1 second // Optional. Default: 1 second
@ -47,14 +55,6 @@ type Config struct {
// Per-connection buffer size for responses' writing. // Per-connection buffer size for responses' writing.
WriteBufferSize int WriteBufferSize int
// tls config for the http client.
TlsConfig *tls.Config //nolint:stylecheck,revive // TODO: Rename to "TLSConfig" in v3
// Client is custom client when client config is complex.
// Note that Servers, Timeout, WriteBufferSize, ReadBufferSize, TlsConfig
// and DialDualStack will not be used if the client are set.
Client *fasthttp.LBClient
// Attempt to connect to both ipv4 and ipv6 host addresses if set to true. // Attempt to connect to both ipv4 and ipv6 host addresses if set to true.
// //
// By default client connects only to ipv4 addresses, since unfortunately ipv6 // By default client connects only to ipv4 addresses, since unfortunately ipv6

View File

@ -214,10 +214,10 @@ func DomainForward(hostname, addr string, clients ...*fasthttp.Client) fiber.Han
} }
type roundrobin struct { type roundrobin struct {
sync.Mutex pool []string
current int current int
pool []string sync.Mutex
} }
// this method will return a string of addr server from list server. // this method will return a string of addr server from list server.

View File

@ -11,15 +11,15 @@ type Config struct {
// Optional. Default: nil // Optional. Default: nil
Next func(c fiber.Ctx) bool Next func(c fiber.Ctx) bool
// EnableStackTrace enables handling stack trace
//
// Optional. Default: false
EnableStackTrace bool
// StackTraceHandler defines a function to handle stack trace // StackTraceHandler defines a function to handle stack trace
// //
// Optional. Default: defaultStackTraceHandler // Optional. Default: defaultStackTraceHandler
StackTraceHandler func(c fiber.Ctx, e any) StackTraceHandler func(c fiber.Ctx, e any)
// EnableStackTrace enables handling stack trace
//
// Optional. Default: false
EnableStackTrace bool
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -21,12 +21,12 @@ type Config struct {
// "/users/*/orders/*": "/user/$1/order/$2", // "/users/*/orders/*": "/user/$1/order/$2",
Rules map[string]string Rules map[string]string
rulesRegex map[*regexp.Regexp]string
// The status code when redirecting // The status code when redirecting
// This is ignored if Redirect is disabled // This is ignored if Redirect is disabled
// Optional. Default: 302 Temporary Redirect // Optional. Default: 302 Temporary Redirect
StatusCode int StatusCode int
rulesRegex map[*regexp.Regexp]string
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -12,15 +12,15 @@ type Config struct {
// Optional. Default: nil // Optional. Default: nil
Next func(c fiber.Ctx) bool Next func(c fiber.Ctx) bool
// Header is the header key where to get/set the unique request ID
//
// Optional. Default: "X-Request-ID"
Header string
// Generator defines a function to generate the unique identifier. // Generator defines a function to generate the unique identifier.
// //
// Optional. Default: utils.UUID // Optional. Default: utils.UUID
Generator func() string Generator func() string
// Header is the header key where to get/set the unique request ID
//
// Optional. Default: "X-Request-ID"
Header string
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -10,14 +10,14 @@ import (
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { type Config struct {
// Allowed session duration
// Optional. Default value 24 * time.Hour
Expiration time.Duration
// Storage interface to store the session data // Storage interface to store the session data
// Optional. Default value memory.New() // Optional. Default value memory.New()
Storage fiber.Storage Storage fiber.Storage
// KeyGenerator generates the session key.
// Optional. Default value utils.UUIDv4
KeyGenerator func() string
// KeyLookup is a string in the form of "<source>:<name>" that is used // KeyLookup is a string in the form of "<source>:<name>" that is used
// to extract session id from the request. // to extract session id from the request.
// Possible values: "header:<name>", "query:<name>" or "cookie:<name>" // Possible values: "header:<name>", "query:<name>" or "cookie:<name>"
@ -32,6 +32,19 @@ type Config struct {
// Optional. Default value "". // Optional. Default value "".
CookiePath string CookiePath string
// Value of SameSite cookie.
// Optional. Default value "Lax".
CookieSameSite string
// Source defines where to obtain the session id
source Source
// The session name
sessionName string
// Allowed session duration
// Optional. Default value 24 * time.Hour
Expiration time.Duration
// Indicates if cookie is secure. // Indicates if cookie is secure.
// Optional. Default value false. // Optional. Default value false.
CookieSecure bool CookieSecure bool
@ -40,24 +53,10 @@ type Config struct {
// Optional. Default value false. // Optional. Default value false.
CookieHTTPOnly bool CookieHTTPOnly bool
// Value of SameSite cookie.
// Optional. Default value "Lax".
CookieSameSite string
// Decides whether cookie should last for only the browser sesison. // Decides whether cookie should last for only the browser sesison.
// Ignores Expiration if set to true // Ignores Expiration if set to true
// Optional. Default value false. // Optional. Default value false.
CookieSessionOnly bool CookieSessionOnly bool
// KeyGenerator generates the session key.
// Optional. Default value utils.UUIDv4
KeyGenerator func() string
// Source defines where to obtain the session id
source Source
// The session name
sessionName string
} }
type Source string type Source string

View File

@ -7,8 +7,8 @@ import (
// go:generate msgp // go:generate msgp
// msgp -file="data.go" -o="data_msgp.go" -tests=false -unexported // msgp -file="data.go" -o="data_msgp.go" -tests=false -unexported
type data struct { type data struct {
sync.RWMutex
Data map[string]any Data map[string]any
sync.RWMutex
} }
var dataPool = sync.Pool{ var dataPool = sync.Pool{

View File

@ -13,14 +13,14 @@ import (
) )
type Session struct { type Session struct {
mu sync.RWMutex // Mutex to protect non-data fields
id string // session id
fresh bool // if new session
ctx fiber.Ctx // fiber context ctx fiber.Ctx // fiber context
config *Store // store configuration config *Store // store configuration
data *data // key value data data *data // key value data
byteBuffer *bytes.Buffer // byte buffer for the en- and decode byteBuffer *bytes.Buffer // byte buffer for the en- and decode
id string // session id
exp time.Duration // expiration of this session exp time.Duration // expiration of this session
mu sync.RWMutex // Mutex to protect non-data fields
fresh bool // if new session
} }
var sessionPool = sync.Pool{ var sessionPool = sync.Pool{

View File

@ -35,7 +35,7 @@ func New(config ...Config) *Store {
} }
return &Store{ return &Store{
cfg, Config: cfg,
} }
} }

View File

@ -9,17 +9,44 @@ import (
// Config defines the config for middleware. // Config defines the config for middleware.
type Config struct { type Config struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c fiber.Ctx) bool
// FS is the file system to serve the static files from. // FS is the file system to serve the static files from.
// You can use interfaces compatible with fs.FS like embed.FS, os.DirFS etc. // You can use interfaces compatible with fs.FS like embed.FS, os.DirFS etc.
// //
// Optional. Default: nil // Optional. Default: nil
FS fs.FS FS fs.FS
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c fiber.Ctx) bool
// ModifyResponse defines a function that allows you to alter the response.
//
// Optional. Default: nil
ModifyResponse fiber.Handler
// NotFoundHandler defines a function to handle when the path is not found.
//
// Optional. Default: nil
NotFoundHandler fiber.Handler
// The names of the index files for serving a directory.
//
// Optional. Default: []string{"index.html"}.
IndexNames []string `json:"index"`
// Expiration duration for inactive file handlers.
// Use a negative time.Duration to disable it.
//
// Optional. Default: 10 * time.Second.
CacheDuration time.Duration `json:"cache_duration"`
// The value for the Cache-Control HTTP-header
// that is set on the file response. MaxAge is defined in seconds.
//
// Optional. Default: 0.
MaxAge int `json:"max_age"`
// When set to true, the server tries minimizing CPU usage by caching compressed files. // When set to true, the server tries minimizing CPU usage by caching compressed files.
// This works differently than the github.com/gofiber/compression middleware. // This works differently than the github.com/gofiber/compression middleware.
// //
@ -40,33 +67,6 @@ type Config struct {
// //
// Optional. Default: false. // Optional. Default: false.
Download bool `json:"download"` Download bool `json:"download"`
// The names of the index files for serving a directory.
//
// Optional. Default: []string{"index.html"}.
IndexNames []string `json:"index"`
// Expiration duration for inactive file handlers.
// Use a negative time.Duration to disable it.
//
// Optional. Default: 10 * time.Second.
CacheDuration time.Duration `json:"cache_duration"`
// The value for the Cache-Control HTTP-header
// that is set on the file response. MaxAge is defined in seconds.
//
// Optional. Default: 0.
MaxAge int `json:"max_age"`
// ModifyResponse defines a function that allows you to alter the response.
//
// Optional. Default: nil
ModifyResponse fiber.Handler
// NotFoundHandler defines a function to handle when the path is not found.
//
// Optional. Default: nil
NotFoundHandler fiber.Handler
} }
// ConfigDefault is the default config // ConfigDefault is the default config

View File

@ -650,11 +650,11 @@ func Test_isFile(t *testing.T) {
t.Parallel() t.Parallel()
cases := []struct { cases := []struct {
filesystem fs.FS
gotError error
name string name string
path string path string
filesystem fs.FS
expected bool expected bool
gotError error
}{ }{
{ {
name: "file", name: "file",

View File

@ -15,14 +15,14 @@ import (
type mountFields struct { type mountFields struct {
// Mounted and main apps // Mounted and main apps
appList map[string]*App appList map[string]*App
// Prefix of app if it was mounted
mountPath string
// Ordered keys of apps (sorted by key length for Render) // Ordered keys of apps (sorted by key length for Render)
appListKeys []string appListKeys []string
// check added routes of sub-apps // check added routes of sub-apps
subAppsRoutesAdded sync.Once subAppsRoutesAdded sync.Once
// check mounted sub-apps // check mounted sub-apps
subAppsProcessed sync.Once subAppsProcessed sync.Once
// Prefix of app if it was mounted
mountPath string
} }
// Create empty mountFields instance // Create empty mountFields instance

14
path.go
View File

@ -29,19 +29,19 @@ type routeParser struct {
type routeSegment struct { type routeSegment struct {
// const information // const information
Const string // constant part of the route Const string // constant part of the route
// parameter information
IsParam bool // Truth value that indicates whether it is a parameter or a constant part
ParamName string // name of the parameter for access to it, for wildcards and plus parameters access iterators starting with 1 are added ParamName string // name of the parameter for access to it, for wildcards and plus parameters access iterators starting with 1 are added
ComparePart string // search part to find the end of the parameter ComparePart string // search part to find the end of the parameter
Constraints []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default
PartCount int // how often is the search part contained in the non-param segments? -> necessary for greedy search PartCount int // how often is the search part contained in the non-param segments? -> necessary for greedy search
Length int // length of the parameter for segment, when its 0 then the length is undetermined
// future TODO: add support for optional groups "/abc(/def)?"
// parameter information
IsParam bool // Truth value that indicates whether it is a parameter or a constant part
IsGreedy bool // indicates whether the parameter is greedy or not, is used with wildcard and plus IsGreedy bool // indicates whether the parameter is greedy or not, is used with wildcard and plus
IsOptional bool // indicates whether the parameter is optional or not IsOptional bool // indicates whether the parameter is optional or not
// common information // common information
IsLast bool // shows if the segment is the last one for the route IsLast bool // shows if the segment is the last one for the route
HasOptionalSlash bool // segment has the possibility of an optional slash HasOptionalSlash bool // segment has the possibility of an optional slash
Constraints []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default
Length int // length of the parameter for segment, when its 0 then the length is undetermined
// future TODO: add support for optional groups "/abc(/def)?"
} }
// different special routing signs // different special routing signs
@ -65,11 +65,11 @@ const (
type TypeConstraint int16 type TypeConstraint int16
type Constraint struct { type Constraint struct {
ID TypeConstraint
RegexCompiler *regexp.Regexp RegexCompiler *regexp.Regexp
Data []string
Name string Name string
Data []string
customConstraints []CustomConstraint customConstraints []CustomConstraint
ID TypeConstraint
} }
// CustomConstraint is an interface for custom constraints // CustomConstraint is an interface for custom constraints

View File

@ -10,8 +10,8 @@ import (
type routeTestCase struct { type routeTestCase struct {
url string url string
match bool
params []string params []string
match bool
partialCheck bool partialCheck bool
} }

View File

@ -72,8 +72,8 @@ func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) er
// 👮 master process 👮 // 👮 master process 👮
type child struct { type child struct {
pid int
err error err error
pid int
} }
// create variables // create variables
max := runtime.GOMAXPROCS(0) max := runtime.GOMAXPROCS(0)
@ -131,7 +131,7 @@ func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) er
// notify master if child crashes // notify master if child crashes
go func() { go func() {
channel <- child{pid, cmd.Wait()} channel <- child{pid: pid, err: cmd.Wait()}
}() }()
} }

View File

@ -422,11 +422,11 @@ func Test_Redirect_Request(t *testing.T) {
// Test cases // Test cases
testCases := []struct { testCases := []struct {
ExpectedErr error
URL string URL string
CookieValue string CookieValue string
ExpectedBody string ExpectedBody string
ExpectedStatusCode int ExpectedStatusCode int
ExpectedErr error
}{ }{
{ {
URL: "/", URL: "/",

View File

@ -43,16 +43,10 @@ type Router interface {
// Route is a struct that holds all metadata for each registered handler. // Route is a struct that holds all metadata for each registered handler.
type Route struct { type Route struct {
// ### important: always keep in sync with the copy method "app.copyRoute" ### // ### important: always keep in sync with the copy method "app.copyRoute" ###
// Data for routing
pos uint32 // Position in stack -> important for the sort of the matched routes
use bool // USE matches path prefixes
mount bool // Indicated a mounted app on a specific route
star bool // Path equals '*'
root bool // Path equals '/'
path string // Prettified path
routeParser routeParser // Parameter parser
group *Group // Group instance. used for routes in groups group *Group // Group instance. used for routes in groups
path string // Prettified path
// Public fields // Public fields
Method string `json:"method"` // HTTP method Method string `json:"method"` // HTTP method
Name string `json:"name"` // Route's name Name string `json:"name"` // Route's name
@ -60,6 +54,13 @@ type Route struct {
Path string `json:"path"` // Original registered route path Path string `json:"path"` // Original registered route path
Params []string `json:"params"` // Case sensitive param keys Params []string `json:"params"` // Case sensitive param keys
Handlers []Handler `json:"-"` // Ctx handlers Handlers []Handler `json:"-"` // Ctx handlers
routeParser routeParser // Parameter parser
// Data for routing
pos uint32 // Position in stack -> important for the sort of the matched routes
use bool // USE matches path prefixes
mount bool // Indicated a mounted app on a specific route
star bool // Path equals '*'
root bool // Path equals '/'
} }
func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool { func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool {