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/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/manager.go b/middleware/cache/manager.go index 0c99d014eb..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 } 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 ":" 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 - // - // If set this will be used in place of an Extractor based on KeyLookup. + // 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. // - // 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" diff --git a/middleware/csrf/csrf.go b/middleware/csrf/csrf.go index a04d85cb2f..182cbaea70 100644 --- a/middleware/csrf/csrf.go +++ b/middleware/csrf/csrf.go @@ -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 diff --git a/middleware/csrf/csrf_test.go b/middleware/csrf/csrf_test.go index e6c2ce8a58..e98902be19 100644 --- a/middleware/csrf/csrf_test.go +++ b/middleware/csrf/csrf_test.go @@ -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 { diff --git a/middleware/csrf/helpers_test.go b/middleware/csrf/helpers_test.go index bcb574e5a1..4540d72bc8 100644 --- a/middleware/csrf/helpers_test.go +++ b/middleware/csrf/helpers_test.go @@ -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 { diff --git a/middleware/csrf/session_manager.go b/middleware/csrf/session_manager.go index 87172eb838..3bbf173a26 100644 --- a/middleware/csrf/session_manager.go +++ b/middleware/csrf/session_manager.go @@ -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) } diff --git a/middleware/csrf/token.go b/middleware/csrf/token.go index ee88b9aee0..b96b013a80 100644 --- a/middleware/csrf/token.go +++ b/middleware/csrf/token.go @@ -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"` } diff --git a/middleware/encryptcookie/config.go b/middleware/encryptcookie/config.go index 80db08d997..de9ba455c8 100644 --- a/middleware/encryptcookie/config.go +++ b/middleware/encryptcookie/config.go @@ -11,10 +11,15 @@ type Config struct { // Optional. Default: nil Next func(c fiber.Ctx) bool - // Array of cookie keys that should not be encrypted. + // Custom function to encrypt cookies. // - // Optional. Default: [] - Except []string + // Optional. Default: EncryptCookie (using AES-GCM) + Encryptor func(decryptedString, key string) (string, error) + + // Custom function to decrypt cookies. + // + // Optional. Default: DecryptCookie (using AES-GCM) + Decryptor func(encryptedString, key string) (string, error) // Base64 encoded unique key to encode & decode cookies. // @@ -23,15 +28,10 @@ type Config struct { // You may use `encryptcookie.GenerateKey(length)` to generate a new key. Key string - // Custom function to encrypt cookies. - // - // Optional. Default: EncryptCookie (using AES-GCM) - Encryptor func(decryptedString, key string) (string, error) - - // Custom function to decrypt cookies. + // Array of cookie keys that should not be encrypted. // - // Optional. Default: DecryptCookie (using AES-GCM) - Decryptor func(encryptedString, key string) (string, error) + // Optional. Default: [] + Except []string } // ConfigDefault is the default config diff --git a/middleware/encryptcookie/encryptcookie_test.go b/middleware/encryptcookie/encryptcookie_test.go index c2f7639bb1..07e729471e 100644 --- a/middleware/encryptcookie/encryptcookie_test.go +++ b/middleware/encryptcookie/encryptcookie_test.go @@ -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 { diff --git a/middleware/envvar/envvar_test.go b/middleware/envvar/envvar_test.go index 197ca81eca..e34969b159 100644 --- a/middleware/envvar/envvar_test.go +++ b/middleware/envvar/envvar_test.go @@ -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) diff --git a/middleware/etag/config.go b/middleware/etag/config.go index 09512702f9..ccc479715f 100644 --- a/middleware/etag/config.go +++ b/middleware/etag/config.go @@ -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 diff --git a/middleware/favicon/favicon.go b/middleware/favicon/favicon.go index f1de00cb3d..e08b9b9507 100644 --- a/middleware/favicon/favicon.go +++ b/middleware/favicon/favicon.go @@ -11,15 +11,16 @@ import ( // Config defines the config for middleware. type Config struct { - // Next defines a function to skip this middleware when returned true. + // 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 - Next func(c fiber.Ctx) bool + FileSystem fs.FS `json:"-"` - // Raw data of the favicon file + // Next defines a function to skip this middleware when returned true. // // Optional. Default: nil - Data []byte `json:"-"` + Next func(c fiber.Ctx) bool // File holds the path to an actual favicon that will be cached // @@ -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 diff --git a/middleware/helmet/config.go b/middleware/helmet/config.go index dbbcd37759..c7fa3cab3c 100644 --- a/middleware/helmet/config.go +++ b/middleware/helmet/config.go @@ -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 diff --git a/middleware/idempotency/config.go b/middleware/idempotency/config.go index e002fcb100..414818a1fa 100644 --- a/middleware/idempotency/config.go +++ b/middleware/idempotency/config.go @@ -13,39 +13,39 @@ var ErrInvalidIdempotencyKey = errors.New("invalid idempotency key") // Config defines the config for middleware. type Config struct { + // Lock locks an idempotency key. + // + // Optional. Default: an in-memory locker for this process only. + Lock Locker + + // Storage stores response data by idempotency key. + // + // 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 - // Lifetime is the maximum lifetime of an idempotency key. + // KeyHeaderValidate defines a function to validate the syntax of the idempotency header. // - // Optional. Default: 30 * time.Minute - Lifetime time.Duration + // 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 - // 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. - Lock Locker - - // Storage stores response data by idempotency key. + // Lifetime is the maximum lifetime of an idempotency key. // - // Optional. Default: an in-memory storage for this process only. - Storage fiber.Storage + // Optional. Default: 30 * time.Minute + Lifetime time.Duration } // ConfigDefault is the default config diff --git a/middleware/idempotency/locker.go b/middleware/idempotency/locker.go index bf8bf0e065..2c3348b8f3 100644 --- a/middleware/idempotency/locker.go +++ b/middleware/idempotency/locker.go @@ -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 { diff --git a/middleware/idempotency/response.go b/middleware/idempotency/response.go index da09630faa..e101b05341 100644 --- a/middleware/idempotency/response.go +++ b/middleware/idempotency/response.go @@ -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"` } diff --git a/middleware/keyauth/config.go b/middleware/keyauth/config.go index 8c41d60f11..c7cb817201 100644 --- a/middleware/keyauth/config.go +++ b/middleware/keyauth/config.go @@ -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 ":" that is used // to extract key from the request. // Optional. Default value "header:Authorization". @@ -34,14 +39,9 @@ type Config struct { // - "cookie:" 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 diff --git a/middleware/keyauth/keyauth_test.go b/middleware/keyauth/keyauth_test.go index e59e0936f7..9da675fe8f 100644 --- a/middleware/keyauth/keyauth_test.go +++ b/middleware/keyauth/keyauth_test.go @@ -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 / { diff --git a/middleware/limiter/config.go b/middleware/limiter/config.go index 3045282eec..5570f96a12 100644 --- a/middleware/limiter/config.go +++ b/middleware/limiter/config.go @@ -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 diff --git a/middleware/logger/config.go b/middleware/logger/config.go index dcf4d45241..4826151e46 100644 --- a/middleware/logger/config.go +++ b/middleware/logger/config.go @@ -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 ( diff --git a/middleware/logger/data.go b/middleware/logger/data.go index 2d5955dc51..8191bfeab3 100644 --- a/middleware/logger/data.go +++ b/middleware/logger/data.go @@ -7,12 +7,12 @@ import ( // Data is a struct to define some variables to use in custom logger function. type Data struct { + Start time.Time + Stop time.Time + ChainErr error + Timestamp atomic.Value Pid string ErrPaddingStr string - ChainErr error TemplateChain [][]byte LogFuncChain []LogFunc - Start time.Time - Stop time.Time - Timestamp atomic.Value } diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go index 49644e5b78..0bc06531c9 100644 --- a/middleware/logger/logger_test.go +++ b/middleware/logger/logger_test.go @@ -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}, } } diff --git a/middleware/proxy/config.go b/middleware/proxy/config.go index 51ccc8ddf4..5edee17f90 100644 --- a/middleware/proxy/config.go +++ b/middleware/proxy/config.go @@ -15,14 +15,6 @@ type Config struct { // Optional. Default: nil Next func(c fiber.Ctx) bool - // Servers defines a list of :// 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 :// 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 diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go index 9dad5d5523..2ac1e2cb44 100644 --- a/middleware/proxy/proxy.go +++ b/middleware/proxy/proxy.go @@ -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. diff --git a/middleware/recover/config.go b/middleware/recover/config.go index a857ae5b94..1641c52806 100644 --- a/middleware/recover/config.go +++ b/middleware/recover/config.go @@ -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 diff --git a/middleware/redirect/config.go b/middleware/redirect/config.go index bebc0c02f7..ebd9d06750 100644 --- a/middleware/redirect/config.go +++ b/middleware/redirect/config.go @@ -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 diff --git a/middleware/requestid/config.go b/middleware/requestid/config.go index 16eddbdcf8..2e47f3b973 100644 --- a/middleware/requestid/config.go +++ b/middleware/requestid/config.go @@ -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 diff --git a/middleware/session/config.go b/middleware/session/config.go index b98eeb2553..1eabc05bd4 100644 --- a/middleware/session/config.go +++ b/middleware/session/config.go @@ -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 ":" that is used // to extract session id from the request. // Possible values: "header:", "query:" or "cookie:" @@ -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 diff --git a/middleware/session/data.go b/middleware/session/data.go index d80c767ea1..02dfc945c2 100644 --- a/middleware/session/data.go +++ b/middleware/session/data.go @@ -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{ diff --git a/middleware/session/session.go b/middleware/session/session.go index 7272ba18af..8a16590064 100644 --- a/middleware/session/session.go +++ b/middleware/session/session.go @@ -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{ diff --git a/middleware/session/store.go b/middleware/session/store.go index 05fba8e233..01b4548c0a 100644 --- a/middleware/session/store.go +++ b/middleware/session/store.go @@ -35,7 +35,7 @@ func New(config ...Config) *Store { } return &Store{ - cfg, + Config: cfg, } } diff --git a/middleware/static/config.go b/middleware/static/config.go index cc7a935744..dab313d35f 100644 --- a/middleware/static/config.go +++ b/middleware/static/config.go @@ -9,37 +9,26 @@ 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 - // When set to true, the server tries minimizing CPU usage by caching compressed files. - // This works differently than the github.com/gofiber/compression middleware. - // - // Optional. Default: false - Compress bool `json:"compress"` - - // When set to true, enables byte range requests. + // Next defines a function to skip this middleware when returned true. // - // Optional. Default: false - ByteRange bool `json:"byte_range"` + // Optional. Default: nil + Next func(c fiber.Ctx) bool - // When set to true, enables directory browsing. + // ModifyResponse defines a function that allows you to alter the response. // - // Optional. Default: false. - Browse bool `json:"browse"` + // Optional. Default: nil + ModifyResponse fiber.Handler - // When set to true, enables direct download. + // NotFoundHandler defines a function to handle when the path is not found. // - // Optional. Default: false. - Download bool `json:"download"` + // Optional. Default: nil + NotFoundHandler fiber.Handler // The names of the index files for serving a directory. // @@ -58,15 +47,26 @@ type Config struct { // Optional. Default: 0. MaxAge int `json:"max_age"` - // ModifyResponse defines a function that allows you to alter the response. + // When set to true, the server tries minimizing CPU usage by caching compressed files. + // This works differently than the github.com/gofiber/compression middleware. // - // Optional. Default: nil - ModifyResponse fiber.Handler + // Optional. Default: false + Compress bool `json:"compress"` - // NotFoundHandler defines a function to handle when the path is not found. + // When set to true, enables byte range requests. // - // Optional. Default: nil - NotFoundHandler fiber.Handler + // Optional. Default: false + ByteRange bool `json:"byte_range"` + + // When set to true, enables directory browsing. + // + // Optional. Default: false. + Browse bool `json:"browse"` + + // When set to true, enables direct download. + // + // Optional. Default: false. + Download bool `json:"download"` } // ConfigDefault is the default config diff --git a/middleware/static/static_test.go b/middleware/static/static_test.go index c4cc519dc2..8f457b23ad 100644 --- a/middleware/static/static_test.go +++ b/middleware/static/static_test.go @@ -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", diff --git a/mount.go b/mount.go index 9eab8aca48..07d7764888 100644 --- a/mount.go +++ b/mount.go @@ -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 diff --git a/path.go b/path.go index 0b0118a3f2..53b80b9f33 100644 --- a/path.go +++ b/path.go @@ -28,20 +28,20 @@ type routeParser struct { // routeSegment holds the segment metadata type routeSegment struct { // const information - Const string // constant part of the route + 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 - 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 + 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 - Constraints []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default - Length int // length of the parameter for segment, when its 0 then the length is undetermined - // future TODO: add support for optional groups "/abc(/def)?" + 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 diff --git a/path_testcases_test.go b/path_testcases_test.go index 26ec5b748e..d4c8dc8d7a 100644 --- a/path_testcases_test.go +++ b/path_testcases_test.go @@ -10,8 +10,8 @@ import ( type routeTestCase struct { url string - match bool params []string + match bool partialCheck bool } diff --git a/prefork.go b/prefork.go index 966b8bc199..b572d6a121 100644 --- a/prefork.go +++ b/prefork.go @@ -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()} }() } diff --git a/redirect.go b/redirect.go index 741a2e12a5..02311859cb 100644 --- a/redirect.go +++ b/redirect.go @@ -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() diff --git a/redirect_test.go b/redirect_test.go index 70f583ea9f..95a084b46c 100644 --- a/redirect_test.go +++ b/redirect_test.go @@ -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: "/", diff --git a/router.go b/router.go index bc93f67977..e0c9a5d60f 100644 --- a/router.go +++ b/router.go @@ -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 {