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

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
This commit is contained in:
Juan Calderon-Perez 2024-07-23 02:37:45 -04:00 committed by GitHub
parent f413bfef99
commit 8c3f81e2b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
74 changed files with 720 additions and 714 deletions

View File

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

View File

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

View File

@ -51,3 +51,8 @@ longtest:
.PHONY: tidy
tidy:
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) {
t.Parallel()
tests := []struct {
name string
expErr error
expBackoff *ExponentialBackoff
f func() error
expErr error
name string
}{
{
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.
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Code int `json:"code"`
}
// App denotes the Fiber application.
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
pool sync.Pool
// Fasthttp server
server *fasthttp.Server
// App config
config Config
// Converts string to a byte slice
getBytes func(s string) (b []byte)
// Converts byte slice to a string
@ -113,24 +100,37 @@ type App struct {
latestRoute *Route
// newCtxFunc
newCtxFunc func(app *App) CustomCtx
// custom binders
customBinders []CustomBinder
// TLS handler
tlsHandler *TLSHandler
// Mount fields
mountFields *mountFields
// Indicates if the value was explicitly configured
configured Config
// Route stack divided by HTTP methods
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 []CustomConstraint
// sendfiles stores configurations for handling ctx.SendFile operations
sendfiles []*sendFileStore
// App config
config Config
// Indicates if the value was explicitly configured
configured Config
// sendfilesMutex is a mutex used for sendfile operations
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.
type Config struct {
type Config struct { //nolint:govet // Aligning the struct fields is not necessary. betteralign:ignore
// Enables the "Server: value" HTTP header.
//
// Default: ""

View File

@ -26,9 +26,9 @@ func Test_Bind_Query(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@ -53,14 +53,14 @@ func Test_Bind_Query(t *testing.T) {
require.Empty(t, empty.Hobby)
type Query2 struct {
Bool bool
ID int
Name string
Hobby string
FavouriteDrinks []string
Empty []string
Alloc []string
No []int64
ID int
Bool bool
}
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())
type Node struct {
Value int `query:"val,required"`
Next *Node `query:"next,required"`
Value int `query:"val,required"`
}
c.Request().URI().SetQueryString("val=1&next.val=3")
n := new(Node)
@ -292,9 +292,9 @@ func Test_Bind_Header(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Header struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@ -318,14 +318,14 @@ func Test_Bind_Header(t *testing.T) {
require.Empty(t, empty.Hobby)
type Header2 struct {
Bool bool
ID int
Name string
Hobby string
FavouriteDrinks []string
Empty []string
Alloc []string
No []int64
ID int
Bool bool
}
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())
type Node struct {
Value int `header:"Val,required"`
Next *Node `header:"Next,required"`
Value int `header:"Val,required"`
}
c.Request().Header.Add("Val", "1")
c.Request().Header.Add("Next.Val", "3")
@ -533,9 +533,9 @@ func Test_Bind_RespHeader(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Header struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@ -559,14 +559,14 @@ func Test_Bind_RespHeader(t *testing.T) {
require.Empty(t, empty.Hobby)
type Header2 struct {
Bool bool
ID int
Name string
Hobby string
FavouriteDrinks []string
Empty []string
Alloc []string
No []int64
ID int
Bool bool
}
c.Response().Header.Add("id", "2")
@ -635,9 +635,9 @@ func Benchmark_Bind_Query(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@ -708,9 +708,9 @@ func Benchmark_Bind_Query_Comma(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Query struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@ -732,9 +732,9 @@ func Benchmark_Bind_Header(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type ReqHeader struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@ -782,9 +782,9 @@ func Benchmark_Bind_RespHeader(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type ReqHeader struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@ -1252,9 +1252,9 @@ func Test_Bind_Cookie(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Cookie struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")
@ -1278,14 +1278,14 @@ func Test_Bind_Cookie(t *testing.T) {
require.Empty(t, empty.Hobby)
type Cookie2 struct {
Bool bool
ID int
Name string
Hobby string
FavouriteDrinks []string
Empty []string
Alloc []string
No []int64
ID int
Bool bool
}
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())
type Node struct {
Value int `cookie:"Val,required"`
Next *Node `cookie:"Next,required"`
Value int `cookie:"Val,required"`
}
c.Request().Header.SetCookie("Val", "1")
c.Request().Header.SetCookie("Next.Val", "3")
@ -1495,9 +1495,9 @@ func Benchmark_Bind_Cookie(b *testing.B) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
type Cookie struct {
ID int
Name string
Hobby []string
ID int
}
c.Request().SetBody([]byte(``))
c.Request().Header.SetContentType("")

View File

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

View File

@ -34,21 +34,32 @@ var (
// Fiber Client also provides an option to override
// or merge most of the client settings at the request.
type Client struct {
mu sync.RWMutex
// logger
logger log.CommonLogger
fasthttp *fasthttp.Client
header *Header
params *QueryParam
cookies *Cookie
path *PathParam
jsonMarshal utils.JSONMarshal
jsonUnmarshal utils.JSONUnmarshal
xmlMarshal utils.XMLMarshal
xmlUnmarshal utils.XMLUnmarshal
cookieJar *CookieJar
// retry
retryConfig *RetryConfig
baseURL string
userAgent string
referer string
header *Header
params *QueryParam
cookies *Cookie
path *PathParam
debug bool
timeout time.Duration
// proxy
proxyURL string
// user defined request hooks
userRequestHooks []RequestHook
@ -62,21 +73,11 @@ type Client struct {
// client package defined response hooks
builtinResponseHooks []ResponseHook
jsonMarshal utils.JSONMarshal
jsonUnmarshal utils.JSONUnmarshal
xmlMarshal utils.XMLMarshal
xmlUnmarshal utils.XMLUnmarshal
timeout time.Duration
cookieJar *CookieJar
mu sync.RWMutex
// proxy
proxyURL string
// retry
retryConfig *RetryConfig
// logger
logger log.CommonLogger
debug bool
}
// R raise a request from the client.
@ -604,19 +605,20 @@ func (c *Client) Reset() {
type Config struct {
Ctx context.Context //nolint:containedctx // It's needed to be stored in the config.
UserAgent string
Referer string
Body any
Header map[string]string
Param map[string]string
Cookie map[string]string
PathParam map[string]string
FormData map[string]string
UserAgent string
Referer string
File []*File
Timeout time.Duration
MaxRedirects int
Body any
FormData map[string]string
File []*File
}
// 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.Parallel()
type args struct {
CookieInt int `cookie:"int"`
CookieString string `cookie:"string"`
CookieInt int `cookie:"int"`
}
req := New().SetCookiesWithStruct(&args{
@ -1087,12 +1087,12 @@ func Test_Client_QueryParam(t *testing.T) {
t.Parallel()
type args struct {
TInt int
TString string
TFloat float64
TBool bool
TSlice []string
TIntSlice []int `param:"int_slice"`
TInt int
TFloat float64
TBool bool
}
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.Parallel()
type args struct {
CookieInt int `path:"int"`
CookieString string `path:"string"`
CookieInt int `path:"int"`
}
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.
type CookieJar struct {
mu sync.Mutex
hostCookies map[string][]*fasthttp.Cookie
mu sync.Mutex
}
// Get returns the cookies stored from a specific domain.

View File

@ -22,8 +22,8 @@ func Test_AddMissing_Port(t *testing.T) {
}
tests := []struct {
name string
args args
want string
args args
}{
{
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.
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.
header *Header
params *QueryParam
cookies *Cookie
path *PathParam
ctx context.Context //nolint:containedctx // It's needed to be stored in the request.
body any
header *Header
params *QueryParam
cookies *Cookie
path *PathParam
client *Client
formData *FormData
RawRequest *fasthttp.Request
url string
method string
userAgent string
boundary string
referer string
files []*File
timeout time.Duration
maxRedirects int
client *Client
body any
formData *FormData
files []*File
bodyType bodyType
RawRequest *fasthttp.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.
type File struct {
reader io.ReadCloser
name string
fieldName string
path string
reader io.ReadCloser
}
// SetName method sets file name.

View File

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

View File

@ -19,9 +19,9 @@ import (
type Response struct {
client *Client
request *Request
cookie []*fasthttp.Cookie
RawResponse *fasthttp.Response
cookie []*fasthttp.Cookie
}
// 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 {
app *App // Reference to *App
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
matched bool // Non use route matched
viewBindMap sync.Map // Default view map to bind template engine
bind *Bind // Default bind 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
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.
@ -112,8 +112,8 @@ type SendFile struct {
// sendFileStore is used to keep the SendFile configuration and the handler.
type sendFileStore struct {
handler fasthttp.RequestHandler
config SendFile
cacheControlValue string
config SendFile
}
// compareConfig compares the current SendFile config with the new one
@ -175,15 +175,15 @@ type RangeSet struct {
// Cookie data for c.Cookie
type Cookie struct {
Expires time.Time `json:"expires"` // The expiration date of the cookie
Name string `json:"name"` // The name 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
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
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
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
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
type ResFmt struct {
MediaType string
Handler func(Ctx) error
MediaType string
}
// 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
End int
}{
start,
end,
Start: start,
End: end,
})
}
if len(rangeData.Ranges) < 1 {

View File

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

View File

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

View File

@ -31,11 +31,11 @@ import (
// along with quality, specificity, parameters, and order.
// Used for sorting accept headers.
type acceptedType struct {
params headerParams
spec string
quality float64
specificity int
order int
params headerParams
}
type headerParams map[string][]byte
@ -474,7 +474,7 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
}
// 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 {

View File

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

View File

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

View File

@ -26,10 +26,10 @@ func newCache() *cache {
// cache caches meta-data about a struct.
type cache struct {
l sync.RWMutex
m map[reflect.Type]*structInfo
regconv map[reflect.Type]Converter
tag string
l sync.RWMutex
}
// 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.
type ConversionError struct {
Key string // key from the source map.
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)
Key string // key from the source map.
Index int // index for multi-value fields; -1 for single-value fields.
}
func (e ConversionError) Error() string {

View File

@ -11,10 +11,10 @@ import (
// Storage interface that is implemented by storage providers
type Storage struct {
mux sync.RWMutex
db map[string]entry
gcInterval time.Duration
done chan struct{}
gcInterval time.Duration
mux sync.RWMutex
}
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()
}
e := entry{val, expire}
e := entry{data: val, expiry: expire}
s.mux.Lock()
s.db[key] = e
s.mux.Unlock()

View File

@ -40,6 +40,36 @@ const (
//
// TODO: Add timeout for graceful shutdown.
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)
// WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen.
//
@ -64,26 +94,6 @@ type ListenConfig struct {
// Default : ""
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.
//
// Default: false
@ -98,17 +108,6 @@ type ListenConfig struct {
//
// Default: false
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.

View File

@ -79,10 +79,10 @@ func Test_Listen_Graceful_Shutdown(t *testing.T) {
}
testCases := []struct {
Time time.Duration
ExpectedBody string
ExpectedStatusCode int
ExpectedErr error
ExpectedBody string
Time time.Duration
ExpectedStatusCode int
}{
{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")},

View File

@ -121,11 +121,11 @@ func Test_CtxLogger(t *testing.T) {
func Test_LogfKeyAndValues(t *testing.T) {
tests := []struct {
name string
level Level
format string
wantOutput string
fmtArgs []any
keysAndValues []any
wantOutput string
level Level
}{
{
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) {
tests := []struct {
name string
level Level
format string
keysAndValues []any
level Level
}{
{
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) {
tests := []struct {
name string
level Level
format string
keysAndValues []any
level Level
}{
{
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
r.Method = expectedMethod
r.Body = &netHTTPBody{[]byte(expectedBody)}
r.Body = &netHTTPBody{b: []byte(expectedBody)}
r.RequestURI = expectedRequestURI
r.ContentLength = int64(expectedContentLength)
r.Host = expectedHost
@ -355,9 +355,9 @@ func (r *netHTTPBody) Close() error {
}
type netHTTPResponseWriter struct {
statusCode int
h http.Header
body []byte
statusCode int
}
func (w *netHTTPResponseWriter) StatusCode() int {

View File

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

View File

@ -19,13 +19,6 @@ type Config struct {
// Required. Default: 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
// to check the credentials however you want.
// It will be called with a username and password
@ -40,6 +33,13 @@ type Config struct {
//
// Optional. Default: nil
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

View File

@ -9,28 +9,16 @@ import (
// Config defines the config for middleware.
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.
//
// Optional. Default: nil
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
//
// Optional. Default: nil
@ -48,15 +36,23 @@ type Config struct {
// Default: nil
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
Storage fiber.Storage
// hit, miss, unreachable
//
// 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
StoreResponseHeaders bool
// Default: []string{fiber.MethodGet, fiber.MethodHead}
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,
// entries with the nearest expiration are deleted to make room for new.
@ -65,11 +61,15 @@ type Config struct {
// Default: 0
MaxBytes uint
// You can specify HTTP methods to cache.
// The middleware just caches the routes of its methods in this slice.
// CacheControl enables client side caching if set to true
//
// Default: []string{fiber.MethodGet, fiber.MethodHead}
Methods []string
// Optional. Default: false
CacheControl bool
// allows you to store additional headers generated by next middlewares & handler
//
// Default: false
StoreResponseHeaders bool
}
// ConfigDefault is the default config

View File

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

View File

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

View File

@ -41,15 +41,6 @@ type Config struct {
// Optional. Default value []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
// access.
//
@ -65,6 +56,15 @@ type Config struct {
// Optional. Default value 0.
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
// 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) {
t.Parallel()
tests := []struct {
pattern []string
reqOrigin string
pattern []string
shouldAllowOrigin bool
}{
{
@ -682,9 +682,9 @@ func Test_CORS_AllowOriginsFunc(t *testing.T) {
func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) {
testCases := []struct {
Name string
Config Config
RequestOrigin string
ResponseOrigin string
Config Config
}{
{
Name: "AllowOriginsDefined/AllowOriginsFuncUndefined/OriginAllowed",
@ -829,10 +829,10 @@ func Test_CORS_AllowOriginsAndAllowOriginsFunc_AllUseCases(t *testing.T) {
func Test_CORS_AllowCredentials(t *testing.T) {
testCases := []struct {
Name string
Config Config
RequestOrigin string
ResponseOrigin string
ResponseCredentials string
Config Config
}{
{
Name: "AllowOriginsFuncDefined",

View File

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

View File

@ -13,11 +13,40 @@ import (
// Config defines the config for middleware.
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.
//
// Optional. Default: nil
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
// to create an Extractor that extracts the token from the request.
// Possible values:
@ -45,45 +74,10 @@ type Config struct {
// Optional. Default value "".
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.
// Optional. Default value "Lax".
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
//
// Default: "csrfToken"
@ -102,22 +96,28 @@ type Config struct {
// Optional. Default: []
TrustedOrigins []string
// KeyGenerator creates a new CSRF token
// Expiration is the duration before csrf token will expire
//
// Optional. Default: utils.UUID
KeyGenerator func() string
// Optional. Default: 1 * time.Hour
Expiration time.Duration
// ErrorHandler is executed when an error is returned from fiber.Handler.
//
// Optional. Default: DefaultErrorHandler
ErrorHandler fiber.ErrorHandler
// Indicates if CSRF cookie is secure.
// Optional. Default value false.
CookieSecure bool
// 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 will create an Extractor based on KeyLookup.
Extractor func(c fiber.Ctx) (string, error)
// Optional. Default: false
SingleUseToken bool
}
const HeaderName = "X-Csrf-Token"

View File

@ -24,9 +24,9 @@ var (
// Handler for CSRF middleware
type Handler struct {
config Config
sessionManager *sessionManager
storageManager *storageManager
config Config
}
// 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
origin string
}{
{"No Scheme", "localhost"},
{"Wildcard", "https://*"},
{"Wildcard domain", "https://*example.com"},
{"File Scheme", "file://example.com"},
{"FTP Scheme", "ftp://example.com"},
{"Port Wildcard", "http://example.com:*"},
{"Multiple Wildcards", "https://*.*.com"},
{name: "No Scheme", origin: "localhost"},
{name: "Wildcard", origin: "https://*"},
{name: "Wildcard domain", origin: "https://*example.com"},
{name: "File Scheme", origin: "file://example.com"},
{name: "FTP Scheme", origin: "ftp://example.com"},
{name: "Port Wildcard", origin: "http://example.com:*"},
{name: "Multiple Wildcards", origin: "https://*.*.com"},
}
for _, tt := range tests {

View File

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

View File

@ -9,8 +9,8 @@ import (
)
type sessionManager struct {
key string
session *session.Store
key string
}
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
}
// 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 {
log.Warn("csrf: failed to save session: ", err)
}

View File

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

View File

@ -11,18 +11,6 @@ type Config struct {
// Optional. Default: nil
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.
//
// Optional. Default: EncryptCookie (using AES-GCM)
@ -32,6 +20,18 @@ type Config struct {
//
// Optional. Default: DecryptCookie (using AES-GCM)
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

View File

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

View File

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

View File

@ -6,6 +6,10 @@ import (
// Config defines the config for middleware.
type Config struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c fiber.Ctx) bool
// Weak indicates that a weak validator is used. Weak etags are easy
// to generate, but are far less useful for comparisons. Strong
// validators are ideal for comparisons but can be very difficult
@ -15,11 +19,6 @@ type Config struct {
// when byte range requests are used, but strong etags mean range
// requests can still be cached.
Weak bool
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c fiber.Ctx) bool
}
// ConfigDefault is the default config

View File

@ -11,16 +11,17 @@ import (
// Config defines the config for middleware.
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.
//
// Optional. Default: nil
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
//
// Optional. Default: ""
@ -31,16 +32,15 @@ type Config struct {
// Optional. Default: "/favicon.ico"
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
//
// Optional. Default: "public, max-age=31536000"
CacheControl string `json:"cache_control"`
// Raw data of the favicon file
//
// Optional. Default: nil
Data []byte `json:"-"`
}
// ConfigDefault is the default config

View File

@ -23,26 +23,10 @@ type Config struct {
// Possible values: "SAMEORIGIN", "DENY", "ALLOW-FROM uri"
XFrameOptions string
// HSTSMaxAge
// Optional. Default value 0.
HSTSMaxAge int
// HSTSExcludeSubdomains
// Optional. Default value false.
HSTSExcludeSubdomains bool
// ContentSecurityPolicy
// Optional. Default value "".
ContentSecurityPolicy string
// CSPReportOnly
// Optional. Default value false.
CSPReportOnly bool
// HSTSPreloadEnabled
// Optional. Default value false.
HSTSPreloadEnabled bool
// ReferrerPolicy
// Optional. Default value "ReferrerPolicy".
ReferrerPolicy string
@ -78,6 +62,22 @@ type Config struct {
// X-Permitted-Cross-Domain-Policies
// Optional. Default value "none".
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

View File

@ -13,30 +13,6 @@ var ErrInvalidIdempotencyKey = errors.New("invalid idempotency key")
// Config defines the config for middleware.
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.
//
// 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.
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

View File

@ -11,9 +11,8 @@ type Locker interface {
}
type MemoryLock struct {
mu sync.Mutex
keys map[string]*sync.Mutex
mu sync.Mutex
}
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
type response struct {
StatusCode int `msg:"sc"`
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
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
// to extract key from the request.
// Optional. Default value "header:Authorization".
@ -34,14 +39,9 @@ type Config struct {
// - "cookie:<name>"
KeyLookup string
CustomKeyLookup KeyLookupFunc
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
AuthScheme string
// Validator is a function to validate key.
Validator func(fiber.Ctx, string) (bool, error)
}
// ConfigDefault is the default config

View File

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

View File

@ -8,16 +8,20 @@ import (
// Config defines the config for middleware.
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.
//
// Optional. Default: nil
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
//
// Default: func(c fiber.Ctx) string {
@ -25,11 +29,6 @@ type Config struct {
// }
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
//
// Default: func(c fiber.Ctx) error {
@ -37,6 +36,16 @@ type Config struct {
// }
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.
//
// Default: false
@ -46,16 +55,6 @@ type Config struct {
//
// Default: false
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

View File

@ -10,6 +10,11 @@ import (
// Config defines the config for middleware.
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.
//
// Optional. Default: nil
@ -26,6 +31,20 @@ type Config struct {
// Optional. Default: 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
//
// Optional. Default: [${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}
@ -46,31 +65,13 @@ type Config struct {
// Optional. Default: 500 * time.Millisecond
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
//
// Default: false
DisableColors bool
enableColors bool
enableLatency bool
timeZoneLocation *time.Location
enableColors bool
enableLatency bool
}
const (

View File

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

View File

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

View File

@ -15,14 +15,6 @@ type Config struct {
// Optional. Default: nil
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
//
// Optional. Default: nil
@ -33,6 +25,22 @@ type Config struct {
// Optional. Default: nil
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
//
// Optional. Default: 1 second
@ -47,14 +55,6 @@ type Config struct {
// Per-connection buffer size for responses' writing.
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.
//
// 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 {
sync.Mutex
pool []string
current int
pool []string
sync.Mutex
}
// this method will return a string of addr server from list server.

View File

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

View File

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

View File

@ -12,15 +12,15 @@ type Config struct {
// Optional. Default: nil
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.
//
// Optional. Default: utils.UUID
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

View File

@ -10,14 +10,14 @@ import (
// Config defines the config for middleware.
type Config struct {
// Allowed session duration
// Optional. Default value 24 * time.Hour
Expiration time.Duration
// Storage interface to store the session data
// Optional. Default value memory.New()
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
// to extract session id from the request.
// Possible values: "header:<name>", "query:<name>" or "cookie:<name>"
@ -32,6 +32,19 @@ type Config struct {
// Optional. Default value "".
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.
// Optional. Default value false.
CookieSecure bool
@ -40,24 +53,10 @@ type Config struct {
// Optional. Default value false.
CookieHTTPOnly bool
// Value of SameSite cookie.
// Optional. Default value "Lax".
CookieSameSite string
// Decides whether cookie should last for only the browser sesison.
// Ignores Expiration if set to true
// Optional. Default value false.
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

View File

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

View File

@ -13,14 +13,14 @@ import (
)
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
config *Store // store configuration
data *data // key value data
byteBuffer *bytes.Buffer // byte buffer for the en- and decode
id string // session id
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{

View File

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

View File

@ -9,17 +9,44 @@ import (
// Config defines the config for middleware.
type Config struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c fiber.Ctx) bool
// 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.
//
// Optional. Default: nil
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.
// This works differently than the github.com/gofiber/compression middleware.
//
@ -40,33 +67,6 @@ type Config struct {
//
// Optional. Default: false.
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

View File

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

View File

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

30
path.go
View File

@ -28,20 +28,20 @@ type routeParser struct {
// routeSegment holds the segment metadata
type routeSegment struct {
// const information
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
ComparePart string // search part to find the end of the parameter
PartCount int // how often is the search part contained in the non-param segments? -> necessary for greedy search
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
// common information
IsLast bool // shows if the segment is the last one for the route
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
Const string // constant part of the route
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
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
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
IsOptional bool // indicates whether the parameter is optional or not
// common information
IsLast bool // shows if the segment is the last one for the route
HasOptionalSlash bool // segment has the possibility of an optional slash
}
// different special routing signs
@ -65,11 +65,11 @@ const (
type TypeConstraint int16
type Constraint struct {
ID TypeConstraint
RegexCompiler *regexp.Regexp
Data []string
Name string
Data []string
customConstraints []CustomConstraint
ID TypeConstraint
}
// CustomConstraint is an interface for custom constraints

View File

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

View File

@ -72,8 +72,8 @@ func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) er
// 👮 master process 👮
type child struct {
pid int
err error
pid int
}
// create variables
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
go func() {
channel <- child{pid, cmd.Wait()}
channel <- child{pid: pid, err: cmd.Wait()}
}()
}

View File

@ -34,11 +34,11 @@ const (
// Redirect is a struct that holds the redirect data.
type Redirect struct {
c *DefaultCtx // Embed ctx
status int // Status code of redirection. Default: StatusFound
messages []string // Flash messages
c *DefaultCtx // Embed ctx
oldInput map[string]string // Old input data
messages []string // Flash messages
status int // Status code of redirection. Default: StatusFound
}
// RedirectConfig A config to use with Redirect().Route()

View File

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

View File

@ -43,23 +43,24 @@ type Router interface {
// Route is a struct that holds all metadata for each registered handler.
type Route struct {
// ### 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
Method string `json:"method"` // HTTP method
Name string `json:"name"` // Route's name
//nolint:revive // Having both a Path (uppercase) and a path (lowercase) is fine
Path string `json:"path"` // Original registered route path
Params []string `json:"params"` // Case sensitive param keys
Handlers []Handler `json:"-"` // Ctx handlers
Path string `json:"path"` // Original registered route path
Params []string `json:"params"` // Case sensitive param keys
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 {