diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index c179282849..e65d956b92 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -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:
diff --git a/.golangci.yml b/.golangci.yml
index f5e43b502a..833cf407fd 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -101,7 +101,6 @@ linters-settings:
govet:
enable-all: true
disable:
- - fieldalignment
- shadow
grouper:
diff --git a/Makefile b/Makefile
index 2e7f2e1cfc..4f533db5c5 100644
--- a/Makefile
+++ b/Makefile
@@ -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 ./...
\ No newline at end of file
diff --git a/addon/retry/exponential_backoff_test.go b/addon/retry/exponential_backoff_test.go
index 0961d4fa43..a3b31fc23c 100644
--- a/addon/retry/exponential_backoff_test.go
+++ b/addon/retry/exponential_backoff_test.go
@@ -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",
diff --git a/app.go b/app.go
index 293f29ba0d..e0240d3c16 100644
--- a/app.go
+++ b/app.go
@@ -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: ""
diff --git a/bind_test.go b/bind_test.go
index 9b29145145..48f53f62a1 100644
--- a/bind_test.go
+++ b/bind_test.go
@@ -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("")
diff --git a/binder/mapping.go b/binder/mapping.go
index 7a09140e2f..07af94a152 100644
--- a/binder/mapping.go
+++ b/binder/mapping.go
@@ -12,9 +12,9 @@ import (
// ParserConfig form decoder config for SetParserDecoder
type ParserConfig struct {
- IgnoreUnknownKeys bool
SetAliasTag string
ParserType []ParserType
+ IgnoreUnknownKeys bool
ZeroEmpty bool
}
diff --git a/client/client.go b/client/client.go
index e9c65e14d8..0e77109034 100644
--- a/client/client.go
+++ b/client/client.go
@@ -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
-
- cookieJar *CookieJar
-
- // proxy
- proxyURL string
+ timeout time.Duration
- // retry
- retryConfig *RetryConfig
+ mu sync.RWMutex
- // 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.
diff --git a/client/client_test.go b/client/client_test.go
index 0f81dda9e0..bdbb7facac 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -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{
diff --git a/client/cookiejar.go b/client/cookiejar.go
index c66d5f3b7c..834357fbbe 100644
--- a/client/cookiejar.go
+++ b/client/cookiejar.go
@@ -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.
diff --git a/client/core_test.go b/client/core_test.go
index c985784c22..f189c9f8a3 100644
--- a/client/core_test.go
+++ b/client/core_test.go
@@ -22,8 +22,8 @@ func Test_AddMissing_Port(t *testing.T) {
}
tests := []struct {
name string
- args args
want string
+ args args
}{
{
name: "do anything",
diff --git a/client/request.go b/client/request.go
index ebddb0d0ee..61b5798c57 100644
--- a/client/request.go
+++ b/client/request.go
@@ -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.
- timeout time.Duration
- maxRedirects int
+ body any
+ header *Header
+ params *QueryParam
+ cookies *Cookie
+ path *PathParam
client *Client
- body any
formData *FormData
- files []*File
- bodyType bodyType
RawRequest *fasthttp.Request
+ url string
+ method string
+ userAgent string
+ boundary string
+ referer string
+ files []*File
+
+ timeout time.Duration
+ maxRedirects int
+
+ bodyType bodyType
}
// 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.
diff --git a/client/request_test.go b/client/request_test.go
index e5369fbbaf..00f19654a1 100644
--- a/client/request_test.go
+++ b/client/request_test.go
@@ -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 {
+ TString string
+ TSlice []string
+ TIntSlice []int `param:"int_slice"`
unexport int
TInt int
- TString string
TFloat float64
TBool bool
- TSlice []string
- TIntSlice []int `param:"int_slice"`
}
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 {
+ TString string
+ TSlice []string
+ TIntSlice []int `param:"int_slice"`
unexport int
TInt int
- TString string
TFloat float64
TBool bool
- TSlice []string
- TIntSlice []int `param:"int_slice"`
}
b.Run("the struct should be applied", func(b *testing.B) {
diff --git a/client/response.go b/client/response.go
index adb70ac4c4..847107681f 100644
--- a/client/response.go
+++ b/client/response.go
@@ -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.
diff --git a/ctx.go b/ctx.go
index 02cbd814bc..eabfa7e1d4 100644
--- a/ctx.go
+++ b/ctx.go
@@ -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
+ fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
+ 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
- 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
+ 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 {
diff --git a/ctx_test.go b/ctx_test.go
index 88e5762072..c7a7ae9ee6 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -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 {
diff --git a/group.go b/group.go
index fe2ac97acb..4142b0ba23 100644
--- a/group.go
+++ b/group.go
@@ -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.
diff --git a/helpers.go b/helpers.go
index 203f51da54..68d8193a97 100644
--- a/helpers.go
+++ b/helpers.go
@@ -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 {
diff --git a/helpers_test.go b/helpers_test.go
index 21344d2236..caf4983d28 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -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 {
diff --git a/internal/memory/memory.go b/internal/memory/memory.go
index cf2b3cac11..3b552c5860 100644
--- a/internal/memory/memory.go
+++ b/internal/memory/memory.go
@@ -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()
diff --git a/internal/schema/cache.go b/internal/schema/cache.go
index 3d77ec07d5..85e28f174a 100644
--- a/internal/schema/cache.go
+++ b/internal/schema/cache.go
@@ -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.
diff --git a/internal/schema/decoder.go b/internal/schema/decoder.go
index 410ad63160..310b783e38 100644
--- a/internal/schema/decoder.go
+++ b/internal/schema/decoder.go
@@ -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 {
diff --git a/internal/storage/memory/memory.go b/internal/storage/memory/memory.go
index e2f305d28b..c22ab92b27 100644
--- a/internal/storage/memory/memory.go
+++ b/internal/storage/memory/memory.go
@@ -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()
diff --git a/listen.go b/listen.go
index e643db202b..543aec0269 100644
--- a/listen.go
+++ b/listen.go
@@ -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.
diff --git a/listen_test.go b/listen_test.go
index da60aa75e9..c828a911cb 100644
--- a/listen_test.go
+++ b/listen_test.go
@@ -79,10 +79,10 @@ func Test_Listen_Graceful_Shutdown(t *testing.T) {
}
testCases := []struct {
- Time time.Duration
+ ExpectedErr error
ExpectedBody string
+ Time time.Duration
ExpectedStatusCode int
- ExpectedErr error
}{
{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")},
diff --git a/log/default_test.go b/log/default_test.go
index 2d2e4f8fa3..4a6ff82e59 100644
--- a/log/default_test.go
+++ b/log/default_test.go
@@ -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",
diff --git a/middleware/adaptor/adaptor_test.go b/middleware/adaptor/adaptor_test.go
index b96259f3bd..32abc64d93 100644
--- a/middleware/adaptor/adaptor_test.go
+++ b/middleware/adaptor/adaptor_test.go
@@ -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 {
diff --git a/middleware/basicauth/basicauth_test.go b/middleware/basicauth/basicauth_test.go
index 421fcfd8dd..b47f557e4a 100644
--- a/middleware/basicauth/basicauth_test.go
+++ b/middleware/basicauth/basicauth_test.go
@@ -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",
diff --git a/middleware/basicauth/config.go b/middleware/basicauth/config.go
index 1aa050a0e2..8668fad835 100644
--- a/middleware/basicauth/config.go
+++ b/middleware/basicauth/config.go
@@ -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
diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go
index c107b212ed..69c3fd5c1d 100644
--- a/middleware/cache/cache.go
+++ b/middleware/cache/cache.go
@@ -117,46 +117,49 @@ func New(config ...Config) fiber.Handler {
// Get timestamp
ts := atomic.LoadUint64(×tamp)
- // Invalidate cache if requested
- if cfg.CacheInvalidator != nil && cfg.CacheInvalidator(c) && e != nil {
- e.exp = ts - 1
- }
-
- // Check if entry is expired
- if e.exp != 0 && ts >= e.exp {
- deleteKey(key)
- if cfg.MaxBytes > 0 {
- _, size := heap.remove(e.heapidx)
- storedBytes -= size
- }
- } else if e.exp != 0 && !hasRequestDirective(c, noCache) {
- // Separate body value to avoid msgp serialization
- // We can store raw bytes with Storage 👍
- if cfg.Storage != nil {
- e.body = manager.getRaw(key + "_body")
- }
- // Set response headers from cache
- c.Response().SetBodyRaw(e.body)
- c.Response().SetStatusCode(e.status)
- c.Response().Header.SetContentTypeBytes(e.ctype)
- if len(e.cencoding) > 0 {
- c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding)
- }
- for k, v := range e.headers {
- c.Response().Header.SetBytesV(k, v)
+ // Cache Entry not found
+ if e != nil {
+ // Invalidate cache if requested
+ if cfg.CacheInvalidator != nil && cfg.CacheInvalidator(c) {
+ e.exp = ts - 1
}
- // Set Cache-Control header if enabled
- if cfg.CacheControl {
- maxAge := strconv.FormatUint(e.exp-ts, 10)
- c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge)
- }
-
- c.Set(cfg.CacheHeader, cacheHit)
-
- mux.Unlock()
- // Return response
- return nil
+ // Check if entry is expired
+ if e.exp != 0 && ts >= e.exp {
+ deleteKey(key)
+ if cfg.MaxBytes > 0 {
+ _, size := heap.remove(e.heapidx)
+ storedBytes -= size
+ }
+ } else if e.exp != 0 && !hasRequestDirective(c, noCache) {
+ // Separate body value to avoid msgp serialization
+ // We can store raw bytes with Storage 👍
+ if cfg.Storage != nil {
+ e.body = manager.getRaw(key + "_body")
+ }
+ // Set response headers from cache
+ c.Response().SetBodyRaw(e.body)
+ c.Response().SetStatusCode(e.status)
+ c.Response().Header.SetContentTypeBytes(e.ctype)
+ if len(e.cencoding) > 0 {
+ c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding)
+ }
+ for k, v := range e.headers {
+ c.Response().Header.SetBytesV(k, v)
+ }
+ // Set Cache-Control header if enabled
+ if cfg.CacheControl {
+ maxAge := strconv.FormatUint(e.exp-ts, 10)
+ c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge)
+ }
+
+ c.Set(cfg.CacheHeader, cacheHit)
+
+ mux.Unlock()
+
+ // Return response
+ return nil
+ }
}
// make sure we're not blocking concurrent requests - do unlock
@@ -193,6 +196,7 @@ func New(config ...Config) fiber.Handler {
}
}
+ e = manager.acquire()
// Cache response
e.body = utils.CopyBytes(c.Response().Body())
e.status = c.Response().StatusCode()
diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go
index d529ccd9f5..1cc3b7375b 100644
--- a/middleware/cache/cache_test.go
+++ b/middleware/cache/cache_test.go
@@ -47,9 +47,10 @@ func Test_Cache_Expired(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use(New(Config{Expiration: 2 * time.Second}))
-
+ count := 0
app.Get("/", func(c fiber.Ctx) error {
- return c.SendString(strconv.FormatInt(time.Now().UnixNano(), 10))
+ count++
+ return c.SendString(strconv.Itoa(count))
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
@@ -86,9 +87,10 @@ func Test_Cache(t *testing.T) {
app := fiber.New()
app.Use(New())
+ count := 0
app.Get("/", func(c fiber.Ctx) error {
- now := strconv.FormatInt(time.Now().UnixNano(), 10)
- return c.SendString(now)
+ count++
+ return c.SendString(strconv.Itoa(count))
})
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
@@ -305,9 +307,10 @@ func Test_Cache_Invalid_Expiration(t *testing.T) {
cache := New(Config{Expiration: 0 * time.Second})
app.Use(cache)
+ count := 0
app.Get("/", func(c fiber.Ctx) error {
- now := strconv.FormatInt(time.Now().UnixNano(), 10)
- return c.SendString(now)
+ count++
+ return c.SendString(strconv.Itoa(count))
})
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
@@ -414,8 +417,10 @@ func Test_Cache_NothingToCache(t *testing.T) {
app.Use(New(Config{Expiration: -(time.Second * 1)}))
+ count := 0
app.Get("/", func(c fiber.Ctx) error {
- return c.SendString(time.Now().String())
+ count++
+ return c.SendString(strconv.Itoa(count))
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
@@ -447,12 +452,16 @@ func Test_Cache_CustomNext(t *testing.T) {
CacheControl: true,
}))
+ count := 0
app.Get("/", func(c fiber.Ctx) error {
- return c.SendString(time.Now().String())
+ count++
+ return c.SendString(strconv.Itoa(count))
})
+ errorCount := 0
app.Get("/error", func(c fiber.Ctx) error {
- return c.Status(fiber.StatusInternalServerError).SendString(time.Now().String())
+ errorCount++
+ return c.Status(fiber.StatusInternalServerError).SendString(strconv.Itoa(errorCount))
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
@@ -508,9 +517,11 @@ func Test_CustomExpiration(t *testing.T) {
return time.Second * time.Duration(newCacheTime)
}}))
+ count := 0
app.Get("/", func(c fiber.Ctx) error {
+ count++
c.Response().Header.Add("Cache-Time", "1")
- return c.SendString(strconv.FormatInt(time.Now().UnixNano(), 10))
+ return c.SendString(strconv.Itoa(count))
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
@@ -588,8 +599,11 @@ func Test_CacheHeader(t *testing.T) {
return c.SendString(fiber.Query[string](c, "cache"))
})
+ count := 0
app.Get("/error", func(c fiber.Ctx) error {
- return c.Status(fiber.StatusInternalServerError).SendString(time.Now().String())
+ count++
+ c.Response().Header.Add("Cache-Time", "1")
+ return c.Status(fiber.StatusInternalServerError).SendString(strconv.Itoa(count))
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
@@ -615,10 +629,13 @@ func Test_Cache_WithHead(t *testing.T) {
app := fiber.New()
app.Use(New())
+ count := 0
handler := func(c fiber.Ctx) error {
- now := strconv.FormatInt(time.Now().UnixNano(), 10)
- return c.SendString(now)
+ count++
+ c.Response().Header.Add("Cache-Time", "1")
+ return c.SendString(strconv.Itoa(count))
}
+
app.Route("/").Get(handler).Head(handler)
req := httptest.NewRequest(fiber.MethodHead, "/", nil)
@@ -708,8 +725,10 @@ func Test_CacheInvalidation(t *testing.T) {
},
}))
+ count := 0
app.Get("/", func(c fiber.Ctx) error {
- return c.SendString(time.Now().String())
+ count++
+ return c.SendString(strconv.Itoa(count))
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
@@ -731,6 +750,93 @@ func Test_CacheInvalidation(t *testing.T) {
require.NotEqual(t, body, bodyInvalidate)
}
+func Test_CacheInvalidation_noCacheEntry(t *testing.T) {
+ t.Parallel()
+ t.Run("Cache Invalidator should not be called if no cache entry exist ", func(t *testing.T) {
+ t.Parallel()
+ app := fiber.New()
+ cacheInvalidatorExecuted := false
+ app.Use(New(Config{
+ CacheControl: true,
+ CacheInvalidator: func(c fiber.Ctx) bool {
+ cacheInvalidatorExecuted = true
+ return fiber.Query[bool](c, "invalidate")
+ },
+ MaxBytes: 10 * 1024 * 1024,
+ }))
+ _, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?invalidate=true", nil))
+ require.NoError(t, err)
+ require.False(t, cacheInvalidatorExecuted)
+ })
+}
+
+func Test_CacheInvalidation_removeFromHeap(t *testing.T) {
+ t.Parallel()
+ t.Run("Invalidate and remove from the heap", func(t *testing.T) {
+ t.Parallel()
+ app := fiber.New()
+ app.Use(New(Config{
+ CacheControl: true,
+ CacheInvalidator: func(c fiber.Ctx) bool {
+ return fiber.Query[bool](c, "invalidate")
+ },
+ MaxBytes: 10 * 1024 * 1024,
+ }))
+
+ count := 0
+ app.Get("/", func(c fiber.Ctx) error {
+ count++
+ return c.SendString(strconv.Itoa(count))
+ })
+
+ resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
+ require.NoError(t, err)
+ body, err := io.ReadAll(resp.Body)
+ require.NoError(t, err)
+
+ respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
+ require.NoError(t, err)
+ bodyCached, err := io.ReadAll(respCached.Body)
+ require.NoError(t, err)
+ require.True(t, bytes.Equal(body, bodyCached))
+ require.NotEmpty(t, respCached.Header.Get(fiber.HeaderCacheControl))
+
+ respInvalidate, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?invalidate=true", nil))
+ require.NoError(t, err)
+ bodyInvalidate, err := io.ReadAll(respInvalidate.Body)
+ require.NoError(t, err)
+ require.NotEqual(t, body, bodyInvalidate)
+ })
+}
+
+func Test_CacheStorage_CustomHeaders(t *testing.T) {
+ t.Parallel()
+ app := fiber.New()
+ app.Use(New(Config{
+ CacheControl: true,
+ Storage: memory.New(),
+ MaxBytes: 10 * 1024 * 1024,
+ }))
+
+ app.Get("/", func(c fiber.Ctx) error {
+ c.Response().Header.Set("Content-Type", "text/xml")
+ c.Response().Header.Set("Content-Encoding", "utf8")
+ return c.Send([]byte("Test"))
+ })
+
+ resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
+ require.NoError(t, err)
+ body, err := io.ReadAll(resp.Body)
+ require.NoError(t, err)
+
+ respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
+ require.NoError(t, err)
+ bodyCached, err := io.ReadAll(respCached.Body)
+ require.NoError(t, err)
+ require.True(t, bytes.Equal(body, bodyCached))
+ require.NotEmpty(t, respCached.Header.Get(fiber.HeaderCacheControl))
+}
+
// Because time points are updated once every X milliseconds, entries in tests can often have
// equal expiration times and thus be in an random order. This closure hands out increasing
// time intervals to maintain strong ascending order of expiration
diff --git a/middleware/cache/config.go b/middleware/cache/config.go
index b32e9e8ce0..b19be8974e 100644
--- a/middleware/cache/config.go
+++ b/middleware/cache/config.go
@@ -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
diff --git a/middleware/cache/heap.go b/middleware/cache/heap.go
index fa97871595..c5715392ef 100644
--- a/middleware/cache/heap.go
+++ b/middleware/cache/heap.go
@@ -15,7 +15,7 @@ type heapEntry struct {
// elements in constant time. It does so by handing out special indices
// and tracking entry movement.
//
-// indexdedHeap is used for quickly finding entries with the lowest
+// indexedHeap is used for quickly finding entries with the lowest
// expiration timestamp and deleting arbitrary entries.
type indexedHeap struct {
// Slice the heap is built on
diff --git a/middleware/cache/manager.go b/middleware/cache/manager.go
index c6ae542805..7e86dd1483 100644
--- a/middleware/cache/manager.go
+++ b/middleware/cache/manager.go
@@ -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
}
@@ -83,8 +83,7 @@ func (m *manager) get(key string) *item {
return it
}
if it, _ = m.memory.Get(key).(*item); it == nil { //nolint:errcheck // We store nothing else in the pool
- it = m.acquire()
- return it
+ return nil
}
return it
}
diff --git a/middleware/cache/manager_test.go b/middleware/cache/manager_test.go
new file mode 100644
index 0000000000..9aec55306f
--- /dev/null
+++ b/middleware/cache/manager_test.go
@@ -0,0 +1,26 @@
+package cache
+
+import (
+ "testing"
+ "time"
+
+ "github.com/gofiber/utils/v2"
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_manager_get(t *testing.T) {
+ t.Parallel()
+ cacheManager := newManager(nil)
+ t.Run("Item not found in cache", func(t *testing.T) {
+ t.Parallel()
+ assert.Nil(t, cacheManager.get(utils.UUID()))
+ })
+ t.Run("Item found in cache", func(t *testing.T) {
+ t.Parallel()
+ id := utils.UUID()
+ cacheItem := cacheManager.acquire()
+ cacheItem.body = []byte("test-body")
+ cacheManager.set(id, cacheItem, 10*time.Second)
+ assert.NotNil(t, cacheManager.get(id))
+ })
+}
diff --git a/middleware/compress/compress_test.go b/middleware/compress/compress_test.go
index 7d42c8bb58..34332e1202 100644
--- a/middleware/compress/compress_test.go
+++ b/middleware/compress/compress_test.go
@@ -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 {
diff --git a/middleware/cors/config.go b/middleware/cors/config.go
index 6e1d4697ba..2613bab943 100644
--- a/middleware/cors/config.go
+++ b/middleware/cors/config.go
@@ -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.
//
diff --git a/middleware/cors/cors_test.go b/middleware/cors/cors_test.go
index d95c0f5313..e255d5af37 100644
--- a/middleware/cors/cors_test.go
+++ b/middleware/cors/cors_test.go
@@ -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",
diff --git a/middleware/cors/utils_test.go b/middleware/cors/utils_test.go
index 4e8495772c..84f217e5d1 100644
--- a/middleware/cors/utils_test.go
+++ b/middleware/cors/utils_test.go
@@ -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 {
diff --git a/middleware/csrf/config.go b/middleware/csrf/config.go
index 7f9f94d224..d37c33a58e 100644
--- a/middleware/csrf/config.go
+++ b/middleware/csrf/config.go
@@ -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 "