mirror of
https://github.com/gofiber/fiber.git
synced 2025-02-06 10:23:55 +00:00
v3: Use Named Fields Instead of Positional and Align Structures to Reduce Memory Usage (#3079)
* Use composites for internal structures. Fix alignment of structures across Fiber * Update struct alignment in test files * Enable alignment check with govet * Fix ctx autoformat unit-test * Revert app Config struct. Add betteralign to Makefile * Disable comment on alert since it wont work for forks * Update benchmark.yml * Update benchmark.yml * Remove warning from using positional fields * Update router.go
This commit is contained in:
parent
f413bfef99
commit
8c3f81e2b7
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@ -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:
|
||||
|
@ -101,7 +101,6 @@ linters-settings:
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
- fieldalignment
|
||||
- shadow
|
||||
|
||||
grouper:
|
||||
|
5
Makefile
5
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 ./...
|
@ -11,10 +11,10 @@ import (
|
||||
func Test_ExponentialBackoff_Retry(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
expErr error
|
||||
expBackoff *ExponentialBackoff
|
||||
f func() error
|
||||
expErr error
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "With default values - successful",
|
||||
|
38
app.go
38
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: ""
|
||||
|
40
bind_test.go
40
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("")
|
||||
|
@ -12,9 +12,9 @@ import (
|
||||
|
||||
// ParserConfig form decoder config for SetParserDecoder
|
||||
type ParserConfig struct {
|
||||
IgnoreUnknownKeys bool
|
||||
SetAliasTag string
|
||||
ParserType []ParserType
|
||||
IgnoreUnknownKeys bool
|
||||
ZeroEmpty bool
|
||||
}
|
||||
|
||||
|
@ -34,21 +34,32 @@ var (
|
||||
// Fiber Client also provides an option to override
|
||||
// or merge most of the client settings at the request.
|
||||
type Client struct {
|
||||
mu sync.RWMutex
|
||||
// logger
|
||||
logger log.CommonLogger
|
||||
|
||||
fasthttp *fasthttp.Client
|
||||
|
||||
header *Header
|
||||
params *QueryParam
|
||||
cookies *Cookie
|
||||
path *PathParam
|
||||
|
||||
jsonMarshal utils.JSONMarshal
|
||||
jsonUnmarshal utils.JSONUnmarshal
|
||||
xmlMarshal utils.XMLMarshal
|
||||
xmlUnmarshal utils.XMLUnmarshal
|
||||
|
||||
cookieJar *CookieJar
|
||||
|
||||
// retry
|
||||
retryConfig *RetryConfig
|
||||
|
||||
baseURL string
|
||||
userAgent string
|
||||
referer string
|
||||
header *Header
|
||||
params *QueryParam
|
||||
cookies *Cookie
|
||||
path *PathParam
|
||||
|
||||
debug bool
|
||||
|
||||
timeout time.Duration
|
||||
// proxy
|
||||
proxyURL string
|
||||
|
||||
// user defined request hooks
|
||||
userRequestHooks []RequestHook
|
||||
@ -62,21 +73,11 @@ type Client struct {
|
||||
// client package defined response hooks
|
||||
builtinResponseHooks []ResponseHook
|
||||
|
||||
jsonMarshal utils.JSONMarshal
|
||||
jsonUnmarshal utils.JSONUnmarshal
|
||||
xmlMarshal utils.XMLMarshal
|
||||
xmlUnmarshal utils.XMLUnmarshal
|
||||
timeout time.Duration
|
||||
|
||||
cookieJar *CookieJar
|
||||
mu sync.RWMutex
|
||||
|
||||
// proxy
|
||||
proxyURL string
|
||||
|
||||
// retry
|
||||
retryConfig *RetryConfig
|
||||
|
||||
// logger
|
||||
logger log.CommonLogger
|
||||
debug bool
|
||||
}
|
||||
|
||||
// R raise a request from the client.
|
||||
@ -604,19 +605,20 @@ func (c *Client) Reset() {
|
||||
type Config struct {
|
||||
Ctx context.Context //nolint:containedctx // It's needed to be stored in the config.
|
||||
|
||||
UserAgent string
|
||||
Referer string
|
||||
Body any
|
||||
Header map[string]string
|
||||
Param map[string]string
|
||||
Cookie map[string]string
|
||||
PathParam map[string]string
|
||||
|
||||
FormData map[string]string
|
||||
|
||||
UserAgent string
|
||||
Referer string
|
||||
File []*File
|
||||
|
||||
Timeout time.Duration
|
||||
MaxRedirects int
|
||||
|
||||
Body any
|
||||
FormData map[string]string
|
||||
File []*File
|
||||
}
|
||||
|
||||
// setConfigToRequest Set the parameters passed via Config to Request.
|
||||
|
@ -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{
|
||||
|
@ -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.
|
||||
|
@ -22,8 +22,8 @@ func Test_AddMissing_Port(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "do anything",
|
||||
|
@ -40,28 +40,30 @@ var ErrClientNil = errors.New("client can not be nil")
|
||||
|
||||
// Request is a struct which contains the request data.
|
||||
type Request struct {
|
||||
url string
|
||||
method string
|
||||
userAgent string
|
||||
boundary string
|
||||
referer string
|
||||
ctx context.Context //nolint:containedctx // It's needed to be stored in the request.
|
||||
header *Header
|
||||
params *QueryParam
|
||||
cookies *Cookie
|
||||
path *PathParam
|
||||
ctx context.Context //nolint:containedctx // It's needed to be stored in the request.
|
||||
|
||||
body any
|
||||
header *Header
|
||||
params *QueryParam
|
||||
cookies *Cookie
|
||||
path *PathParam
|
||||
|
||||
client *Client
|
||||
|
||||
formData *FormData
|
||||
|
||||
RawRequest *fasthttp.Request
|
||||
url string
|
||||
method string
|
||||
userAgent string
|
||||
boundary string
|
||||
referer string
|
||||
files []*File
|
||||
|
||||
timeout time.Duration
|
||||
maxRedirects int
|
||||
|
||||
client *Client
|
||||
|
||||
body any
|
||||
formData *FormData
|
||||
files []*File
|
||||
bodyType bodyType
|
||||
|
||||
RawRequest *fasthttp.Request
|
||||
}
|
||||
|
||||
// Method returns http method in request.
|
||||
@ -782,10 +784,10 @@ func (f *FormData) Reset() {
|
||||
|
||||
// File is a struct which support send files via request.
|
||||
type File struct {
|
||||
reader io.ReadCloser
|
||||
name string
|
||||
fieldName string
|
||||
path string
|
||||
reader io.ReadCloser
|
||||
}
|
||||
|
||||
// SetName method sets file name.
|
||||
|
@ -222,12 +222,12 @@ func Test_Request_QueryParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
TInt int
|
||||
TString string
|
||||
TFloat float64
|
||||
TBool bool
|
||||
TSlice []string
|
||||
TIntSlice []int `param:"int_slice"`
|
||||
TInt int
|
||||
TFloat float64
|
||||
TBool bool
|
||||
}
|
||||
|
||||
p := AcquireRequest()
|
||||
@ -334,8 +334,8 @@ func Test_Request_Cookie(t *testing.T) {
|
||||
t.Run("set cookies with struct", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
type args struct {
|
||||
CookieInt int `cookie:"int"`
|
||||
CookieString string `cookie:"string"`
|
||||
CookieInt int `cookie:"int"`
|
||||
}
|
||||
|
||||
req := AcquireRequest().SetCookiesWithStruct(&args{
|
||||
@ -396,8 +396,8 @@ func Test_Request_PathParam(t *testing.T) {
|
||||
t.Run("set path params with struct", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
type args struct {
|
||||
CookieInt int `path:"int"`
|
||||
CookieString string `path:"string"`
|
||||
CookieInt int `path:"int"`
|
||||
}
|
||||
|
||||
req := AcquireRequest().SetPathParamsWithStruct(&args{
|
||||
@ -510,12 +510,12 @@ func Test_Request_FormData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
TInt int
|
||||
TString string
|
||||
TFloat float64
|
||||
TBool bool
|
||||
TSlice []string
|
||||
TIntSlice []int `form:"int_slice"`
|
||||
TInt int
|
||||
TFloat float64
|
||||
TBool bool
|
||||
}
|
||||
|
||||
p := AcquireRequest()
|
||||
@ -1299,13 +1299,13 @@ func Test_SetValWithStruct(t *testing.T) {
|
||||
|
||||
// test SetValWithStruct vai QueryParam struct.
|
||||
type args struct {
|
||||
unexport int
|
||||
TInt int
|
||||
TString string
|
||||
TFloat float64
|
||||
TBool bool
|
||||
TSlice []string
|
||||
TIntSlice []int `param:"int_slice"`
|
||||
unexport int
|
||||
TInt int
|
||||
TFloat float64
|
||||
TBool bool
|
||||
}
|
||||
|
||||
t.Run("the struct should be applied", func(t *testing.T) {
|
||||
@ -1453,13 +1453,13 @@ func Test_SetValWithStruct(t *testing.T) {
|
||||
func Benchmark_SetValWithStruct(b *testing.B) {
|
||||
// test SetValWithStruct vai QueryParam struct.
|
||||
type args struct {
|
||||
unexport int
|
||||
TInt int
|
||||
TString string
|
||||
TFloat float64
|
||||
TBool bool
|
||||
TSlice []string
|
||||
TIntSlice []int `param:"int_slice"`
|
||||
unexport int
|
||||
TInt int
|
||||
TFloat float64
|
||||
TBool bool
|
||||
}
|
||||
|
||||
b.Run("the struct should be applied", func(b *testing.B) {
|
||||
|
@ -19,9 +19,9 @@ import (
|
||||
type Response struct {
|
||||
client *Client
|
||||
request *Request
|
||||
cookie []*fasthttp.Cookie
|
||||
|
||||
RawResponse *fasthttp.Response
|
||||
cookie []*fasthttp.Cookie
|
||||
}
|
||||
|
||||
// setClient method sets client object in response instance.
|
||||
|
40
ctx.go
40
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
|
||||
method string // HTTP method
|
||||
methodINT int // HTTP method INT equivalent
|
||||
baseURI string // HTTP base uri
|
||||
path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer
|
||||
pathBuffer []byte // HTTP path buffer
|
||||
detectionPath string // Route detection path -> string copy from detectionPathBuffer
|
||||
detectionPathBuffer []byte // HTTP detectionPath buffer
|
||||
treePath string // Path for the search in the tree
|
||||
pathOriginal string // Original HTTP path
|
||||
values [maxParams]string // Route parameter values
|
||||
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
||||
matched bool // Non use route matched
|
||||
viewBindMap sync.Map // Default view map to bind template engine
|
||||
bind *Bind // Default bind reference
|
||||
redirect *Redirect // Default redirect reference
|
||||
values [maxParams]string // Route parameter values
|
||||
viewBindMap sync.Map // Default view map to bind template engine
|
||||
method string // HTTP method
|
||||
baseURI string // HTTP base uri
|
||||
path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer
|
||||
detectionPath string // Route detection path -> string copy from detectionPathBuffer
|
||||
treePath string // Path for the search in the tree
|
||||
pathOriginal string // Original HTTP path
|
||||
pathBuffer []byte // HTTP path buffer
|
||||
detectionPathBuffer []byte // HTTP detectionPath buffer
|
||||
redirectionMessages []string // Messages of the previous redirect
|
||||
indexRoute int // Index of the current route
|
||||
indexHandler int // Index of the current handler
|
||||
methodINT int // HTTP method INT equivalent
|
||||
matched bool // Non use route matched
|
||||
}
|
||||
|
||||
// SendFile defines configuration options when to transfer file with SendFile.
|
||||
@ -112,8 +112,8 @@ type SendFile struct {
|
||||
// sendFileStore is used to keep the SendFile configuration and the handler.
|
||||
type sendFileStore struct {
|
||||
handler fasthttp.RequestHandler
|
||||
config SendFile
|
||||
cacheControlValue string
|
||||
config SendFile
|
||||
}
|
||||
|
||||
// compareConfig compares the current SendFile config with the new one
|
||||
@ -175,15 +175,15 @@ type RangeSet struct {
|
||||
|
||||
// Cookie data for c.Cookie
|
||||
type Cookie struct {
|
||||
Expires time.Time `json:"expires"` // The expiration date of the cookie
|
||||
Name string `json:"name"` // The name of the cookie
|
||||
Value string `json:"value"` // The value of the cookie
|
||||
Path string `json:"path"` // Specifies a URL path which is allowed to receive the cookie
|
||||
Domain string `json:"domain"` // Specifies the domain which is allowed to receive the cookie
|
||||
SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
|
||||
MaxAge int `json:"max_age"` // The maximum age (in seconds) of the cookie
|
||||
Expires time.Time `json:"expires"` // The expiration date of the cookie
|
||||
Secure bool `json:"secure"` // Indicates that the cookie should only be transmitted over a secure HTTPS connection
|
||||
HTTPOnly bool `json:"http_only"` // Indicates that the cookie is accessible only through the HTTP protocol
|
||||
SameSite string `json:"same_site"` // Controls whether or not a cookie is sent with cross-site requests
|
||||
Partitioned bool `json:"partitioned"` // Indicates if the cookie is stored in a partitioned cookie jar
|
||||
SessionOnly bool `json:"session_only"` // Indicates if the cookie is a session-only cookie
|
||||
}
|
||||
@ -196,8 +196,8 @@ type Views interface {
|
||||
|
||||
// ResFmt associates a Content Type to a fiber.Handler for c.Format
|
||||
type ResFmt struct {
|
||||
MediaType string
|
||||
Handler func(Ctx) error
|
||||
MediaType string
|
||||
}
|
||||
|
||||
// Accepts checks if the specified extensions or content types are acceptable.
|
||||
@ -1285,8 +1285,8 @@ func (c *DefaultCtx) Range(size int) (Range, error) {
|
||||
Start int
|
||||
End int
|
||||
}{
|
||||
start,
|
||||
end,
|
||||
Start: start,
|
||||
End: end,
|
||||
})
|
||||
}
|
||||
if len(rangeData.Ranges) < 1 {
|
||||
|
86
ctx_test.go
86
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 {
|
||||
|
10
group.go
10
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.
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
61
listen.go
61
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.
|
||||
|
@ -79,10 +79,10 @@ func Test_Listen_Graceful_Shutdown(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Time time.Duration
|
||||
ExpectedBody string
|
||||
ExpectedStatusCode int
|
||||
ExpectedErr error
|
||||
ExpectedBody string
|
||||
Time time.Duration
|
||||
ExpectedStatusCode int
|
||||
}{
|
||||
{Time: 500 * time.Millisecond, ExpectedBody: "example.com", ExpectedStatusCode: StatusOK, ExpectedErr: nil},
|
||||
{Time: 3 * time.Second, ExpectedBody: "", ExpectedStatusCode: StatusOK, ExpectedErr: errors.New("InmemoryListener is already closed: use of closed network connection")},
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
54
middleware/cache/config.go
vendored
54
middleware/cache/config.go
vendored
@ -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
|
||||
|
2
middleware/cache/manager.go
vendored
2
middleware/cache/manager.go
vendored
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -13,11 +13,40 @@ import (
|
||||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Store is used to store the state of the middleware
|
||||
//
|
||||
// Optional. Default: memory.New()
|
||||
// Ignored if Session is set.
|
||||
Storage fiber.Storage
|
||||
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// Session is used to store the state of the middleware
|
||||
//
|
||||
// Optional. Default: nil
|
||||
// If set, the middleware will use the session store instead of the storage
|
||||
Session *session.Store
|
||||
|
||||
// KeyGenerator creates a new CSRF token
|
||||
//
|
||||
// Optional. Default: utils.UUID
|
||||
KeyGenerator func() string
|
||||
|
||||
// ErrorHandler is executed when an error is returned from fiber.Handler.
|
||||
//
|
||||
// Optional. Default: DefaultErrorHandler
|
||||
ErrorHandler fiber.ErrorHandler
|
||||
|
||||
// Extractor returns the csrf token
|
||||
//
|
||||
// If set this will be used in place of an Extractor based on KeyLookup.
|
||||
//
|
||||
// Optional. Default will create an Extractor based on KeyLookup.
|
||||
Extractor func(c fiber.Ctx) (string, error)
|
||||
|
||||
// KeyLookup is a string in the form of "<source>:<key>" that is used
|
||||
// to create an Extractor that extracts the token from the request.
|
||||
// Possible values:
|
||||
@ -45,45 +74,10 @@ type Config struct {
|
||||
// Optional. Default value "".
|
||||
CookiePath string
|
||||
|
||||
// Indicates if CSRF cookie is secure.
|
||||
// Optional. Default value false.
|
||||
CookieSecure bool
|
||||
|
||||
// Indicates if CSRF cookie is HTTP only.
|
||||
// Optional. Default value false.
|
||||
CookieHTTPOnly bool
|
||||
|
||||
// Value of SameSite cookie.
|
||||
// Optional. Default value "Lax".
|
||||
CookieSameSite string
|
||||
|
||||
// Decides whether cookie should last for only the browser sesison.
|
||||
// Ignores Expiration if set to true
|
||||
CookieSessionOnly bool
|
||||
|
||||
// Expiration is the duration before csrf token will expire
|
||||
//
|
||||
// Optional. Default: 1 * time.Hour
|
||||
Expiration time.Duration
|
||||
|
||||
// SingleUseToken indicates if the CSRF token be destroyed
|
||||
// and a new one generated on each use.
|
||||
//
|
||||
// Optional. Default: false
|
||||
SingleUseToken bool
|
||||
|
||||
// Store is used to store the state of the middleware
|
||||
//
|
||||
// Optional. Default: memory.New()
|
||||
// Ignored if Session is set.
|
||||
Storage fiber.Storage
|
||||
|
||||
// Session is used to store the state of the middleware
|
||||
//
|
||||
// Optional. Default: nil
|
||||
// If set, the middleware will use the session store instead of the storage
|
||||
Session *session.Store
|
||||
|
||||
// SessionKey is the key used to store the token in the session
|
||||
//
|
||||
// Default: "csrfToken"
|
||||
@ -102,22 +96,28 @@ type Config struct {
|
||||
// Optional. Default: []
|
||||
TrustedOrigins []string
|
||||
|
||||
// KeyGenerator creates a new CSRF token
|
||||
// Expiration is the duration before csrf token will expire
|
||||
//
|
||||
// Optional. Default: utils.UUID
|
||||
KeyGenerator func() string
|
||||
// Optional. Default: 1 * time.Hour
|
||||
Expiration time.Duration
|
||||
|
||||
// ErrorHandler is executed when an error is returned from fiber.Handler.
|
||||
//
|
||||
// Optional. Default: DefaultErrorHandler
|
||||
ErrorHandler fiber.ErrorHandler
|
||||
// Indicates if CSRF cookie is secure.
|
||||
// Optional. Default value false.
|
||||
CookieSecure bool
|
||||
|
||||
// Extractor returns the csrf token
|
||||
// Indicates if CSRF cookie is HTTP only.
|
||||
// Optional. Default value false.
|
||||
CookieHTTPOnly bool
|
||||
|
||||
// Decides whether cookie should last for only the browser sesison.
|
||||
// Ignores Expiration if set to true
|
||||
CookieSessionOnly bool
|
||||
|
||||
// SingleUseToken indicates if the CSRF token be destroyed
|
||||
// and a new one generated on each use.
|
||||
//
|
||||
// If set this will be used in place of an Extractor based on KeyLookup.
|
||||
//
|
||||
// Optional. Default will create an Extractor based on KeyLookup.
|
||||
Extractor func(c fiber.Ctx) (string, error)
|
||||
// Optional. Default: false
|
||||
SingleUseToken bool
|
||||
}
|
||||
|
||||
const HeaderName = "X-Csrf-Token"
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -11,18 +11,6 @@ type Config struct {
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// Array of cookie keys that should not be encrypted.
|
||||
//
|
||||
// Optional. Default: []
|
||||
Except []string
|
||||
|
||||
// Base64 encoded unique key to encode & decode cookies.
|
||||
//
|
||||
// Required. Key length should be 16, 24, or 32 bytes when decoded
|
||||
// if using the default EncryptCookie and DecryptCookie functions.
|
||||
// You may use `encryptcookie.GenerateKey(length)` to generate a new key.
|
||||
Key string
|
||||
|
||||
// Custom function to encrypt cookies.
|
||||
//
|
||||
// Optional. Default: EncryptCookie (using AES-GCM)
|
||||
@ -32,6 +20,18 @@ type Config struct {
|
||||
//
|
||||
// Optional. Default: DecryptCookie (using AES-GCM)
|
||||
Decryptor func(encryptedString, key string) (string, error)
|
||||
|
||||
// Base64 encoded unique key to encode & decode cookies.
|
||||
//
|
||||
// Required. Key length should be 16, 24, or 32 bytes when decoded
|
||||
// if using the default EncryptCookie and DecryptCookie functions.
|
||||
// You may use `encryptcookie.GenerateKey(length)` to generate a new key.
|
||||
Key string
|
||||
|
||||
// Array of cookie keys that should not be encrypted.
|
||||
//
|
||||
// Optional. Default: []
|
||||
Except []string
|
||||
}
|
||||
|
||||
// ConfigDefault is the default config
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -11,16 +11,17 @@ import (
|
||||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// FileSystem is an optional alternate filesystem to search for the favicon in.
|
||||
// An example of this could be an embedded or network filesystem
|
||||
//
|
||||
// Optional. Default: nil
|
||||
FileSystem fs.FS `json:"-"`
|
||||
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// Raw data of the favicon file
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Data []byte `json:"-"`
|
||||
|
||||
// File holds the path to an actual favicon that will be cached
|
||||
//
|
||||
// Optional. Default: ""
|
||||
@ -31,16 +32,15 @@ type Config struct {
|
||||
// Optional. Default: "/favicon.ico"
|
||||
URL string `json:"url"`
|
||||
|
||||
// FileSystem is an optional alternate filesystem to search for the favicon in.
|
||||
// An example of this could be an embedded or network filesystem
|
||||
//
|
||||
// Optional. Default: nil
|
||||
FileSystem fs.FS `json:"-"`
|
||||
|
||||
// CacheControl defines how the Cache-Control header in the response should be set
|
||||
//
|
||||
// Optional. Default: "public, max-age=31536000"
|
||||
CacheControl string `json:"cache_control"`
|
||||
|
||||
// Raw data of the favicon file
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Data []byte `json:"-"`
|
||||
}
|
||||
|
||||
// ConfigDefault is the default config
|
||||
|
@ -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
|
||||
|
@ -13,30 +13,6 @@ var ErrInvalidIdempotencyKey = errors.New("invalid idempotency key")
|
||||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: a function which skips the middleware on safe HTTP request method.
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// Lifetime is the maximum lifetime of an idempotency key.
|
||||
//
|
||||
// Optional. Default: 30 * time.Minute
|
||||
Lifetime time.Duration
|
||||
|
||||
// KeyHeader is the name of the header that contains the idempotency key.
|
||||
//
|
||||
// Optional. Default: X-Idempotency-Key
|
||||
KeyHeader string
|
||||
// KeyHeaderValidate defines a function to validate the syntax of the idempotency header.
|
||||
//
|
||||
// Optional. Default: a function which ensures the header is 36 characters long (the size of an UUID).
|
||||
KeyHeaderValidate func(string) error
|
||||
|
||||
// KeepResponseHeaders is a list of headers that should be kept from the original response.
|
||||
//
|
||||
// Optional. Default: nil (to keep all headers)
|
||||
KeepResponseHeaders []string
|
||||
|
||||
// Lock locks an idempotency key.
|
||||
//
|
||||
// Optional. Default: an in-memory locker for this process only.
|
||||
@ -46,6 +22,30 @@ type Config struct {
|
||||
//
|
||||
// Optional. Default: an in-memory storage for this process only.
|
||||
Storage fiber.Storage
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: a function which skips the middleware on safe HTTP request method.
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// KeyHeaderValidate defines a function to validate the syntax of the idempotency header.
|
||||
//
|
||||
// Optional. Default: a function which ensures the header is 36 characters long (the size of an UUID).
|
||||
KeyHeaderValidate func(string) error
|
||||
|
||||
// KeyHeader is the name of the header that contains the idempotency key.
|
||||
//
|
||||
// Optional. Default: X-Idempotency-Key
|
||||
KeyHeader string
|
||||
|
||||
// KeepResponseHeaders is a list of headers that should be kept from the original response.
|
||||
//
|
||||
// Optional. Default: nil (to keep all headers)
|
||||
KeepResponseHeaders []string
|
||||
|
||||
// Lifetime is the maximum lifetime of an idempotency key.
|
||||
//
|
||||
// Optional. Default: 30 * time.Minute
|
||||
Lifetime time.Duration
|
||||
}
|
||||
|
||||
// ConfigDefault is the default config
|
||||
|
@ -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 {
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -23,6 +23,11 @@ type Config struct {
|
||||
// Optional. Default: 401 Invalid or expired key
|
||||
ErrorHandler fiber.ErrorHandler
|
||||
|
||||
CustomKeyLookup KeyLookupFunc
|
||||
|
||||
// Validator is a function to validate key.
|
||||
Validator func(fiber.Ctx, string) (bool, error)
|
||||
|
||||
// KeyLookup is a string in the form of "<source>:<name>" that is used
|
||||
// to extract key from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
@ -34,14 +39,9 @@ type Config struct {
|
||||
// - "cookie:<name>"
|
||||
KeyLookup string
|
||||
|
||||
CustomKeyLookup KeyLookupFunc
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
// Optional. Default value "Bearer".
|
||||
AuthScheme string
|
||||
|
||||
// Validator is a function to validate key.
|
||||
Validator func(fiber.Ctx, string) (bool, error)
|
||||
}
|
||||
|
||||
// ConfigDefault is the default config
|
||||
|
@ -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 /
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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 (
|
||||
|
@ -7,12 +7,12 @@ import (
|
||||
|
||||
// Data is a struct to define some variables to use in custom logger function.
|
||||
type Data struct {
|
||||
Pid string
|
||||
ErrPaddingStr string
|
||||
ChainErr error
|
||||
TemplateChain [][]byte
|
||||
LogFuncChain []LogFunc
|
||||
Start time.Time
|
||||
Stop time.Time
|
||||
ChainErr error
|
||||
Timestamp atomic.Value
|
||||
Pid string
|
||||
ErrPaddingStr string
|
||||
TemplateChain [][]byte
|
||||
LogFuncChain []LogFunc
|
||||
}
|
||||
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,14 +15,6 @@ type Config struct {
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// Servers defines a list of <scheme>://<host> HTTP servers,
|
||||
//
|
||||
// which are used in a round-robin manner.
|
||||
// i.e.: "https://foobar.com, http://www.foobar.com"
|
||||
//
|
||||
// Required
|
||||
Servers []string
|
||||
|
||||
// ModifyRequest allows you to alter the request
|
||||
//
|
||||
// Optional. Default: nil
|
||||
@ -33,6 +25,22 @@ type Config struct {
|
||||
// Optional. Default: nil
|
||||
ModifyResponse fiber.Handler
|
||||
|
||||
// tls config for the http client.
|
||||
TlsConfig *tls.Config //nolint:stylecheck,revive // TODO: Rename to "TLSConfig" in v3
|
||||
|
||||
// Client is custom client when client config is complex.
|
||||
// Note that Servers, Timeout, WriteBufferSize, ReadBufferSize, TlsConfig
|
||||
// and DialDualStack will not be used if the client are set.
|
||||
Client *fasthttp.LBClient
|
||||
|
||||
// Servers defines a list of <scheme>://<host> HTTP servers,
|
||||
//
|
||||
// which are used in a round-robin manner.
|
||||
// i.e.: "https://foobar.com, http://www.foobar.com"
|
||||
//
|
||||
// Required
|
||||
Servers []string
|
||||
|
||||
// Timeout is the request timeout used when calling the proxy client
|
||||
//
|
||||
// Optional. Default: 1 second
|
||||
@ -47,14 +55,6 @@ type Config struct {
|
||||
// Per-connection buffer size for responses' writing.
|
||||
WriteBufferSize int
|
||||
|
||||
// tls config for the http client.
|
||||
TlsConfig *tls.Config //nolint:stylecheck,revive // TODO: Rename to "TLSConfig" in v3
|
||||
|
||||
// Client is custom client when client config is complex.
|
||||
// Note that Servers, Timeout, WriteBufferSize, ReadBufferSize, TlsConfig
|
||||
// and DialDualStack will not be used if the client are set.
|
||||
Client *fasthttp.LBClient
|
||||
|
||||
// Attempt to connect to both ipv4 and ipv6 host addresses if set to true.
|
||||
//
|
||||
// By default client connects only to ipv4 addresses, since unfortunately ipv6
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -10,14 +10,14 @@ import (
|
||||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Allowed session duration
|
||||
// Optional. Default value 24 * time.Hour
|
||||
Expiration time.Duration
|
||||
|
||||
// Storage interface to store the session data
|
||||
// Optional. Default value memory.New()
|
||||
Storage fiber.Storage
|
||||
|
||||
// KeyGenerator generates the session key.
|
||||
// Optional. Default value utils.UUIDv4
|
||||
KeyGenerator func() string
|
||||
|
||||
// KeyLookup is a string in the form of "<source>:<name>" that is used
|
||||
// to extract session id from the request.
|
||||
// Possible values: "header:<name>", "query:<name>" or "cookie:<name>"
|
||||
@ -32,6 +32,19 @@ type Config struct {
|
||||
// Optional. Default value "".
|
||||
CookiePath string
|
||||
|
||||
// Value of SameSite cookie.
|
||||
// Optional. Default value "Lax".
|
||||
CookieSameSite string
|
||||
|
||||
// Source defines where to obtain the session id
|
||||
source Source
|
||||
|
||||
// The session name
|
||||
sessionName string
|
||||
// Allowed session duration
|
||||
// Optional. Default value 24 * time.Hour
|
||||
Expiration time.Duration
|
||||
|
||||
// Indicates if cookie is secure.
|
||||
// Optional. Default value false.
|
||||
CookieSecure bool
|
||||
@ -40,24 +53,10 @@ type Config struct {
|
||||
// Optional. Default value false.
|
||||
CookieHTTPOnly bool
|
||||
|
||||
// Value of SameSite cookie.
|
||||
// Optional. Default value "Lax".
|
||||
CookieSameSite string
|
||||
|
||||
// Decides whether cookie should last for only the browser sesison.
|
||||
// Ignores Expiration if set to true
|
||||
// Optional. Default value false.
|
||||
CookieSessionOnly bool
|
||||
|
||||
// KeyGenerator generates the session key.
|
||||
// Optional. Default value utils.UUIDv4
|
||||
KeyGenerator func() string
|
||||
|
||||
// Source defines where to obtain the session id
|
||||
source Source
|
||||
|
||||
// The session name
|
||||
sessionName string
|
||||
}
|
||||
|
||||
type Source string
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -35,7 +35,7 @@ func New(config ...Config) *Store {
|
||||
}
|
||||
|
||||
return &Store{
|
||||
cfg,
|
||||
Config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,17 +9,44 @@ import (
|
||||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// FS is the file system to serve the static files from.
|
||||
// You can use interfaces compatible with fs.FS like embed.FS, os.DirFS etc.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
FS fs.FS
|
||||
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// ModifyResponse defines a function that allows you to alter the response.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
ModifyResponse fiber.Handler
|
||||
|
||||
// NotFoundHandler defines a function to handle when the path is not found.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
NotFoundHandler fiber.Handler
|
||||
|
||||
// The names of the index files for serving a directory.
|
||||
//
|
||||
// Optional. Default: []string{"index.html"}.
|
||||
IndexNames []string `json:"index"`
|
||||
|
||||
// Expiration duration for inactive file handlers.
|
||||
// Use a negative time.Duration to disable it.
|
||||
//
|
||||
// Optional. Default: 10 * time.Second.
|
||||
CacheDuration time.Duration `json:"cache_duration"`
|
||||
|
||||
// The value for the Cache-Control HTTP-header
|
||||
// that is set on the file response. MaxAge is defined in seconds.
|
||||
//
|
||||
// Optional. Default: 0.
|
||||
MaxAge int `json:"max_age"`
|
||||
|
||||
// When set to true, the server tries minimizing CPU usage by caching compressed files.
|
||||
// This works differently than the github.com/gofiber/compression middleware.
|
||||
//
|
||||
@ -40,33 +67,6 @@ type Config struct {
|
||||
//
|
||||
// Optional. Default: false.
|
||||
Download bool `json:"download"`
|
||||
|
||||
// The names of the index files for serving a directory.
|
||||
//
|
||||
// Optional. Default: []string{"index.html"}.
|
||||
IndexNames []string `json:"index"`
|
||||
|
||||
// Expiration duration for inactive file handlers.
|
||||
// Use a negative time.Duration to disable it.
|
||||
//
|
||||
// Optional. Default: 10 * time.Second.
|
||||
CacheDuration time.Duration `json:"cache_duration"`
|
||||
|
||||
// The value for the Cache-Control HTTP-header
|
||||
// that is set on the file response. MaxAge is defined in seconds.
|
||||
//
|
||||
// Optional. Default: 0.
|
||||
MaxAge int `json:"max_age"`
|
||||
|
||||
// ModifyResponse defines a function that allows you to alter the response.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
ModifyResponse fiber.Handler
|
||||
|
||||
// NotFoundHandler defines a function to handle when the path is not found.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
NotFoundHandler fiber.Handler
|
||||
}
|
||||
|
||||
// ConfigDefault is the default config
|
||||
|
@ -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",
|
||||
|
4
mount.go
4
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
|
||||
|
30
path.go
30
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
|
||||
// parameter information
|
||||
IsParam bool // Truth value that indicates whether it is a parameter or a constant part
|
||||
ParamName string // name of the parameter for access to it, for wildcards and plus parameters access iterators starting with 1 are added
|
||||
ComparePart string // search part to find the end of the parameter
|
||||
PartCount int // how often is the search part contained in the non-param segments? -> necessary for greedy search
|
||||
IsGreedy bool // indicates whether the parameter is greedy or not, is used with wildcard and plus
|
||||
IsOptional bool // indicates whether the parameter is optional or not
|
||||
// common information
|
||||
IsLast bool // shows if the segment is the last one for the route
|
||||
HasOptionalSlash bool // segment has the possibility of an optional slash
|
||||
Constraints []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default
|
||||
Length int // length of the parameter for segment, when its 0 then the length is undetermined
|
||||
Const string // constant part of the route
|
||||
ParamName string // name of the parameter for access to it, for wildcards and plus parameters access iterators starting with 1 are added
|
||||
ComparePart string // search part to find the end of the parameter
|
||||
Constraints []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default
|
||||
PartCount int // how often is the search part contained in the non-param segments? -> necessary for greedy search
|
||||
Length int // length of the parameter for segment, when its 0 then the length is undetermined
|
||||
// future TODO: add support for optional groups "/abc(/def)?"
|
||||
// parameter information
|
||||
IsParam bool // Truth value that indicates whether it is a parameter or a constant part
|
||||
IsGreedy bool // indicates whether the parameter is greedy or not, is used with wildcard and plus
|
||||
IsOptional bool // indicates whether the parameter is optional or not
|
||||
// common information
|
||||
IsLast bool // shows if the segment is the last one for the route
|
||||
HasOptionalSlash bool // segment has the possibility of an optional slash
|
||||
}
|
||||
|
||||
// different special routing signs
|
||||
@ -65,11 +65,11 @@ const (
|
||||
type TypeConstraint int16
|
||||
|
||||
type Constraint struct {
|
||||
ID TypeConstraint
|
||||
RegexCompiler *regexp.Regexp
|
||||
Data []string
|
||||
Name string
|
||||
Data []string
|
||||
customConstraints []CustomConstraint
|
||||
ID TypeConstraint
|
||||
}
|
||||
|
||||
// CustomConstraint is an interface for custom constraints
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
|
||||
type routeTestCase struct {
|
||||
url string
|
||||
match bool
|
||||
params []string
|
||||
match bool
|
||||
partialCheck bool
|
||||
}
|
||||
|
||||
|
@ -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()}
|
||||
}()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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: "/",
|
||||
|
25
router.go
25
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user