mirror of https://github.com/gofiber/fiber.git
Merge remote-tracking branch 'refs/remotes/origin/main' into generic_app_ctx_3221
# Conflicts: # app.go # ctx.go # ctx_interface.go # ctx_interface_gen.go # helpers.go # listen.go # router.gogeneric_app_ctx_3221
commit
334612f5b2
|
@ -17,6 +17,7 @@ categories:
|
|||
- title: '🧹 Updates'
|
||||
labels:
|
||||
- '🧹 Updates'
|
||||
- '⚡️ Performance'
|
||||
- title: '🐛 Fixes'
|
||||
labels:
|
||||
- '☢️ Bug'
|
||||
|
@ -48,6 +49,7 @@ version-resolver:
|
|||
- '☢️ Bug'
|
||||
- '🤖 Dependencies'
|
||||
- '🧹 Updates'
|
||||
- '⚡️ Performance'
|
||||
default: patch
|
||||
template: |
|
||||
$CHANGES
|
||||
|
|
|
@ -12,6 +12,7 @@ changelog:
|
|||
- title: '🧹 Updates'
|
||||
labels:
|
||||
- '🧹 Updates'
|
||||
- '⚡️ Performance'
|
||||
- title: '🐛 Bug Fixes'
|
||||
labels:
|
||||
- '☢️ Bug'
|
||||
|
|
|
@ -3,11 +3,11 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- main
|
||||
paths-ignore:
|
||||
- "**/*.md"
|
||||
paths:
|
||||
- "**.go"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "**/*.md"
|
||||
paths:
|
||||
- "**.go"
|
||||
|
||||
permissions:
|
||||
# deployments permission to deploy GitHub pages website
|
||||
|
|
|
@ -37,4 +37,7 @@ jobs:
|
|||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
# NOTE: Keep this in sync with the version from .golangci.yml
|
||||
version: v1.62.2
|
||||
version: v1.64.7
|
||||
# NOTE(ldez): temporary workaround
|
||||
install-mode: goinstall
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.23.x' }}
|
||||
uses: codecov/codecov-action@v5.3.1
|
||||
uses: codecov/codecov-action@v5.4.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./coverage.txt
|
||||
|
|
|
@ -7,7 +7,6 @@ run:
|
|||
|
||||
output:
|
||||
sort-results: true
|
||||
uniq-by-line: false
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
|
@ -187,7 +186,7 @@ linters-settings:
|
|||
- name: unchecked-type-assertion
|
||||
disabled: true # TODO: Do not disable
|
||||
- name: unhandled-error
|
||||
arguments: ['bytes\.Buffer\.Write']
|
||||
disabled: true
|
||||
|
||||
stylecheck:
|
||||
checks:
|
||||
|
@ -250,7 +249,10 @@ issues:
|
|||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
exclude-dirs:
|
||||
- internal # TODO: Do not ignore interal packages
|
||||
- internal # TODO: Do not ignore internal packages
|
||||
exclude-files:
|
||||
- '_msgp\.go'
|
||||
- '_msgp_test\.go'
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- err113
|
||||
|
@ -263,7 +265,10 @@ issues:
|
|||
linters:
|
||||
- bodyclose
|
||||
- err113
|
||||
# fix: true
|
||||
- source: 'fmt.Fprintf?'
|
||||
linters:
|
||||
- errcheck
|
||||
- revive
|
||||
|
||||
linters:
|
||||
enable:
|
||||
|
@ -358,7 +363,6 @@ linters:
|
|||
- stylecheck
|
||||
# - tagalign # TODO: Enable
|
||||
- tagliatelle
|
||||
- tenv
|
||||
- testableexamples
|
||||
- testifylint
|
||||
# - testpackage # TODO: Enable
|
||||
|
|
2
Makefile
2
Makefile
|
@ -35,7 +35,7 @@ markdown:
|
|||
## lint: 🚨 Run lint checks
|
||||
.PHONY: lint
|
||||
lint:
|
||||
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 run ./...
|
||||
golangci-lint run
|
||||
|
||||
## test: 🚦 Execute all tests
|
||||
.PHONY: test
|
||||
|
|
|
@ -19,17 +19,47 @@ a jitter is a way to break synchronization across the client and avoid collision
|
|||
## Signatures
|
||||
|
||||
```go
|
||||
func NewExponentialBackoff(config ...Config) *ExponentialBackoff
|
||||
func NewExponentialBackoff(config ...retry.Config) *retry.ExponentialBackoff
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Firstly, import the addon from Fiber,
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v3/addon/retry"
|
||||
"github.com/gofiber/fiber/v3/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
expBackoff := retry.NewExponentialBackoff(retry.Config{})
|
||||
|
||||
// Local variables that will be used inside of Retry
|
||||
var resp *client.Response
|
||||
var err error
|
||||
|
||||
// Retry a network request and return an error to signify to try again
|
||||
err = expBackoff.Retry(func() error {
|
||||
client := client.New()
|
||||
resp, err = client.Get("https://gofiber.io")
|
||||
if err != nil {
|
||||
return fmt.Errorf("GET gofiber.io failed: %w", err)
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("GET gofiber.io did not return OK 200")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// If all retries failed, panic
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("GET gofiber.io succeeded with status code %d\n", resp.StatusCode())
|
||||
}
|
||||
```
|
||||
|
||||
## Default Config
|
||||
|
@ -58,28 +88,23 @@ type Config struct {
|
|||
//
|
||||
// Optional. Default: 1 * time.Second
|
||||
InitialInterval time.Duration
|
||||
|
||||
|
||||
// MaxBackoffTime defines maximum time duration for backoff algorithm. When
|
||||
// the algorithm is reached this time, rest of the retries will be maximum
|
||||
// 32 seconds.
|
||||
//
|
||||
// Optional. Default: 32 * time.Second
|
||||
MaxBackoffTime time.Duration
|
||||
|
||||
|
||||
// Multiplier defines multiplier number of the backoff algorithm.
|
||||
//
|
||||
// Optional. Default: 2.0
|
||||
Multiplier float64
|
||||
|
||||
|
||||
// MaxRetryCount defines maximum retry count for the backoff algorithm.
|
||||
//
|
||||
// Optional. Default: 10
|
||||
MaxRetryCount int
|
||||
|
||||
// currentInterval tracks the current waiting time.
|
||||
//
|
||||
// Optional. Default: 1 * time.Second
|
||||
currentInterval time.Duration
|
||||
}
|
||||
```
|
||||
|
||||
|
|
36
app.go
36
app.go
|
@ -109,7 +109,7 @@ type App[TCtx CtxGeneric[TCtx]] struct {
|
|||
// Route stack divided by HTTP methods
|
||||
stack [][]*Route[TCtx]
|
||||
// Route stack divided by HTTP methods and route prefixes
|
||||
treeStack []map[string][]*Route[TCtx]
|
||||
treeStack []map[int][]*Route[TCtx]
|
||||
// custom binders
|
||||
customBinders []CustomBinder
|
||||
// customConstraints is a list of external constraints
|
||||
|
@ -456,17 +456,29 @@ const (
|
|||
DefaultWriteBufferSize = 4096
|
||||
)
|
||||
|
||||
const (
|
||||
methodGet = iota
|
||||
methodHead
|
||||
methodPost
|
||||
methodPut
|
||||
methodDelete
|
||||
methodConnect
|
||||
methodOptions
|
||||
methodTrace
|
||||
methodPatch
|
||||
)
|
||||
|
||||
// HTTP methods enabled by default
|
||||
var DefaultMethods = []string{
|
||||
MethodGet,
|
||||
MethodHead,
|
||||
MethodPost,
|
||||
MethodPut,
|
||||
MethodDelete,
|
||||
MethodConnect,
|
||||
MethodOptions,
|
||||
MethodTrace,
|
||||
MethodPatch,
|
||||
methodGet: MethodGet,
|
||||
methodHead: MethodHead,
|
||||
methodPost: MethodPost,
|
||||
methodPut: MethodPut,
|
||||
methodDelete: MethodDelete,
|
||||
methodConnect: MethodConnect,
|
||||
methodOptions: MethodOptions,
|
||||
methodTrace: MethodTrace,
|
||||
methodPatch: MethodPatch,
|
||||
}
|
||||
|
||||
// DefaultErrorHandler that process return errors from handlers
|
||||
|
@ -621,7 +633,7 @@ func newApp[TCtx CtxGeneric[TCtx]](config ...Config[TCtx]) *App[TCtx] {
|
|||
|
||||
// Create router stack
|
||||
app.stack = make([][]*Route[TCtx], len(app.config.RequestMethods))
|
||||
app.treeStack = make([]map[string][]*Route[TCtx], len(app.config.RequestMethods))
|
||||
app.treeStack = make([]map[int][]*Route[TCtx], len(app.config.RequestMethods))
|
||||
|
||||
// Override colors
|
||||
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
||||
|
@ -1048,7 +1060,7 @@ func (app *App[TCtx]) Test(req *http.Request, config ...TestConfig) (*http.Respo
|
|||
select {
|
||||
case err = <-channel:
|
||||
case <-time.After(cfg.Timeout):
|
||||
conn.Close() //nolint:errcheck, revive // It is fine to ignore the error here
|
||||
conn.Close() //nolint:errcheck // It is fine to ignore the error here
|
||||
if cfg.FailOnTimeout {
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
|
|
|
@ -403,7 +403,7 @@ func Test_App_serverErrorHandler_Internal_Error(t *testing.T) {
|
|||
t.Parallel()
|
||||
app := New()
|
||||
msg := "test err"
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
app.serverErrorHandler(c.fasthttp, errors.New(msg))
|
||||
require.Equal(t, string(c.fasthttp.Response.Body()), msg)
|
||||
|
@ -413,7 +413,7 @@ func Test_App_serverErrorHandler_Internal_Error(t *testing.T) {
|
|||
func Test_App_serverErrorHandler_Network_Error(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
app.serverErrorHandler(c.fasthttp, &net.DNSError{
|
||||
Err: "test error",
|
||||
|
|
|
@ -1378,7 +1378,7 @@ func Benchmark_Bind_URI(b *testing.B) {
|
|||
var err error
|
||||
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.route = &Route{
|
||||
Params: []string{
|
||||
|
@ -1415,7 +1415,7 @@ func Benchmark_Bind_URI_Map(b *testing.B) {
|
|||
var err error
|
||||
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.route = &Route{
|
||||
Params: []string{
|
||||
|
|
|
@ -28,7 +28,7 @@ Fiber provides several default binders out of the box:
|
|||
|
||||
### Binding into a Struct
|
||||
|
||||
Fiber supports binding request data directly into a struct using [gorilla/schema](https://github.com/gorilla/schema). Here's an example:
|
||||
Fiber supports binding request data directly into a struct using [gofiber/schema](https://github.com/gofiber/schema). Here's an example:
|
||||
|
||||
```go
|
||||
// Field names must start with an uppercase letter
|
||||
|
|
|
@ -58,19 +58,19 @@ func Benchmark_FormBinder_Bind(b *testing.B) {
|
|||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
binder := &QueryBinding{
|
||||
binder := &FormBinding{
|
||||
EnableSplitting: true,
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `query:"name"`
|
||||
Posts []string `query:"posts"`
|
||||
Age int `query:"age"`
|
||||
Name string `form:"name"`
|
||||
Posts []string `form:"posts"`
|
||||
Age int `form:"age"`
|
||||
}
|
||||
var user User
|
||||
|
||||
req := fasthttp.AcquireRequest()
|
||||
req.URI().SetQueryString("name=john&age=42&posts=post1,post2,post3")
|
||||
req.SetBodyString("name=john&age=42&posts=post1,post2,post3")
|
||||
req.Header.SetContentType("application/x-www-form-urlencoded")
|
||||
|
||||
b.ResetTimer()
|
||||
|
|
|
@ -87,7 +87,7 @@ func parse(aliasTag string, out any, data map[string][]string, files ...map[stri
|
|||
return parseToStruct(aliasTag, out, data, files...)
|
||||
}
|
||||
|
||||
// Parse data into the struct with gorilla/schema
|
||||
// Parse data into the struct with gofiber/schema
|
||||
func parseToStruct(aliasTag string, out any, data map[string][]string, files ...map[string][]*multipart.FileHeader) error {
|
||||
// Get decoder from pool
|
||||
schemaDecoder := decoderPoolMap[aliasTag].Get().(*schema.Decoder) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
|
|
@ -131,7 +131,7 @@ func testRequestFail(t *testing.T, handler fiber.Handler, wrapAgent func(agent *
|
|||
}
|
||||
}
|
||||
|
||||
func testClient(t *testing.T, handler fiber.Handler, wrapAgent func(agent *Client), excepted string, count ...int) { //nolint: unparam // maybe needed
|
||||
func testClient(t *testing.T, handler fiber.Handler, wrapAgent func(agent *Client), excepted string, count ...int) { //nolint:unparam // maybe needed
|
||||
t.Helper()
|
||||
|
||||
app, ln, start := createHelperServer(t)
|
||||
|
|
|
@ -199,7 +199,7 @@ func parserRequestBody(c *Client, req *Request) error {
|
|||
case filesBody:
|
||||
return parserRequestBodyFile(req)
|
||||
case rawBody:
|
||||
if body, ok := req.body.([]byte); ok {
|
||||
if body, ok := req.body.([]byte); ok { //nolint:revive // ignore simplicity
|
||||
req.RawRequest.SetBody(body)
|
||||
} else {
|
||||
return ErrBodyType
|
||||
|
|
|
@ -298,11 +298,12 @@ func (r *Request) Cookie(key string) string {
|
|||
// Use maps.Collect() to gather them into a map if needed.
|
||||
func (r *Request) Cookies() iter.Seq2[string, string] {
|
||||
return func(yield func(string, string) bool) {
|
||||
r.cookies.VisitAll(func(key, val string) {
|
||||
if !yield(key, val) {
|
||||
for k, v := range *r.cookies {
|
||||
res := yield(k, v)
|
||||
if !res {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,11 +344,11 @@ func (r *Request) PathParam(key string) string {
|
|||
// Use maps.Collect() to gather them into a map if needed.
|
||||
func (r *Request) PathParams() iter.Seq2[string, string] {
|
||||
return func(yield func(string, string) bool) {
|
||||
r.path.VisitAll(func(key, val string) {
|
||||
if !yield(key, val) {
|
||||
for k, v := range *r.path {
|
||||
if !yield(k, v) {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//nolint:goconst // Much easier to just ignore memory leaks in tests
|
||||
package client
|
||||
|
||||
import (
|
||||
|
@ -451,6 +452,14 @@ func Test_Request_Cookies(t *testing.T) {
|
|||
require.Equal(t, "bar", cookies["foo"])
|
||||
require.Equal(t, "foo", cookies["bar"])
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
for _, v := range req.Cookies() {
|
||||
if v == "bar" {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
require.Len(t, cookies, 2)
|
||||
}
|
||||
|
||||
|
@ -564,6 +573,14 @@ func Test_Request_PathParams(t *testing.T) {
|
|||
require.Equal(t, "foo", pathParams["bar"])
|
||||
|
||||
require.Len(t, pathParams, 2)
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
for _, v := range req.PathParams() {
|
||||
if v == "bar" {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Request_PathParams(b *testing.B) {
|
||||
|
@ -1579,7 +1596,7 @@ func Test_SetValWithStruct(t *testing.T) {
|
|||
|
||||
require.True(t, func() bool {
|
||||
for _, v := range p.PeekMulti("TSlice") {
|
||||
if string(v) == "bar" { //nolint:goconst // test
|
||||
if string(v) == "bar" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
115
ctx.go
115
ctx.go
|
@ -33,8 +33,11 @@ const (
|
|||
schemeHTTPS = "https"
|
||||
)
|
||||
|
||||
// maxParams defines the maximum number of parameters per route.
|
||||
const maxParams = 30
|
||||
const (
|
||||
// maxParams defines the maximum number of parameters per route.
|
||||
maxParams = 30
|
||||
maxDetectionPaths = 3
|
||||
)
|
||||
|
||||
// The contextKey type is unexported to prevent collisions with context keys defined in
|
||||
// other packages.
|
||||
|
@ -50,26 +53,25 @@ const userContextKey contextKey = 0 // __local_user_context__
|
|||
//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface CtxGeneric --pkg fiber --output ctx_interface.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on."
|
||||
//go:generate go run ctx_interface_gen.go
|
||||
type DefaultCtx struct {
|
||||
app *App[*DefaultCtx] // Reference to *App
|
||||
route *Route[*DefaultCtx] // Reference to *Route
|
||||
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
||||
bind *Bind // Default bind reference
|
||||
redirect *Redirect // Default redirect reference
|
||||
values [maxParams]string // Route parameter values
|
||||
viewBindMap sync.Map // Default view map to bind template engine
|
||||
method string // HTTP method
|
||||
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
|
||||
flashMessages redirectionMsgs // Flash messages
|
||||
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
|
||||
app *App[*DefaultCtx] // Reference to *App
|
||||
route *Route[*DefaultCtx] // Reference to *Route
|
||||
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
||||
bind *Bind // Default bind reference
|
||||
redirect *Redirect // Default redirect reference
|
||||
req *DefaultReq // Default request api reference
|
||||
res *DefaultRes // Default response api reference
|
||||
values [maxParams]string // Route parameter values
|
||||
viewBindMap sync.Map // Default view map to bind template engine
|
||||
baseURI string // HTTP base uri
|
||||
pathOriginal string // Original HTTP path
|
||||
flashMessages redirectionMsgs // Flash messages
|
||||
path []byte // HTTP path with the modifications by the configuration
|
||||
detectionPath []byte // Route detection path
|
||||
treePathHash int // Hash of the path for the search in the tree
|
||||
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
|
||||
}
|
||||
|
||||
type Ctx = CtxGeneric[*DefaultCtx]
|
||||
|
@ -1006,19 +1008,17 @@ func (c *DefaultCtx) Location(path string) {
|
|||
func (c *DefaultCtx) Method(override ...string) string {
|
||||
if len(override) == 0 {
|
||||
// Nothing to override, just return current method from context
|
||||
return c.method
|
||||
return c.app.method(c.methodInt)
|
||||
}
|
||||
|
||||
method := utils.ToUpper(override[0])
|
||||
mINT := c.app.methodInt(method)
|
||||
if mINT == -1 {
|
||||
methodInt := c.app.methodInt(method)
|
||||
if methodInt == -1 {
|
||||
// Provided override does not valid HTTP method, no override, return current method
|
||||
return c.method
|
||||
return c.app.method(c.methodInt)
|
||||
}
|
||||
|
||||
c.method = method
|
||||
c.methodINT = mINT
|
||||
return c.method
|
||||
c.methodInt = methodInt
|
||||
return method
|
||||
}
|
||||
|
||||
// MultipartForm parse form entries from binary.
|
||||
|
@ -1126,8 +1126,9 @@ func Params[V GenericType](c Ctx, key string, defaultValue ...V) V {
|
|||
|
||||
// Path returns the path part of the request URL.
|
||||
// Optionally, you could override the path.
|
||||
// Make copies or use the Immutable setting to use the value outside the Handler.
|
||||
func (c *DefaultCtx) Path(override ...string) string {
|
||||
if len(override) != 0 && c.path != override[0] {
|
||||
if len(override) != 0 && string(c.path) != override[0] {
|
||||
// Set new path to context
|
||||
c.pathOriginal = override[0]
|
||||
|
||||
|
@ -1136,7 +1137,7 @@ func (c *DefaultCtx) Path(override ...string) string {
|
|||
// Prettify path
|
||||
c.configDependentPaths()
|
||||
}
|
||||
return c.path
|
||||
return c.app.getString(c.path)
|
||||
}
|
||||
|
||||
// Scheme contains the request protocol string: http or https for TLS requests.
|
||||
|
@ -1352,7 +1353,7 @@ func (c *DefaultCtx) getLocationFromRoute(route Route, params Map) (string, erro
|
|||
|
||||
for key, val := range params {
|
||||
isSame := key == segment.ParamName || (!c.app.config.CaseSensitive && utils.EqualFold(key, segment.ParamName))
|
||||
isGreedy := segment.IsGreedy && len(key) == 1 && isInCharset(key[0], greedyParameters)
|
||||
isGreedy := segment.IsGreedy && len(key) == 1 && bytes.IndexByte(greedyParameters, key[0]) != -1
|
||||
if isSame || isGreedy {
|
||||
_, err := buf.WriteString(utils.ToString(val))
|
||||
if err != nil {
|
||||
|
@ -1468,6 +1469,18 @@ func (c *DefaultCtx) renderExtensions(bind any) {
|
|||
}
|
||||
}
|
||||
|
||||
// Req returns a convenience type whose API is limited to operations
|
||||
// on the incoming request.
|
||||
func (c *DefaultCtx) Req() Req {
|
||||
return c.req
|
||||
}
|
||||
|
||||
// Res returns a convenience type whose API is limited to operations
|
||||
// on the outgoing response.
|
||||
func (c *DefaultCtx) Res() Res {
|
||||
return c.res
|
||||
}
|
||||
|
||||
// Route returns the matched Route struct.
|
||||
func (c *DefaultCtx) Route() *Route {
|
||||
if c.route == nil {
|
||||
|
@ -1475,7 +1488,7 @@ func (c *DefaultCtx) Route() *Route {
|
|||
return &Route{
|
||||
path: c.pathOriginal,
|
||||
Path: c.pathOriginal,
|
||||
Method: c.method,
|
||||
Method: c.Method(),
|
||||
Handlers: make([]Handler, 0),
|
||||
Params: make([]string, 0),
|
||||
}
|
||||
|
@ -1823,32 +1836,31 @@ func (c *DefaultCtx) XHR() bool {
|
|||
// configDependentPaths set paths for route recognition and prepared paths for the user,
|
||||
// here the features for caseSensitive, decoded paths, strict paths are evaluated
|
||||
func (c *DefaultCtx) configDependentPaths() {
|
||||
c.pathBuffer = append(c.pathBuffer[0:0], c.pathOriginal...)
|
||||
c.path = append(c.path[:0], c.pathOriginal...)
|
||||
// If UnescapePath enabled, we decode the path and save it for the framework user
|
||||
if c.app.config.UnescapePath {
|
||||
c.pathBuffer = fasthttp.AppendUnquotedArg(c.pathBuffer[:0], c.pathBuffer)
|
||||
c.path = fasthttp.AppendUnquotedArg(c.path[:0], c.path)
|
||||
}
|
||||
c.path = c.app.getString(c.pathBuffer)
|
||||
|
||||
// another path is specified which is for routing recognition only
|
||||
// use the path that was changed by the previous configuration flags
|
||||
c.detectionPathBuffer = append(c.detectionPathBuffer[0:0], c.pathBuffer...)
|
||||
c.detectionPath = append(c.detectionPath[:0], c.path...)
|
||||
// If CaseSensitive is disabled, we lowercase the original path
|
||||
if !c.app.config.CaseSensitive {
|
||||
c.detectionPathBuffer = utils.ToLowerBytes(c.detectionPathBuffer)
|
||||
c.detectionPath = utils.ToLowerBytes(c.detectionPath)
|
||||
}
|
||||
// If StrictRouting is disabled, we strip all trailing slashes
|
||||
if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' {
|
||||
c.detectionPathBuffer = utils.TrimRight(c.detectionPathBuffer, '/')
|
||||
if !c.app.config.StrictRouting && len(c.detectionPath) > 1 && c.detectionPath[len(c.detectionPath)-1] == '/' {
|
||||
c.detectionPath = utils.TrimRight(c.detectionPath, '/')
|
||||
}
|
||||
c.detectionPath = c.app.getString(c.detectionPathBuffer)
|
||||
|
||||
// Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed,
|
||||
// since the first three characters area select a list of routes
|
||||
c.treePath = c.treePath[0:0]
|
||||
const maxDetectionPaths = 3
|
||||
c.treePathHash = 0
|
||||
if len(c.detectionPath) >= maxDetectionPaths {
|
||||
c.treePath = c.detectionPath[:maxDetectionPaths]
|
||||
c.treePathHash = int(c.detectionPath[0])<<16 |
|
||||
int(c.detectionPath[1])<<8 |
|
||||
int(c.detectionPath[2])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1909,8 +1921,7 @@ func (c *DefaultCtx) Reset(fctx *fasthttp.RequestCtx) {
|
|||
// Set paths
|
||||
c.pathOriginal = c.app.getString(fctx.URI().PathOriginal())
|
||||
// Set method
|
||||
c.method = c.app.getString(fctx.Request.Header.Method())
|
||||
c.methodINT = c.app.methodInt(c.method)
|
||||
c.methodInt = c.app.methodInt(utils.UnsafeString(fctx.Request.Header.Method()))
|
||||
// Attach *fasthttp.RequestCtx to ctx
|
||||
c.fasthttp = fctx
|
||||
// reset base uri
|
||||
|
@ -1941,20 +1952,20 @@ func (c *DefaultCtx) getBody() []byte {
|
|||
}
|
||||
|
||||
// Methods to use with next stack.
|
||||
func (c *DefaultCtx) getMethodINT() int {
|
||||
return c.methodINT
|
||||
func (c *DefaultCtx) getMethodInt() int {
|
||||
return c.methodInt
|
||||
}
|
||||
|
||||
func (c *DefaultCtx) getIndexRoute() int {
|
||||
return c.indexRoute
|
||||
}
|
||||
|
||||
func (c *DefaultCtx) getTreePath() string {
|
||||
return c.treePath
|
||||
func (c *DefaultCtx) getTreePathHash() int {
|
||||
return c.treePathHash
|
||||
}
|
||||
|
||||
func (c *DefaultCtx) getDetectionPath() string {
|
||||
return c.detectionPath
|
||||
return c.app.getString(c.detectionPath)
|
||||
}
|
||||
|
||||
func (c *DefaultCtx) getPathOriginal() string {
|
||||
|
|
|
@ -339,9 +339,9 @@ type CtxGeneric[T any] interface {
|
|||
release()
|
||||
getBody() []byte
|
||||
// Methods to use with next stack.
|
||||
getMethodINT() int
|
||||
getMethodInt() int
|
||||
getIndexRoute() int
|
||||
getTreePath() string
|
||||
getTreePathHash() int
|
||||
getDetectionPath() string
|
||||
getPathOriginal() string
|
||||
getValues() *[maxParams]string
|
||||
|
@ -349,11 +349,47 @@ type CtxGeneric[T any] interface {
|
|||
setIndexHandler(handler int)
|
||||
setIndexRoute(route int)
|
||||
setMatched(matched bool)
|
||||
setRoute(route *Route[T])
|
||||
// Drop closes the underlying connection without sending any response headers or body.
|
||||
// This can be useful for silently terminating client connections, such as in DDoS mitigation
|
||||
// or when blocking access to sensitive endpoints.
|
||||
Drop() error
|
||||
// End immediately flushes the current response and closes the underlying connection.
|
||||
End() error
|
||||
setRoute(route *Route)
|
||||
}
|
||||
|
||||
func NewDefaultCtx(app *App) *DefaultCtx {
|
||||
// return ctx
|
||||
ctx := &DefaultCtx{
|
||||
// Set app reference
|
||||
app: app,
|
||||
}
|
||||
ctx.req = &DefaultReq{ctx: ctx}
|
||||
ctx.res = &DefaultRes{ctx: ctx}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (app *App) newCtx() Ctx {
|
||||
var c Ctx
|
||||
|
||||
if app.newCtxFunc != nil {
|
||||
c = app.newCtxFunc(app)
|
||||
} else {
|
||||
c = NewDefaultCtx(app)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// AcquireCtx retrieves a new Ctx from the pool.
|
||||
func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx {
|
||||
ctx, ok := app.pool.Get().(Ctx)
|
||||
|
||||
if !ok {
|
||||
panic(errors.New("failed to type-assert to Ctx"))
|
||||
}
|
||||
ctx.Reset(fctx)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// ReleaseCtx releases the ctx back into the pool.
|
||||
func (app *App) ReleaseCtx(c Ctx) {
|
||||
c.release()
|
||||
app.pool.Put(c)
|
||||
}
|
||||
|
|
144
ctx_test.go
144
ctx_test.go
|
@ -46,7 +46,7 @@ func Test_Ctx_Accepts(t *testing.T) {
|
|||
|
||||
c.Request().Header.Set(HeaderAccept, "text/html,application/xhtml+xml,application/xml;q=0.9")
|
||||
require.Equal(t, "", c.Accepts(""))
|
||||
require.Equal(t, "", c.Accepts())
|
||||
require.Equal(t, "", c.Req().Accepts())
|
||||
require.Equal(t, ".xml", c.Accepts(".xml"))
|
||||
require.Equal(t, "", c.Accepts(".john"))
|
||||
require.Equal(t, "application/xhtml+xml", c.Accepts("application/xml", "application/xml+rss", "application/yaml", "application/xhtml+xml"), "must use client-preferred mime type")
|
||||
|
@ -57,13 +57,13 @@ func Test_Ctx_Accepts(t *testing.T) {
|
|||
c.Request().Header.Set(HeaderAccept, "text/*, application/json")
|
||||
require.Equal(t, "html", c.Accepts("html"))
|
||||
require.Equal(t, "text/html", c.Accepts("text/html"))
|
||||
require.Equal(t, "json", c.Accepts("json", "text"))
|
||||
require.Equal(t, "json", c.Req().Accepts("json", "text"))
|
||||
require.Equal(t, "application/json", c.Accepts("application/json"))
|
||||
require.Equal(t, "", c.Accepts("image/png"))
|
||||
require.Equal(t, "", c.Accepts("png"))
|
||||
|
||||
c.Request().Header.Set(HeaderAccept, "text/html, application/json")
|
||||
require.Equal(t, "text/*", c.Accepts("text/*"))
|
||||
require.Equal(t, "text/*", c.Req().Accepts("text/*"))
|
||||
|
||||
c.Request().Header.Set(HeaderAccept, "*/*")
|
||||
require.Equal(t, "html", c.Accepts("html"))
|
||||
|
@ -192,7 +192,7 @@ func Test_Ctx_AcceptsCharsets(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsCharsets -benchmem -count=4
|
||||
func Benchmark_Ctx_AcceptsCharsets(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5")
|
||||
var res string
|
||||
|
@ -218,7 +218,7 @@ func Test_Ctx_AcceptsEncodings(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsEncodings -benchmem -count=4
|
||||
func Benchmark_Ctx_AcceptsEncodings(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5")
|
||||
var res string
|
||||
|
@ -243,7 +243,7 @@ func Test_Ctx_AcceptsLanguages(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsLanguages -benchmem -count=4
|
||||
func Benchmark_Ctx_AcceptsLanguages(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().Header.Set(HeaderAcceptLanguage, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5")
|
||||
var res string
|
||||
|
@ -304,7 +304,7 @@ func Test_Ctx_Append(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_Append -benchmem -count=4
|
||||
func Benchmark_Ctx_Append(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -337,7 +337,7 @@ func Test_Ctx_Attachment(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_Attachment -benchmem -count=4
|
||||
func Benchmark_Ctx_Attachment(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -363,7 +363,7 @@ func Test_Ctx_BaseURL(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_BaseURL -benchmem
|
||||
func Benchmark_Ctx_BaseURL(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetHost("google.com:1337")
|
||||
c.Request().URI().SetPath("/haha/oke/lol")
|
||||
|
@ -380,7 +380,7 @@ func Benchmark_Ctx_BaseURL(b *testing.B) {
|
|||
func Test_Ctx_Body(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetBody([]byte("john=doe"))
|
||||
require.Equal(t, []byte("john=doe"), c.Body())
|
||||
|
@ -390,7 +390,7 @@ func Test_Ctx_Body(t *testing.T) {
|
|||
func Test_Ctx_BodyRaw(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetBodyRaw([]byte("john=doe"))
|
||||
require.Equal(t, []byte("john=doe"), c.BodyRaw())
|
||||
|
@ -400,7 +400,7 @@ func Test_Ctx_BodyRaw(t *testing.T) {
|
|||
func Test_Ctx_BodyRaw_Immutable(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New(Config{Immutable: true})
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetBodyRaw([]byte("john=doe"))
|
||||
require.Equal(t, []byte("john=doe"), c.BodyRaw())
|
||||
|
@ -411,7 +411,7 @@ func Benchmark_Ctx_Body(b *testing.B) {
|
|||
const input = "john=doe"
|
||||
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetBody([]byte(input))
|
||||
b.ReportAllocs()
|
||||
|
@ -428,7 +428,7 @@ func Benchmark_Ctx_BodyRaw(b *testing.B) {
|
|||
const input = "john=doe"
|
||||
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetBodyRaw([]byte(input))
|
||||
b.ReportAllocs()
|
||||
|
@ -445,7 +445,7 @@ func Benchmark_Ctx_BodyRaw_Immutable(b *testing.B) {
|
|||
const input = "john=doe"
|
||||
|
||||
app := New(Config{Immutable: true})
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetBodyRaw([]byte(input))
|
||||
b.ReportAllocs()
|
||||
|
@ -462,7 +462,7 @@ func Test_Ctx_Body_Immutable(t *testing.T) {
|
|||
t.Parallel()
|
||||
app := New()
|
||||
app.config.Immutable = true
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetBody([]byte("john=doe"))
|
||||
require.Equal(t, []byte("john=doe"), c.Body())
|
||||
|
@ -474,7 +474,7 @@ func Benchmark_Ctx_Body_Immutable(b *testing.B) {
|
|||
|
||||
app := New()
|
||||
app.config.Immutable = true
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().SetBody([]byte(input))
|
||||
b.ReportAllocs()
|
||||
|
@ -527,7 +527,7 @@ func Test_Ctx_Body_With_Compression(t *testing.T) {
|
|||
t.Run(tCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
c.Request().Header.Set("Content-Encoding", tCase.contentEncoding)
|
||||
|
||||
if strings.Contains(tCase.contentEncoding, "gzip") {
|
||||
|
@ -720,7 +720,7 @@ func Test_Ctx_Body_With_Compression_Immutable(t *testing.T) {
|
|||
t.Parallel()
|
||||
app := New()
|
||||
app.config.Immutable = true
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
c.Request().Header.Set("Content-Encoding", tCase.contentEncoding)
|
||||
|
||||
if strings.Contains(tCase.contentEncoding, "gzip") {
|
||||
|
@ -897,7 +897,7 @@ func Test_Ctx_Context(t *testing.T) {
|
|||
t.Parallel()
|
||||
testKey := struct{}{}
|
||||
testValue := "Test Value"
|
||||
ctx := context.WithValue(context.Background(), testKey, testValue) //nolint: staticcheck // not needed for tests
|
||||
ctx := context.WithValue(context.Background(), testKey, testValue) //nolint:staticcheck // not needed for tests
|
||||
require.Equal(t, testValue, ctx.Value(testKey))
|
||||
})
|
||||
}
|
||||
|
@ -910,7 +910,7 @@ func Test_Ctx_SetContext(t *testing.T) {
|
|||
|
||||
testKey := struct{}{}
|
||||
testValue := "Test Value"
|
||||
ctx := context.WithValue(context.Background(), testKey, testValue) //nolint: staticcheck // not needed for tests
|
||||
ctx := context.WithValue(context.Background(), testKey, testValue) //nolint:staticcheck // not needed for tests
|
||||
c.SetContext(ctx)
|
||||
require.Equal(t, testValue, c.Context().Value(testKey))
|
||||
}
|
||||
|
@ -930,7 +930,7 @@ func Test_Ctx_Context_Multiple_Requests(t *testing.T) {
|
|||
}
|
||||
|
||||
input := utils.CopyString(Query(c, "input", "NO_VALUE"))
|
||||
ctx = context.WithValue(ctx, testKey, fmt.Sprintf("%s_%s", testValue, input)) //nolint: staticcheck // not needed for tests
|
||||
ctx = context.WithValue(ctx, testKey, fmt.Sprintf("%s_%s", testValue, input)) //nolint:staticcheck // not needed for tests
|
||||
c.SetContext(ctx)
|
||||
|
||||
return c.Status(StatusOK).SendString(fmt.Sprintf("resp_%s_returned", input))
|
||||
|
@ -968,52 +968,52 @@ func Test_Ctx_Cookie(t *testing.T) {
|
|||
Expires: expire,
|
||||
// SameSite: CookieSameSiteStrictMode, // default is "lax"
|
||||
}
|
||||
c.Cookie(cookie)
|
||||
c.Res().Cookie(cookie)
|
||||
expect := "username=john; expires=" + httpdate + "; path=/; SameSite=Lax"
|
||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
||||
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||
|
||||
expect = "username=john; expires=" + httpdate + "; path=/"
|
||||
cookie.SameSite = CookieSameSiteDisabled
|
||||
c.Cookie(cookie)
|
||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
||||
c.Res().Cookie(cookie)
|
||||
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||
|
||||
expect = "username=john; expires=" + httpdate + "; path=/; SameSite=Strict"
|
||||
cookie.SameSite = CookieSameSiteStrictMode
|
||||
c.Cookie(cookie)
|
||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
||||
c.Res().Cookie(cookie)
|
||||
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||
|
||||
expect = "username=john; expires=" + httpdate + "; path=/; secure; SameSite=None"
|
||||
cookie.Secure = true
|
||||
cookie.SameSite = CookieSameSiteNoneMode
|
||||
c.Cookie(cookie)
|
||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
||||
c.Res().Cookie(cookie)
|
||||
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||
|
||||
expect = "username=john; path=/; secure; SameSite=None"
|
||||
// should remove expires and max-age headers
|
||||
cookie.SessionOnly = true
|
||||
cookie.Expires = expire
|
||||
cookie.MaxAge = 10000
|
||||
c.Cookie(cookie)
|
||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
||||
c.Res().Cookie(cookie)
|
||||
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||
|
||||
expect = "username=john; path=/; secure; SameSite=None"
|
||||
// should remove expires and max-age headers when no expire and no MaxAge (default time)
|
||||
cookie.SessionOnly = false
|
||||
cookie.Expires = time.Time{}
|
||||
cookie.MaxAge = 0
|
||||
c.Cookie(cookie)
|
||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
||||
c.Res().Cookie(cookie)
|
||||
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||
|
||||
expect = "username=john; path=/; secure; SameSite=None; Partitioned"
|
||||
cookie.Partitioned = true
|
||||
c.Cookie(cookie)
|
||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
||||
c.Res().Cookie(cookie)
|
||||
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||
}
|
||||
|
||||
// go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4
|
||||
func Benchmark_Ctx_Cookie(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -1033,8 +1033,8 @@ func Test_Ctx_Cookies(t *testing.T) {
|
|||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
|
||||
c.Request().Header.Set("Cookie", "john=doe")
|
||||
require.Equal(t, "doe", c.Cookies("john"))
|
||||
require.Equal(t, "default", c.Cookies("unknown", "default"))
|
||||
require.Equal(t, "doe", c.Req().Cookies("john"))
|
||||
require.Equal(t, "default", c.Req().Cookies("unknown", "default"))
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Format
|
||||
|
@ -1058,13 +1058,13 @@ func Test_Ctx_Format(t *testing.T) {
|
|||
}
|
||||
|
||||
c.Request().Header.Set(HeaderAccept, `text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7`)
|
||||
err := c.Format(formatHandlers("application/xhtml+xml", "application/xml", "foo/bar")...)
|
||||
err := c.Res().Format(formatHandlers("application/xhtml+xml", "application/xml", "foo/bar")...)
|
||||
require.Equal(t, "application/xhtml+xml", accepted)
|
||||
require.Equal(t, "application/xhtml+xml", c.GetRespHeader(HeaderContentType))
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode())
|
||||
|
||||
err = c.Format(formatHandlers("foo/bar;a=b")...)
|
||||
err = c.Res().Format(formatHandlers("foo/bar;a=b")...)
|
||||
require.Equal(t, "foo/bar;a=b", accepted)
|
||||
require.Equal(t, "foo/bar;a=b", c.GetRespHeader(HeaderContentType))
|
||||
require.NoError(t, err)
|
||||
|
@ -1165,7 +1165,7 @@ func Test_Ctx_AutoFormat(t *testing.T) {
|
|||
require.Equal(t, "Hello, World!", string(c.Response().Body()))
|
||||
|
||||
c.Request().Header.Set(HeaderAccept, MIMETextHTML)
|
||||
err = c.AutoFormat("Hello, World!")
|
||||
err = c.Res().AutoFormat("Hello, World!")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "<p>Hello, World!</p>", string(c.Response().Body()))
|
||||
|
||||
|
@ -1175,7 +1175,7 @@ func Test_Ctx_AutoFormat(t *testing.T) {
|
|||
require.Equal(t, `"Hello, World!"`, string(c.Response().Body()))
|
||||
|
||||
c.Request().Header.Set(HeaderAccept, MIMETextPlain)
|
||||
err = c.AutoFormat(complex(1, 1))
|
||||
err = c.Res().AutoFormat(complex(1, 1))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "(1+1i)", string(c.Response().Body()))
|
||||
|
||||
|
@ -1544,12 +1544,12 @@ func Test_Ctx_Binders(t *testing.T) {
|
|||
t.Run("URI", func(t *testing.T) {
|
||||
t.Skip("URI is not ready for v3")
|
||||
//nolint:gocritic // TODO: uncomment
|
||||
//t.Parallel()
|
||||
//withValues(t, func(c Ctx, testStruct *TestStruct) error {
|
||||
// t.Parallel()
|
||||
// withValues(t, func(c Ctx, testStruct *TestStruct) error {
|
||||
// c.Route().Params = []string{"name", "name2", "class", "class2"}
|
||||
// c.Params().value = [30]string{"foo", "bar", "111", "222"}
|
||||
// return c.Bind().URI(testStruct)
|
||||
//})
|
||||
// })
|
||||
})
|
||||
t.Run("ReqHeader", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
@ -2566,7 +2566,7 @@ func Test_Ctx_Params_Case_Sensitive(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_Params -benchmem -count=4
|
||||
func Benchmark_Ctx_Params(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.route = &Route{
|
||||
Params: []string{
|
||||
|
@ -2939,7 +2939,7 @@ func Test_Ctx_SaveFile(t *testing.T) {
|
|||
app := New()
|
||||
|
||||
app.Post("/test", func(c Ctx) error {
|
||||
fh, err := c.FormFile("file")
|
||||
fh, err := c.Req().FormFile("file")
|
||||
require.NoError(t, err)
|
||||
|
||||
tempFile, err := os.CreateTemp(os.TempDir(), "test-")
|
||||
|
@ -3075,7 +3075,7 @@ func Test_Ctx_ClearCookie(t *testing.T) {
|
|||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
|
||||
c.Request().Header.Set(HeaderCookie, "john=doe")
|
||||
c.ClearCookie("john")
|
||||
c.Res().ClearCookie("john")
|
||||
require.True(t, strings.HasPrefix(string(c.Response().Header.Peek(HeaderSetCookie)), "john=; expires="))
|
||||
|
||||
c.Request().Header.Set(HeaderCookie, "test1=dummy")
|
||||
|
@ -3104,7 +3104,7 @@ func Test_Ctx_Download(t *testing.T) {
|
|||
require.Equal(t, expect, c.Response().Body())
|
||||
require.Equal(t, `attachment; filename="Awesome+File%21"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
|
||||
|
||||
require.NoError(t, c.Download("ctx.go"))
|
||||
require.NoError(t, c.Res().Download("ctx.go"))
|
||||
require.Equal(t, `attachment; filename="ctx.go"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
|
||||
}
|
||||
|
||||
|
@ -3136,7 +3136,7 @@ func Test_Ctx_SendFile(t *testing.T) {
|
|||
|
||||
// test with custom error code
|
||||
c = app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
err = c.Status(StatusInternalServerError).SendFile("ctx.go")
|
||||
err = c.Res().Status(StatusInternalServerError).SendFile("ctx.go")
|
||||
// check expectation
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectFileContent, c.Response().Body())
|
||||
|
@ -3161,7 +3161,7 @@ func Test_Ctx_SendFile_ContentType(t *testing.T) {
|
|||
|
||||
// 1) simple case
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
err := c.SendFile("./.github/testdata/fs/img/fiber.png")
|
||||
err := c.Res().SendFile("./.github/testdata/fs/img/fiber.png")
|
||||
// check expectation
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, StatusOK, c.Response().StatusCode())
|
||||
|
@ -3746,7 +3746,7 @@ func Benchmark_Ctx_CBOR(b *testing.B) {
|
|||
func Benchmark_Ctx_JSON_Ctype(b *testing.B) {
|
||||
app := New()
|
||||
// TODO: Check extra allocs because of the interface stuff
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
type SomeStruct struct {
|
||||
Name string
|
||||
Age uint8
|
||||
|
@ -3782,7 +3782,7 @@ func Test_Ctx_JSONP(t *testing.T) {
|
|||
require.Equal(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Response().Body()))
|
||||
require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))
|
||||
|
||||
err = c.JSONP(Map{
|
||||
err = c.Res().JSONP(Map{
|
||||
"Name": "Grame",
|
||||
"Age": 20,
|
||||
}, "john")
|
||||
|
@ -3813,7 +3813,7 @@ func Test_Ctx_JSONP(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_JSONP -benchmem -count=4
|
||||
func Benchmark_Ctx_JSONP(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
type SomeStruct struct {
|
||||
Name string
|
||||
|
@ -3838,7 +3838,7 @@ func Benchmark_Ctx_JSONP(b *testing.B) {
|
|||
func Test_Ctx_XML(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
require.Error(t, c.JSON(complex(1, 1)))
|
||||
|
||||
|
@ -3897,7 +3897,7 @@ func Test_Ctx_XML(t *testing.T) {
|
|||
// go test -run=^$ -bench=Benchmark_Ctx_XML -benchmem -count=4
|
||||
func Benchmark_Ctx_XML(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
type SomeStruct struct {
|
||||
Name string `xml:"Name"`
|
||||
Age uint8 `xml:"Age"`
|
||||
|
@ -3936,7 +3936,7 @@ func Test_Ctx_Links(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_Links -benchmem -count=4
|
||||
func Benchmark_Ctx_Links(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -4006,7 +4006,7 @@ func Test_Ctx_Render(t *testing.T) {
|
|||
err = c.Render("./.github/testdata/template-non-exists.html", nil)
|
||||
require.Error(t, err)
|
||||
|
||||
err = c.Render("./.github/testdata/template-invalid.html", nil)
|
||||
err = c.Res().Render("./.github/testdata/template-invalid.html", nil)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -4363,7 +4363,7 @@ func Benchmark_Ctx_Render_Engine(b *testing.B) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_Get_Location_From_Route -benchmem -count=4
|
||||
func Benchmark_Ctx_Get_Location_From_Route(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
app.Get("/user/:name", func(c Ctx) error {
|
||||
return c.SendString(c.Params("name"))
|
||||
|
@ -4578,14 +4578,14 @@ func Test_Ctx_SendStreamWriter(t *testing.T) {
|
|||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
|
||||
err := c.SendStreamWriter(func(w *bufio.Writer) {
|
||||
w.WriteString("Don't crash please") //nolint:errcheck, revive // It is fine to ignore the error
|
||||
w.WriteString("Don't crash please") //nolint:errcheck // It is fine to ignore the error
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Don't crash please", string(c.Response().Body()))
|
||||
|
||||
err = c.SendStreamWriter(func(w *bufio.Writer) {
|
||||
for lineNum := 1; lineNum <= 5; lineNum++ {
|
||||
fmt.Fprintf(w, "Line %d\n", lineNum) //nolint:errcheck, revive // It is fine to ignore the error
|
||||
fmt.Fprintf(w, "Line %d\n", lineNum)
|
||||
if err := w.Flush(); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
return
|
||||
|
@ -4607,7 +4607,7 @@ func Test_Ctx_SendStreamWriter_Interrupted(t *testing.T) {
|
|||
app.Get("/", func(c Ctx) error {
|
||||
return c.SendStreamWriter(func(w *bufio.Writer) {
|
||||
for lineNum := 1; lineNum <= 5; lineNum++ {
|
||||
fmt.Fprintf(w, "Line %d\n", lineNum) //nolint:errcheck // It is fine to ignore the error
|
||||
fmt.Fprintf(w, "Line %d\n", lineNum)
|
||||
|
||||
if err := w.Flush(); err != nil {
|
||||
if lineNum < 3 {
|
||||
|
@ -4728,7 +4728,7 @@ func Benchmark_Ctx_Type(b *testing.B) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_Type_Charset -benchmem -count=4
|
||||
func Benchmark_Ctx_Type_Charset(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -4753,7 +4753,7 @@ func Test_Ctx_Vary(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_Vary -benchmem -count=4
|
||||
func Benchmark_Ctx_Vary(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -4806,7 +4806,7 @@ func Test_Ctx_Writef(t *testing.T) {
|
|||
// go test -v -run=^$ -bench=Benchmark_Ctx_Writef -benchmem -count=4
|
||||
func Benchmark_Ctx_Writef(b *testing.B) {
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
world := "World!"
|
||||
b.ReportAllocs()
|
||||
|
@ -4907,7 +4907,7 @@ func Test_Ctx_Queries(t *testing.T) {
|
|||
|
||||
c.Request().URI().SetQueryString("tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits")
|
||||
|
||||
queries = c.Queries()
|
||||
queries = c.Req().Queries()
|
||||
require.Equal(t, "apple,orange,banana", queries["tags"])
|
||||
require.Equal(t, "apple,orange,banana", queries["filters[tags]"])
|
||||
require.Equal(t, "fruits", queries["filters[category][name]"])
|
||||
|
@ -4951,11 +4951,11 @@ func Test_Ctx_BodyStreamWriter(t *testing.T) {
|
|||
ctx := &fasthttp.RequestCtx{}
|
||||
|
||||
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
||||
fmt.Fprintf(w, "body writer line 1\n") //nolint: errcheck // It is fine to ignore the error
|
||||
fmt.Fprintf(w, "body writer line 1\n")
|
||||
if err := w.Flush(); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
fmt.Fprintf(w, "body writer line 2\n") //nolint: errcheck // It is fine to ignore the error
|
||||
fmt.Fprintf(w, "body writer line 2\n")
|
||||
})
|
||||
|
||||
require.True(t, ctx.IsBodyStream())
|
||||
|
@ -5055,7 +5055,7 @@ func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) {
|
|||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90")
|
||||
|
||||
require.False(t, c.IsFromLocal())
|
||||
require.False(t, c.Req().IsFromLocal())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5088,8 +5088,8 @@ func Test_Ctx_IsFromLocal_RemoteAddr(t *testing.T) {
|
|||
fastCtx := &fasthttp.RequestCtx{}
|
||||
fastCtx.SetRemoteAddr(localIPv6)
|
||||
c := app.AcquireCtx(fastCtx)
|
||||
require.Equal(t, "::1", c.IP())
|
||||
require.True(t, c.IsFromLocal())
|
||||
require.Equal(t, "::1", c.Req().IP())
|
||||
require.True(t, c.Req().IsFromLocal())
|
||||
}
|
||||
// Test for the case fasthttp remoteAddr is set to "0:0:0:0:0:0:0:1".
|
||||
{
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"label": "\uD83D\uDD0C Addon",
|
||||
"position": 5,
|
||||
"collapsed": true,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Addon is an additional useful package that can be used in Fiber."
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
---
|
||||
id: retry
|
||||
---
|
||||
|
||||
# Retry Addon
|
||||
|
||||
Retry addon for [Fiber](https://github.com/gofiber/fiber) designed to apply retry mechanism for unsuccessful network
|
||||
operations. This addon uses an exponential backoff algorithm with jitter. It calls the function multiple times and tries
|
||||
to make it successful. If all calls are failed, then, it returns an error. It adds a jitter at each retry step because adding
|
||||
a jitter is a way to break synchronization across the client and avoid collision.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Retry Addon](#retry-addon)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Signatures](#signatures)
|
||||
- [Examples](#examples)
|
||||
- [Default Config](#default-config)
|
||||
- [Custom Config](#custom-config)
|
||||
- [Config](#config)
|
||||
- [Default Config Example](#default-config-example)
|
||||
|
||||
## Signatures
|
||||
|
||||
```go
|
||||
func NewExponentialBackoff(config ...retry.Config) *retry.ExponentialBackoff
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v3/addon/retry"
|
||||
"github.com/gofiber/fiber/v3/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
expBackoff := retry.NewExponentialBackoff(retry.Config{})
|
||||
|
||||
// Local variables that will be used inside of Retry
|
||||
var resp *client.Response
|
||||
var err error
|
||||
|
||||
// Retry a network request and return an error to signify to try again
|
||||
err = expBackoff.Retry(func() error {
|
||||
client := client.New()
|
||||
resp, err = client.Get("https://gofiber.io")
|
||||
if err != nil {
|
||||
return fmt.Errorf("GET gofiber.io failed: %w", err)
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("GET gofiber.io did not return OK 200")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// If all retries failed, panic
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("GET gofiber.io succeeded with status code %d\n", resp.StatusCode())
|
||||
}
|
||||
```
|
||||
|
||||
## Default Config
|
||||
|
||||
```go
|
||||
retry.NewExponentialBackoff()
|
||||
```
|
||||
|
||||
## Custom Config
|
||||
|
||||
```go
|
||||
retry.NewExponentialBackoff(retry.Config{
|
||||
InitialInterval: 2 * time.Second,
|
||||
MaxBackoffTime: 64 * time.Second,
|
||||
Multiplier: 2.0,
|
||||
MaxRetryCount: 15,
|
||||
})
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
```go
|
||||
// Config defines the config for addon.
|
||||
type Config struct {
|
||||
// InitialInterval defines the initial time interval for backoff algorithm.
|
||||
//
|
||||
// Optional. Default: 1 * time.Second
|
||||
InitialInterval time.Duration
|
||||
|
||||
// MaxBackoffTime defines maximum time duration for backoff algorithm. When
|
||||
// the algorithm is reached this time, rest of the retries will be maximum
|
||||
// 32 seconds.
|
||||
//
|
||||
// Optional. Default: 32 * time.Second
|
||||
MaxBackoffTime time.Duration
|
||||
|
||||
// Multiplier defines multiplier number of the backoff algorithm.
|
||||
//
|
||||
// Optional. Default: 2.0
|
||||
Multiplier float64
|
||||
|
||||
// MaxRetryCount defines maximum retry count for the backoff algorithm.
|
||||
//
|
||||
// Optional. Default: 10
|
||||
MaxRetryCount int
|
||||
}
|
||||
```
|
||||
|
||||
## Default Config Example
|
||||
|
||||
```go
|
||||
// DefaultConfig is the default config for retry.
|
||||
var DefaultConfig = Config{
|
||||
InitialInterval: 1 * time.Second,
|
||||
MaxBackoffTime: 32 * time.Second,
|
||||
Multiplier: 2.0,
|
||||
MaxRetryCount: 10,
|
||||
currentInterval: 1 * time.Second,
|
||||
}
|
||||
```
|
2024
docs/api/ctx.md
2024
docs/api/ctx.md
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"label": "\uD83C\uDF0E Client",
|
||||
"position": 5,
|
||||
"position": 6,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "HTTP client for Fiber."
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"label": "\uD83E\uDDE9 Extra",
|
||||
"position": 6,
|
||||
"position": 8,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Extra contents for Fiber."
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"label": "\uD83D\uDCD6 Guide",
|
||||
"position": 5,
|
||||
"position": 7,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Guides for Fiber."
|
||||
|
|
|
@ -8,8 +8,7 @@ sidebar_position: 5
|
|||
|
||||
Fiber provides the [Bind](../api/bind.md#validation) function to validate and bind [request data](../api/bind.md#binders) to a struct.
|
||||
|
||||
```go title="Example"
|
||||
|
||||
```go title="Basic Example"
|
||||
import "github.com/go-playground/validator/v10"
|
||||
|
||||
type structValidator struct {
|
||||
|
@ -42,3 +41,71 @@ app.Post("/", func(c fiber.Ctx) error {
|
|||
return c.JSON(user)
|
||||
})
|
||||
```
|
||||
|
||||
```go title="Advanced Validation Example"
|
||||
type User struct {
|
||||
Name string `json:"name" validate:"required,min=3,max=32"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Age int `json:"age" validate:"gte=0,lte=100"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
Website string `json:"website" validate:"url"`
|
||||
}
|
||||
|
||||
// Custom validation error messages
|
||||
type UserWithCustomMessages struct {
|
||||
Name string `json:"name" validate:"required,min=3,max=32" message:"Name is required and must be between 3 and 32 characters"`
|
||||
Email string `json:"email" validate:"required,email" message:"Valid email is required"`
|
||||
Age int `json:"age" validate:"gte=0,lte=100" message:"Age must be between 0 and 100"`
|
||||
}
|
||||
|
||||
app.Post("/user", func(c fiber.Ctx) error {
|
||||
user := new(User)
|
||||
|
||||
if err := c.Bind().Body(user); err != nil {
|
||||
// Handle validation errors
|
||||
if validationErrors, ok := err.(validator.ValidationErrors); ok {
|
||||
for _, e := range validationErrors {
|
||||
// e.Field() - field name
|
||||
// e.Tag() - validation tag
|
||||
// e.Value() - invalid value
|
||||
// e.Param() - validation parameter
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"field": e.Field(),
|
||||
"error": e.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(user)
|
||||
})
|
||||
```
|
||||
|
||||
```go title="Custom Validator Example"
|
||||
// Custom validator for password strength
|
||||
type PasswordValidator struct {
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
func (v *PasswordValidator) Validate(out any) error {
|
||||
if err := v.validate.Struct(out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Custom password validation logic
|
||||
if user, ok := out.(*User); ok {
|
||||
if len(user.Password) < 8 {
|
||||
return errors.New("password must be at least 8 characters")
|
||||
}
|
||||
// Add more password validation rules here
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Usage
|
||||
app := fiber.New(fiber.Config{
|
||||
StructValidator: &PasswordValidator{validate: validator.New()},
|
||||
})
|
||||
```
|
||||
|
|
|
@ -54,7 +54,7 @@ curl -I http://localhost:3000
|
|||
| ContentSecurityPolicy | `string` | ContentSecurityPolicy | "" |
|
||||
| CSPReportOnly | `bool` | CSPReportOnly | false |
|
||||
| HSTSPreloadEnabled | `bool` | HSTSPreloadEnabled | false |
|
||||
| ReferrerPolicy | `string` | ReferrerPolicy | "ReferrerPolicy" |
|
||||
| ReferrerPolicy | `string` | ReferrerPolicy | "no-referrer" |
|
||||
| PermissionPolicy | `string` | Permissions-Policy | "" |
|
||||
| CrossOriginEmbedderPolicy | `string` | Cross-Origin-Embedder-Policy | "require-corp" |
|
||||
| CrossOriginOpenerPolicy | `string` | Cross-Origin-Opener-Policy | "same-origin" |
|
||||
|
|
|
@ -55,13 +55,13 @@ app.Use(logger.New(logger.Config{
|
|||
}))
|
||||
|
||||
// Custom File Writer
|
||||
file, err := os.OpenFile("./123.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
accessLog, err := os.OpenFile("./access.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening file: %v", err)
|
||||
log.Fatalf("error opening access.log file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
defer accessLog.Close()
|
||||
app.Use(logger.New(logger.Config{
|
||||
Output: file,
|
||||
Stream: accessLog,
|
||||
}))
|
||||
|
||||
// Add Custom Tags
|
||||
|
@ -79,7 +79,7 @@ app.Use(logger.New(logger.Config{
|
|||
TimeZone: "Asia/Shanghai",
|
||||
Done: func(c fiber.Ctx, logString []byte) {
|
||||
if c.Response().StatusCode() != fiber.StatusOK {
|
||||
reporter.SendToSlack(logString)
|
||||
reporter.SendToSlack(logString)
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
@ -88,6 +88,23 @@ app.Use(logger.New(logger.Config{
|
|||
app.Use(logger.New(logger.Config{
|
||||
DisableColors: true,
|
||||
}))
|
||||
|
||||
// Use predefined formats
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: logger.FormatCommon,
|
||||
}))
|
||||
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: logger.FormatCombined,
|
||||
}))
|
||||
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: logger.FormatJSON,
|
||||
}))
|
||||
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: logger.FormatECS,
|
||||
}))
|
||||
```
|
||||
|
||||
### Use Logger Middleware with Other Loggers
|
||||
|
@ -115,7 +132,7 @@ func main() {
|
|||
|
||||
// Use the logger middleware with zerolog logger
|
||||
app.Use(logger.New(logger.Config{
|
||||
Output: logger.LoggerToWriter(zap, log.LevelDebug),
|
||||
Stream: logger.LoggerToWriter(zap, log.LevelDebug),
|
||||
}))
|
||||
|
||||
// Define a route
|
||||
|
@ -129,45 +146,57 @@ func main() {
|
|||
```
|
||||
|
||||
:::tip
|
||||
Writing to os.File is goroutine-safe, but if you are using a custom Output that is not goroutine-safe, make sure to implement locking to properly serialize writes.
|
||||
Writing to os.File is goroutine-safe, but if you are using a custom Stream that is not goroutine-safe, make sure to implement locking to properly serialize writes.
|
||||
:::
|
||||
|
||||
## Config
|
||||
|
||||
### Config
|
||||
|
||||
| Property | Type | Description | Default |
|
||||
|:-----------------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------|
|
||||
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
||||
| Done | `func(fiber.Ctx, []byte)` | Done is a function that is called after the log string for a request is written to Output, and pass the log string as parameter. | `nil` |
|
||||
| CustomTags | `map[string]LogFunc` | tagFunctions defines the custom tag action. | `map[string]LogFunc` |
|
||||
| Format | `string` | Format defines the logging tags. | `[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n` |
|
||||
| TimeFormat | `string` | TimeFormat defines the time format for log timestamps. | `15:04:05` |
|
||||
| TimeZone | `string` | TimeZone can be specified, such as "UTC" and "America/New_York" and "Asia/Chongqing", etc | `"Local"` |
|
||||
| TimeInterval | `time.Duration` | TimeInterval is the delay before the timestamp is updated. | `500 * time.Millisecond` |
|
||||
| Output | `io.Writer` | Output is a writer where logs are written. | `os.Stdout` |
|
||||
| LoggerFunc | `func(c fiber.Ctx, data *Data, cfg Config) error` | Custom logger function for integration with logging libraries (Zerolog, Zap, Logrus, etc). Defaults to Fiber's default logger if not defined. | `see default_logger.go defaultLoggerInstance` |
|
||||
| DisableColors | `bool` | DisableColors defines if the logs output should be colorized. | `false` |
|
||||
| enableColors | `bool` | Internal field for enabling colors in the log output. (This is not a user-configurable field) | - |
|
||||
| enableLatency | `bool` | Internal field for enabling latency measurement in logs. (This is not a user-configurable field) | - |
|
||||
| timeZoneLocation | `*time.Location` | Internal field for the time zone location. (This is not a user-configurable field) | - |
|
||||
| Property | Type | Description | Default |
|
||||
| :------------ | :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- |
|
||||
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
||||
| Skip | `func(fiber.Ctx) bool` | Skip is a function to determine if logging is skipped or written to Stream. | `nil` |
|
||||
| Done | `func(fiber.Ctx, []byte)` | Done is a function that is called after the log string for a request is written to Stream, and pass the log string as parameter. | `nil` |
|
||||
| CustomTags | `map[string]LogFunc` | tagFunctions defines the custom tag action. | `map[string]LogFunc` |
|
||||
| `Format` | `string` | Defines the logging tags. See more in [Predefined Formats](#predefined-formats), or create your own using [Tags](#constants). | `[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n` (same as `DefaultFormat`) |
|
||||
| TimeFormat | `string` | TimeFormat defines the time format for log timestamps. | `15:04:05` |
|
||||
| TimeZone | `string` | TimeZone can be specified, such as "UTC" and "America/New_York" and "Asia/Chongqing", etc | `"Local"` |
|
||||
| TimeInterval | `time.Duration` | TimeInterval is the delay before the timestamp is updated. | `500 * time.Millisecond` |
|
||||
| Stream | `io.Writer` | Stream is a writer where logs are written. | `os.Stdout` |
|
||||
| LoggerFunc | `func(c fiber.Ctx, data *Data, cfg Config) error` | Custom logger function for integration with logging libraries (Zerolog, Zap, Logrus, etc). Defaults to Fiber's default logger if not defined. | `see default_logger.go defaultLoggerInstance` |
|
||||
| DisableColors | `bool` | DisableColors defines if the logs output should be colorized. | `false` |
|
||||
|
||||
## Default Config
|
||||
|
||||
```go
|
||||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Done: nil,
|
||||
Format: "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n",
|
||||
TimeFormat: "15:04:05",
|
||||
TimeZone: "Local",
|
||||
TimeInterval: 500 * time.Millisecond,
|
||||
Output: os.Stdout,
|
||||
DisableColors: false,
|
||||
LoggerFunc: defaultLoggerInstance,
|
||||
Next: nil,
|
||||
Skip: nil,
|
||||
Done: nil,
|
||||
Format: DefaultFormat,
|
||||
TimeFormat: "15:04:05",
|
||||
TimeZone: "Local",
|
||||
TimeInterval: 500 * time.Millisecond,
|
||||
Stream: os.Stdout,
|
||||
BeforeHandlerFunc: beforeHandlerFunc,
|
||||
LoggerFunc: defaultLoggerInstance,
|
||||
enableColors: true,
|
||||
}
|
||||
```
|
||||
|
||||
## Predefined Formats
|
||||
|
||||
Logger provides predefined formats that you can use by name or directly by specifying the format string.
|
||||
|
||||
| **Format Constant** | **Format String** | **Description** |
|
||||
|---------------------|--------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
|
||||
| `DefaultFormat` | `"[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n"` | Fiber's default logger format. |
|
||||
| `CommonFormat` | `"${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent}\n"` | Common Log Format (CLF) used in web server logs. |
|
||||
| `CombinedFormat` | `"${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent} "${referer}" "${ua}"\n"` | CLF format plus the `referer` and `user agent` fields. |
|
||||
| `JSONFormat` | `"{time: ${time}, ip: ${ip}, method: ${method}, url: ${url}, status: ${status}, bytesSent: ${bytesSent}}\n"` | JSON format for structured logging. |
|
||||
| `ECSFormat` | `"{\"@timestamp\":\"${time}\",\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":\"${ip}\"},\"http\":{\"request\":{\"method\":\"${method}\",\"url\":\"${url}\",\"protocol\":\"${protocol}\"},\"response\":{\"status_code\":${status},\"body\":{\"bytes\":${bytesSent}}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":\"${method} ${url} responded with ${status}\"}\n"` | Elastic Common Schema (ECS) format for structured logging. |
|
||||
|
||||
## Constants
|
||||
|
||||
```go
|
||||
|
|
|
@ -33,6 +33,7 @@ Here's a quick overview of the changes in Fiber `v3`:
|
|||
- [Filesystem](#filesystem)
|
||||
- [Monitor](#monitor)
|
||||
- [Healthcheck](#healthcheck)
|
||||
- [🔌 Addons](#-addons)
|
||||
- [📋 Migration guide](#-migration-guide)
|
||||
|
||||
## Drop for old Go versions
|
||||
|
@ -915,6 +916,47 @@ func main() {
|
|||
|
||||
</details>
|
||||
|
||||
The `Skip` is a function to determine if logging is skipped or written to `Stream`.
|
||||
|
||||
<details>
|
||||
<summary>Example Usage</summary>
|
||||
|
||||
```go
|
||||
app.Use(logger.New(logger.Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
// Skip logging HTTP 200 requests
|
||||
return c.Response().StatusCode() == fiber.StatusOK
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
```go
|
||||
app.Use(logger.New(logger.Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
// Only log errors, similar to an error.log
|
||||
return c.Response().StatusCode() < 400
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Predefined Formats
|
||||
|
||||
Logger provides predefined formats that you can use by name or directly by specifying the format string.
|
||||
<details>
|
||||
|
||||
<summary>Example Usage</summary>
|
||||
|
||||
```go
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: logger.FormatCombined,
|
||||
}))
|
||||
```
|
||||
|
||||
See more in [Logger](./middleware/logger.md#predefined-formats)
|
||||
</details>
|
||||
|
||||
### Filesystem
|
||||
|
||||
We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
|
||||
|
@ -943,6 +985,59 @@ The Healthcheck middleware has been enhanced to support more than two routes, wi
|
|||
|
||||
Refer to the [healthcheck middleware migration guide](./middleware/healthcheck.md) or the [general migration guide](#-migration-guide) to review the changes.
|
||||
|
||||
## 🔌 Addons
|
||||
|
||||
In v3, Fiber introduced Addons. Addons are additional useful packages that can be used in Fiber.
|
||||
|
||||
### Retry
|
||||
|
||||
The Retry addon is a new addon that implements a retry mechanism for unsuccessful network operations. It uses an exponential backoff algorithm with jitter.
|
||||
It calls the function multiple times and tries to make it successful. If all calls are failed, then, it returns an error.
|
||||
It adds a jitter at each retry step because adding a jitter is a way to break synchronization across the client and avoid collision.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v3/addon/retry"
|
||||
"github.com/gofiber/fiber/v3/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
expBackoff := retry.NewExponentialBackoff(retry.Config{})
|
||||
|
||||
// Local variables that will be used inside of Retry
|
||||
var resp *client.Response
|
||||
var err error
|
||||
|
||||
// Retry a network request and return an error to signify to try again
|
||||
err = expBackoff.Retry(func() error {
|
||||
client := client.New()
|
||||
resp, err = client.Get("https://gofiber.io")
|
||||
if err != nil {
|
||||
return fmt.Errorf("GET gofiber.io failed: %w", err)
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("GET gofiber.io did not return OK 200")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// If all retries failed, panic
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("GET gofiber.io succeeded with status code %d\n", resp.StatusCode())
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 📋 Migration guide
|
||||
|
||||
- [🚀 App](#-app-1)
|
||||
|
|
2
error.go
2
error.go
|
@ -40,7 +40,7 @@ var (
|
|||
ErrNoHandlers = errors.New("format: at least one handler is required, but none were set")
|
||||
)
|
||||
|
||||
// gorilla/schema errors
|
||||
// gofiber/schema errors
|
||||
type (
|
||||
// ConversionError Conversion error exposes the internal schema.ConversionError for public use.
|
||||
ConversionError = schema.ConversionError
|
||||
|
|
8
go.mod
8
go.mod
|
@ -12,7 +12,7 @@ require (
|
|||
github.com/tinylib/msgp v1.2.5
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
github.com/valyala/fasthttp v1.59.0
|
||||
golang.org/x/crypto v0.35.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -23,8 +23,8 @@ require (
|
|||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
14
go.sum
14
go.sum
|
@ -32,15 +32,17 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
|||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
62
helpers.go
62
helpers.go
|
@ -14,6 +14,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -51,16 +52,16 @@ func getTLSConfig(ln net.Listener) *tls.Config {
|
|||
}
|
||||
|
||||
// Copy value from pointer
|
||||
if val := reflect.Indirect(pointer); val.Type() != nil {
|
||||
if val := reflect.Indirect(pointer); val.IsValid() {
|
||||
// Get private field from value
|
||||
if field := val.FieldByName("config"); field.Type() != nil {
|
||||
if field := val.FieldByName("config"); field.IsValid() {
|
||||
// Copy value from pointer field (unsafe)
|
||||
newval := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())) //nolint:gosec // Probably the only way to extract the *tls.Config from a net.Listener. TODO: Verify there really is no easier way without using unsafe.
|
||||
if newval.Type() == nil {
|
||||
if !newval.IsValid() {
|
||||
return nil
|
||||
}
|
||||
// Get element from pointer
|
||||
if elem := newval.Elem(); elem.Type() != nil {
|
||||
if elem := newval.Elem(); elem.IsValid() {
|
||||
// Cast value to *tls.Config
|
||||
c, ok := elem.Interface().(*tls.Config)
|
||||
if !ok {
|
||||
|
@ -107,15 +108,15 @@ func (app *App[TCtx]) methodExist(c *DefaultCtx) bool {
|
|||
methods := app.config.RequestMethods
|
||||
for i := 0; i < len(methods); i++ {
|
||||
// Skip original method
|
||||
if c.getMethodINT() == i {
|
||||
if c.getMethodInt() == i {
|
||||
continue
|
||||
}
|
||||
// Reset stack index
|
||||
c.setIndexRoute(-1)
|
||||
|
||||
tree, ok := app.treeStack[i][c.getTreePath()]
|
||||
tree, ok := app.treeStack[i][c.treePathHash]
|
||||
if !ok {
|
||||
tree = app.treeStack[i][""]
|
||||
tree = app.treeStack[i][0]
|
||||
}
|
||||
// Get stack length
|
||||
lenr := len(tree) - 1
|
||||
|
@ -152,15 +153,15 @@ func (app *App[TCtx]) methodExistCustom(c CustomCtx[TCtx]) bool {
|
|||
methods := app.config.RequestMethods
|
||||
for i := 0; i < len(methods); i++ {
|
||||
// Skip original method
|
||||
if c.getMethodINT() == i {
|
||||
if c.getMethodInt() == i {
|
||||
continue
|
||||
}
|
||||
// Reset stack index
|
||||
c.setIndexRoute(-1)
|
||||
|
||||
tree, ok := app.treeStack[i][c.getTreePath()]
|
||||
tree, ok := app.treeStack[i][c.getTreePathHash()]
|
||||
if !ok {
|
||||
tree = app.treeStack[i][""]
|
||||
tree = app.treeStack[i][0]
|
||||
}
|
||||
// Get stack length
|
||||
lenr := len(tree) - 1
|
||||
|
@ -484,7 +485,7 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
|
|||
|
||||
if len(acceptedTypes) > 1 {
|
||||
// Sort accepted types by quality and specificity, preserving order of equal elements
|
||||
sortAcceptedTypes(&acceptedTypes)
|
||||
sortAcceptedTypes(acceptedTypes)
|
||||
}
|
||||
|
||||
// Find the first offer that matches the accepted types
|
||||
|
@ -512,19 +513,14 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
|
|||
// A type with parameters has higher priority than an equivalent one without parameters.
|
||||
// e.g., text/html;a=1;b=2 comes before text/html;a=1
|
||||
// See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields
|
||||
func sortAcceptedTypes(acceptedTypes *[]acceptedType) {
|
||||
if acceptedTypes == nil || len(*acceptedTypes) < 2 {
|
||||
return
|
||||
}
|
||||
at := *acceptedTypes
|
||||
|
||||
func sortAcceptedTypes(at []acceptedType) {
|
||||
for i := 1; i < len(at); i++ {
|
||||
lo, hi := 0, i-1
|
||||
for lo <= hi {
|
||||
mid := (lo + hi) / 2
|
||||
if at[i].quality < at[mid].quality ||
|
||||
(at[i].quality == at[mid].quality && at[i].specificity < at[mid].specificity) ||
|
||||
(at[i].quality == at[mid].quality && at[i].specificity < at[mid].specificity && len(at[i].params) < len(at[mid].params)) ||
|
||||
(at[i].quality == at[mid].quality && at[i].specificity == at[mid].specificity && len(at[i].params) < len(at[mid].params)) ||
|
||||
(at[i].quality == at[mid].quality && at[i].specificity == at[mid].specificity && len(at[i].params) == len(at[mid].params) && at[i].order > at[mid].order) {
|
||||
lo = mid + 1
|
||||
} else {
|
||||
|
@ -658,39 +654,35 @@ func getBytesImmutable(s string) []byte {
|
|||
func (app *App[TCtx]) methodInt(s string) int {
|
||||
// For better performance
|
||||
if len(app.configured.RequestMethods) == 0 {
|
||||
// TODO: Use iota instead
|
||||
switch s {
|
||||
case MethodGet:
|
||||
return 0
|
||||
return methodGet
|
||||
case MethodHead:
|
||||
return 1
|
||||
return methodHead
|
||||
case MethodPost:
|
||||
return 2
|
||||
return methodPost
|
||||
case MethodPut:
|
||||
return 3
|
||||
return methodPut
|
||||
case MethodDelete:
|
||||
return 4
|
||||
return methodDelete
|
||||
case MethodConnect:
|
||||
return 5
|
||||
return methodConnect
|
||||
case MethodOptions:
|
||||
return 6
|
||||
return methodOptions
|
||||
case MethodTrace:
|
||||
return 7
|
||||
return methodTrace
|
||||
case MethodPatch:
|
||||
return 8
|
||||
return methodPatch
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// For method customization
|
||||
for i, v := range app.config.RequestMethods {
|
||||
if s == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return slices.Index(app.config.RequestMethods, s)
|
||||
}
|
||||
|
||||
return -1
|
||||
func (app *App) method(methodInt int) string {
|
||||
return app.config.RequestMethods[methodInt]
|
||||
}
|
||||
|
||||
// IsMethodSafe reports whether the HTTP method is considered safe.
|
||||
|
|
|
@ -354,7 +354,6 @@ func Test_Utils_SortAcceptedTypes(t *testing.T) {
|
|||
{spec: "text/html", quality: 1, specificity: 3, order: 0},
|
||||
{spec: "text/*", quality: 0.5, specificity: 2, order: 1},
|
||||
{spec: "*/*", quality: 0.1, specificity: 1, order: 2},
|
||||
{spec: "application/json", quality: 0.999, specificity: 3, order: 3},
|
||||
{spec: "application/xml", quality: 1, specificity: 3, order: 4},
|
||||
{spec: "application/pdf", quality: 1, specificity: 3, order: 5},
|
||||
{spec: "image/png", quality: 1, specificity: 3, order: 6},
|
||||
|
@ -363,8 +362,9 @@ func Test_Utils_SortAcceptedTypes(t *testing.T) {
|
|||
{spec: "image/gif", quality: 1, specificity: 3, order: 9},
|
||||
{spec: "text/plain", quality: 1, specificity: 3, order: 10},
|
||||
{spec: "application/json", quality: 0.999, specificity: 3, params: headerParams{"a": []byte("1")}, order: 11},
|
||||
{spec: "application/json", quality: 0.999, specificity: 3, order: 3},
|
||||
}
|
||||
sortAcceptedTypes(&acceptedTypes)
|
||||
sortAcceptedTypes(acceptedTypes)
|
||||
require.Equal(t, []acceptedType{
|
||||
{spec: "text/html", quality: 1, specificity: 3, order: 0},
|
||||
{spec: "application/xml", quality: 1, specificity: 3, order: 4},
|
||||
|
@ -390,7 +390,7 @@ func Benchmark_Utils_SortAcceptedTypes_Sorted(b *testing.B) {
|
|||
acceptedTypes[0] = acceptedType{spec: "text/html", quality: 1, specificity: 1, order: 0}
|
||||
acceptedTypes[1] = acceptedType{spec: "text/*", quality: 0.5, specificity: 1, order: 1}
|
||||
acceptedTypes[2] = acceptedType{spec: "*/*", quality: 0.1, specificity: 1, order: 2}
|
||||
sortAcceptedTypes(&acceptedTypes)
|
||||
sortAcceptedTypes(acceptedTypes)
|
||||
}
|
||||
require.Equal(b, "text/html", acceptedTypes[0].spec)
|
||||
require.Equal(b, "text/*", acceptedTypes[1].spec)
|
||||
|
@ -414,7 +414,7 @@ func Benchmark_Utils_SortAcceptedTypes_Unsorted(b *testing.B) {
|
|||
acceptedTypes[8] = acceptedType{spec: "image/*", quality: 1, specificity: 2, order: 8}
|
||||
acceptedTypes[9] = acceptedType{spec: "image/gif", quality: 1, specificity: 3, order: 9}
|
||||
acceptedTypes[10] = acceptedType{spec: "text/plain", quality: 1, specificity: 3, order: 10}
|
||||
sortAcceptedTypes(&acceptedTypes)
|
||||
sortAcceptedTypes(acceptedTypes)
|
||||
}
|
||||
require.Equal(b, []acceptedType{
|
||||
{spec: "text/html", quality: 1, specificity: 3, order: 0},
|
||||
|
@ -566,7 +566,7 @@ func Test_Utils_TestConn_Closed_Write(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Close early, write should fail
|
||||
conn.Close() //nolint:errcheck, revive // It is fine to ignore the error here
|
||||
conn.Close() //nolint:errcheck // It is fine to ignore the error here
|
||||
_, err = conn.Write([]byte("Response 2\n"))
|
||||
require.ErrorIs(t, err, errTestConnClosed)
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ func Benchmark_Memory_Set(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = testStore.Set("john", []byte("doe"), 0) //nolint: errcheck // error not needed for benchmark
|
||||
_ = testStore.Set("john", []byte("doe"), 0) //nolint:errcheck // error not needed for benchmark
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +220,7 @@ func Benchmark_Memory_Set_Parallel(b *testing.B) {
|
|||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = testStore.Set("john", []byte("doe"), 0) //nolint: errcheck // error not needed for benchmark
|
||||
_ = testStore.Set("john", []byte("doe"), 0) //nolint:errcheck // error not needed for benchmark
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ func Benchmark_Memory_Get(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = testStore.Get("john") //nolint: errcheck // error not needed for benchmark
|
||||
_, _ = testStore.Get("john") //nolint:errcheck // error not needed for benchmark
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,7 +273,7 @@ func Benchmark_Memory_Get_Parallel(b *testing.B) {
|
|||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, _ = testStore.Get("john") //nolint: errcheck // error not needed for benchmark
|
||||
_, _ = testStore.Get("john") //nolint:errcheck // error not needed for benchmark
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -315,8 +315,8 @@ func Benchmark_Memory_SetAndDelete(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = testStore.Set("john", []byte("doe"), 0) //nolint: errcheck // error not needed for benchmark
|
||||
_ = testStore.Delete("john") //nolint: errcheck // error not needed for benchmark
|
||||
_ = testStore.Set("john", []byte("doe"), 0) //nolint:errcheck // error not needed for benchmark
|
||||
_ = testStore.Delete("john") //nolint:errcheck // error not needed for benchmark
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,8 +327,8 @@ func Benchmark_Memory_SetAndDelete_Parallel(b *testing.B) {
|
|||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = testStore.Set("john", []byte("doe"), 0) //nolint: errcheck // error not needed for benchmark
|
||||
_ = testStore.Delete("john") //nolint: errcheck // error not needed for benchmark
|
||||
_ = testStore.Set("john", []byte("doe"), 0) //nolint:errcheck // error not needed for benchmark
|
||||
_ = testStore.Delete("john") //nolint:errcheck // error not needed for benchmark
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
34
listen.go
34
listen.go
|
@ -328,7 +328,7 @@ func (*App[TCtx]) prepareListenData(addr string, isTLS bool, cfg ListenConfig[TC
|
|||
}
|
||||
|
||||
// startupMessage prepares the startup message with the handler number, port, address and other information
|
||||
func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg ListenConfig[TCtx]) { //nolint: revive // Accepting a bool param named isTLS if fine here
|
||||
func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg ListenConfig[TCtx]) { //nolint:revive // Accepting a bool param named isTLS if fine here
|
||||
// ignore child processes
|
||||
if IsChild() {
|
||||
return
|
||||
|
@ -366,38 +366,35 @@ func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg L
|
|||
out = colorable.NewNonColorable(os.Stdout)
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "%s\n", fmt.Sprintf(figletFiberText, colors.Red+"v"+Version+colors.Reset)) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, strings.Repeat("-", 50)+"\n") //nolint:errcheck,revive,govet // ignore error
|
||||
fmt.Fprintf(out, "%s\n", fmt.Sprintf(figletFiberText, colors.Red+"v"+Version+colors.Reset))
|
||||
fmt.Fprintf(out, strings.Repeat("-", 50)+"\n")
|
||||
|
||||
if host == "0.0.0.0" {
|
||||
//nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out,
|
||||
"%sINFO%s Server started on: \t%s%s://127.0.0.1:%s%s (bound on host 0.0.0.0 and port %s)\n",
|
||||
colors.Green, colors.Reset, colors.Blue, scheme, port, colors.Reset, port)
|
||||
} else {
|
||||
//nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out,
|
||||
"%sINFO%s Server started on: \t%s%s%s\n",
|
||||
colors.Green, colors.Reset, colors.Blue, fmt.Sprintf("%s://%s:%s", scheme, host, port), colors.Reset)
|
||||
}
|
||||
|
||||
if app.config.AppName != "" {
|
||||
fmt.Fprintf(out, "%sINFO%s Application name: \t\t%s%s%s\n", colors.Green, colors.Reset, colors.Blue, app.config.AppName, colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "%sINFO%s Application name: \t\t%s%s%s\n", colors.Green, colors.Reset, colors.Blue, app.config.AppName, colors.Reset)
|
||||
}
|
||||
|
||||
//nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out,
|
||||
"%sINFO%s Total handlers count: \t%s%s%s\n",
|
||||
colors.Green, colors.Reset, colors.Blue, strconv.Itoa(int(app.handlersCount)), colors.Reset)
|
||||
|
||||
if isPrefork == "Enabled" {
|
||||
fmt.Fprintf(out, "%sINFO%s Prefork: \t\t\t%s%s%s\n", colors.Green, colors.Reset, colors.Blue, isPrefork, colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "%sINFO%s Prefork: \t\t\t%s%s%s\n", colors.Green, colors.Reset, colors.Blue, isPrefork, colors.Reset)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%sINFO%s Prefork: \t\t\t%s%s%s\n", colors.Green, colors.Reset, colors.Red, isPrefork, colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "%sINFO%s Prefork: \t\t\t%s%s%s\n", colors.Green, colors.Reset, colors.Red, isPrefork, colors.Reset)
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "%sINFO%s PID: \t\t\t%s%v%s\n", colors.Green, colors.Reset, colors.Blue, os.Getpid(), colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "%sINFO%s Total process count: \t%s%s%s\n", colors.Green, colors.Reset, colors.Blue, procs, colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "%sINFO%s PID: \t\t\t%s%v%s\n", colors.Green, colors.Reset, colors.Blue, os.Getpid(), colors.Reset)
|
||||
fmt.Fprintf(out, "%sINFO%s Total process count: \t%s%s%s\n", colors.Green, colors.Reset, colors.Blue, procs, colors.Reset)
|
||||
|
||||
if cfg.EnablePrefork {
|
||||
// Turn the `pids` variable (in the form ",a,b,c,d,e,f,etc") into a slice of PIDs
|
||||
|
@ -408,7 +405,7 @@ func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg L
|
|||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "%sINFO%s Child PIDs: \t\t%s", colors.Green, colors.Reset, colors.Blue) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "%sINFO%s Child PIDs: \t\t%s", colors.Green, colors.Reset, colors.Blue)
|
||||
totalPids := len(pidSlice)
|
||||
rowTotalPidCount := 10
|
||||
|
||||
|
@ -421,17 +418,17 @@ func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg L
|
|||
}
|
||||
|
||||
for n, pid := range pidSlice[start:end] {
|
||||
fmt.Fprintf(out, "%s", pid) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "%s", pid)
|
||||
if n+1 != len(pidSlice[start:end]) {
|
||||
fmt.Fprintf(out, ", ") //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, ", ")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(out, "\n%s", colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "\n%s", colors.Reset)
|
||||
}
|
||||
}
|
||||
|
||||
// add new Line as spacer
|
||||
fmt.Fprintf(out, "\n%s", colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(out, "\n%s", colors.Reset)
|
||||
}
|
||||
|
||||
// printRoutesMessage print all routes with method, path, name and handlers
|
||||
|
@ -473,11 +470,10 @@ func (app *App[TCtx]) printRoutesMessage() {
|
|||
return routes[i].path < routes[j].path
|
||||
})
|
||||
|
||||
fmt.Fprintf(w, "%smethod\t%s| %spath\t%s| %sname\t%s| %shandlers\t%s\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow, colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(w, "%s------\t%s| %s----\t%s| %s----\t%s| %s--------\t%s\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow, colors.Reset) //nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(w, "%smethod\t%s| %spath\t%s| %sname\t%s| %shandlers\t%s\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow, colors.Reset)
|
||||
fmt.Fprintf(w, "%s------\t%s| %s----\t%s| %s----\t%s| %s--------\t%s\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow, colors.Reset)
|
||||
|
||||
for _, route := range routes {
|
||||
//nolint:errcheck,revive // ignore error
|
||||
fmt.Fprintf(w, "%s%s\t%s| %s%s\t%s| %s%s\t%s| %s%s%s\n", colors.Blue, route.method, colors.White, colors.Green, route.path, colors.White, colors.Cyan, route.name, colors.White, colors.Yellow, route.handlers, colors.Reset)
|
||||
}
|
||||
|
||||
|
|
|
@ -53,9 +53,9 @@ func (l *defaultLogger) privateLogf(lv Level, format string, fmtArgs []any) {
|
|||
buf.WriteString(level)
|
||||
|
||||
if len(fmtArgs) > 0 {
|
||||
_, _ = fmt.Fprintf(buf, format, fmtArgs...) //nolint: errcheck // It is fine to ignore the error
|
||||
_, _ = fmt.Fprintf(buf, format, fmtArgs...)
|
||||
} else {
|
||||
_, _ = fmt.Fprint(buf, fmtArgs...) //nolint: errcheck // It is fine to ignore the error
|
||||
_, _ = fmt.Fprint(buf, fmtArgs...)
|
||||
}
|
||||
|
||||
_ = l.stdlog.Output(l.depth, buf.String()) //nolint:errcheck // It is fine to ignore the error
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//nolint:contextcheck, revive // Much easier to just ignore memory leaks in tests
|
||||
//nolint:contextcheck,revive // Much easier to just ignore memory leaks in tests
|
||||
package adaptor
|
||||
|
||||
import (
|
||||
|
@ -68,7 +68,7 @@ func Test_HTTPHandler(t *testing.T) {
|
|||
w.Header().Set("Header1", "value1")
|
||||
w.Header().Set("Header2", "value2")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "request body is %q", body) //nolint:errcheck // not needed
|
||||
fmt.Fprintf(w, "request body is %q", body)
|
||||
}
|
||||
fiberH := HTTPHandlerFunc(http.HandlerFunc(nethttpH))
|
||||
fiberH = setFiberContextValueMiddleware(fiberH, expectedContextKey, expectedContextValue)
|
||||
|
|
|
@ -1331,56 +1331,65 @@ func Test_CSRF_Cookie_Injection_Exploit(t *testing.T) {
|
|||
}
|
||||
|
||||
// TODO: use this test case and make the unsafe header value bug from https://github.com/gofiber/fiber/issues/2045 reproducible and permanently fixed/tested by this testcase
|
||||
// func Test_CSRF_UnsafeHeaderValue(t *testing.T) {
|
||||
// t.Parallel()
|
||||
// app := fiber.New()
|
||||
func Test_CSRF_UnsafeHeaderValue(t *testing.T) {
|
||||
t.SkipNow()
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
// app.Use(New())
|
||||
// app.Get("/", func(c fiber.Ctx) error {
|
||||
// return c.SendStatus(fiber.StatusOK)
|
||||
// })
|
||||
// app.Get("/test", func(c fiber.Ctx) error {
|
||||
// return c.SendStatus(fiber.StatusOK)
|
||||
// })
|
||||
// app.Post("/", func(c fiber.Ctx) error {
|
||||
// return c.SendStatus(fiber.StatusOK)
|
||||
// })
|
||||
app.Use(New())
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
app.Get("/test", func(c fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
app.Post("/", func(c fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
// resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
// var token string
|
||||
// for _, c := range resp.Cookies() {
|
||||
// if c.Name != ConfigDefault.CookieName {
|
||||
// continue
|
||||
// }
|
||||
// token = c.Value
|
||||
// break
|
||||
// }
|
||||
var token string
|
||||
for _, c := range resp.Cookies() {
|
||||
if c.Name != ConfigDefault.CookieName {
|
||||
continue
|
||||
}
|
||||
token = c.Value
|
||||
break
|
||||
}
|
||||
|
||||
// fmt.Println("token", token)
|
||||
t.Log("token", token)
|
||||
|
||||
// getReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
// getReq.Header.Set(HeaderName, token)
|
||||
// resp, err = app.Test(getReq)
|
||||
getReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
getReq.Header.Set(HeaderName, token)
|
||||
resp, err = app.Test(getReq)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
// getReq = httptest.NewRequest(fiber.MethodGet, "/test", nil)
|
||||
// getReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||
// getReq.Header.Set(fiber.HeaderCacheControl, "no")
|
||||
// getReq.Header.Set(HeaderName, token)
|
||||
getReq = httptest.NewRequest(fiber.MethodGet, "/test", nil)
|
||||
getReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||
getReq.Header.Set(fiber.HeaderCacheControl, "no")
|
||||
getReq.Header.Set(HeaderName, token)
|
||||
|
||||
// resp, err = app.Test(getReq)
|
||||
resp, err = app.Test(getReq)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
// getReq.Header.Set(fiber.HeaderAccept, "*/*")
|
||||
// getReq.Header.Del(HeaderName)
|
||||
// resp, err = app.Test(getReq)
|
||||
getReq.Header.Set(fiber.HeaderAccept, "*/*")
|
||||
getReq.Header.Del(HeaderName)
|
||||
resp, err = app.Test(getReq)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
// postReq := httptest.NewRequest(fiber.MethodPost, "/", nil)
|
||||
// postReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||
// postReq.Header.Set(HeaderName, token)
|
||||
// resp, err = app.Test(postReq)
|
||||
// }
|
||||
postReq := httptest.NewRequest(fiber.MethodPost, "/", nil)
|
||||
postReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||
postReq.Header.Set(HeaderName, token)
|
||||
resp, err = app.Test(postReq)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
// go test -v -run=^$ -bench=Benchmark_Middleware_CSRF_Check -benchmem -count=4
|
||||
func Benchmark_Middleware_CSRF_Check(b *testing.B) {
|
||||
|
|
|
@ -28,7 +28,7 @@ type Config struct {
|
|||
ContentSecurityPolicy string
|
||||
|
||||
// ReferrerPolicy
|
||||
// Optional. Default value "ReferrerPolicy".
|
||||
// Optional. Default value "no-referrer".
|
||||
ReferrerPolicy string
|
||||
|
||||
// Permissions-Policy
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package keyauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -59,7 +60,10 @@ func New(config ...Config) fiber.Handler {
|
|||
valid, err := cfg.Validator(c, key)
|
||||
|
||||
if err == nil && valid {
|
||||
// Store in both Locals and Context
|
||||
c.Locals(tokenKey, key)
|
||||
ctx := context.WithValue(c.Context(), tokenKey, key)
|
||||
c.SetContext(ctx)
|
||||
return cfg.SuccessHandler(c)
|
||||
}
|
||||
return cfg.ErrorHandler(c, err)
|
||||
|
@ -68,12 +72,20 @@ func New(config ...Config) fiber.Handler {
|
|||
|
||||
// TokenFromContext returns the bearer token from the request context.
|
||||
// returns an empty string if the token does not exist
|
||||
func TokenFromContext(c fiber.Ctx) string {
|
||||
token, ok := c.Locals(tokenKey).(string)
|
||||
if !ok {
|
||||
return ""
|
||||
func TokenFromContext(c any) string {
|
||||
switch ctx := c.(type) {
|
||||
case context.Context:
|
||||
if token, ok := ctx.Value(tokenKey).(string); ok {
|
||||
return token
|
||||
}
|
||||
case fiber.Ctx:
|
||||
if token, ok := ctx.Locals(tokenKey).(string); ok {
|
||||
return token
|
||||
}
|
||||
default:
|
||||
panic("unsupported context type, expected fiber.Ctx or context.Context")
|
||||
}
|
||||
return token
|
||||
return ""
|
||||
}
|
||||
|
||||
// MultipleKeySourceLookup creates a CustomKeyLookup function that checks multiple sources until one is found
|
||||
|
|
|
@ -503,33 +503,67 @@ func Test_TokenFromContext_None(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_TokenFromContext(t *testing.T) {
|
||||
app := fiber.New()
|
||||
// Wire up keyauth middleware to set TokenFromContext now
|
||||
app.Use(New(Config{
|
||||
KeyLookup: "header:Authorization",
|
||||
AuthScheme: "Basic",
|
||||
Validator: func(_ fiber.Ctx, key string) (bool, error) {
|
||||
if key == CorrectKey {
|
||||
return true, nil
|
||||
}
|
||||
return false, ErrMissingOrMalformedAPIKey
|
||||
},
|
||||
}))
|
||||
// Define a test handler that checks TokenFromContext
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString(TokenFromContext(c))
|
||||
// Test that TokenFromContext returns the correct token
|
||||
t.Run("fiber.Ctx", func(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
KeyLookup: "header:Authorization",
|
||||
AuthScheme: "Basic",
|
||||
Validator: func(_ fiber.Ctx, key string) (bool, error) {
|
||||
if key == CorrectKey {
|
||||
return true, nil
|
||||
}
|
||||
return false, ErrMissingOrMalformedAPIKey
|
||||
},
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString(TokenFromContext(c))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Add("Authorization", "Basic "+CorrectKey)
|
||||
res, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CorrectKey, string(body))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Add("Authorization", "Basic "+CorrectKey)
|
||||
// Send
|
||||
res, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
t.Run("context.Context", func(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
KeyLookup: "header:Authorization",
|
||||
AuthScheme: "Basic",
|
||||
Validator: func(_ fiber.Ctx, key string) (bool, error) {
|
||||
if key == CorrectKey {
|
||||
return true, nil
|
||||
}
|
||||
return false, ErrMissingOrMalformedAPIKey
|
||||
},
|
||||
}))
|
||||
// Verify that TokenFromContext works with context.Context
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
token := TokenFromContext(ctx)
|
||||
return c.SendString(token)
|
||||
})
|
||||
|
||||
// Read the response body into a string
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CorrectKey, string(body))
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Add("Authorization", "Basic "+CorrectKey)
|
||||
res, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CorrectKey, string(body))
|
||||
})
|
||||
|
||||
t.Run("invalid context type", func(t *testing.T) {
|
||||
require.Panics(t, func() {
|
||||
_ = TokenFromContext("invalid")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AuthSchemeToken(t *testing.T) {
|
||||
|
|
|
@ -10,16 +10,21 @@ import (
|
|||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Output is a writer where logs are written
|
||||
// Stream is a writer where logs are written
|
||||
//
|
||||
// Default: os.Stdout
|
||||
Output io.Writer
|
||||
Stream io.Writer
|
||||
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// Skip is a function to determine if logging is skipped or written to Stream.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Skip func(c fiber.Ctx) bool
|
||||
|
||||
// Done is a function that is called after the log string for a request is written to Output,
|
||||
// and pass the log string as parameter.
|
||||
//
|
||||
|
@ -45,9 +50,23 @@ type Config struct {
|
|||
|
||||
timeZoneLocation *time.Location
|
||||
|
||||
// Format defines the logging tags
|
||||
// Format defines the logging format for the middleware.
|
||||
//
|
||||
// Optional. Default: [${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}
|
||||
// You can customize the log output by defining a format string with placeholders
|
||||
// such as: ${time}, ${ip}, ${status}, ${method}, ${path}, ${latency}, ${error}, etc.
|
||||
// The full list of available placeholders can be found in 'tags.go' or at
|
||||
// 'https://docs.gofiber.io/api/middleware/logger/#constants'.
|
||||
//
|
||||
// Fiber provides predefined logging formats that can be used directly:
|
||||
//
|
||||
// - DefaultFormat → Uses the default log format: "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}"
|
||||
// - CommonFormat → Uses the Apache Common Log Format (CLF): "${ip} - - [${time}] \"${method} ${url} ${protocol}\" ${status} ${bytesSent}\n"
|
||||
// - CombinedFormat → Uses the Apache Combined Log Format: "${ip} - - [${time}] \"${method} ${url} ${protocol}\" ${status} ${bytesSent} \"${referer}\" \"${ua}\"\n"
|
||||
// - JSONFormat → Uses the JSON log format: "{\"time\":\"${time}\",\"ip\":\"${ip}\",\"method\":\"${method}\",\"url\":\"${url}\",\"status\":${status},\"bytesSent\":${bytesSent}}\n"
|
||||
// - ECSFormat → Uses the Elastic Common Schema (ECS) log format: {\"@timestamp\":\"${time}\",\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":\"${ip}\"},\"http\":{\"request\":{\"method\":\"${method}\",\"url\":\"${url}\",\"protocol\":\"${protocol}\"},\"response\":{\"status_code\":${status},\"body\":{\"bytes\":${bytesSent}}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":\"${method} ${url} responded with ${status}\"}"
|
||||
// If both `Format` and `CustomFormat` are provided, the `CustomFormat` will be used, and the `Format` field will be ignored.
|
||||
// If no format is specified, the default format is used:
|
||||
// "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}"
|
||||
Format string
|
||||
|
||||
// TimeFormat https://programming.guide/go/format-parse-string-time-date-example.html
|
||||
|
@ -98,20 +117,18 @@ type LogFunc func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (in
|
|||
// ConfigDefault is the default config
|
||||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Skip: nil,
|
||||
Done: nil,
|
||||
Format: defaultFormat,
|
||||
Format: DefaultFormat,
|
||||
TimeFormat: "15:04:05",
|
||||
TimeZone: "Local",
|
||||
TimeInterval: 500 * time.Millisecond,
|
||||
Output: os.Stdout,
|
||||
Stream: os.Stdout,
|
||||
BeforeHandlerFunc: beforeHandlerFunc,
|
||||
LoggerFunc: defaultLoggerInstance,
|
||||
enableColors: true,
|
||||
}
|
||||
|
||||
// default logging format for Fiber's default logger
|
||||
var defaultFormat = "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n"
|
||||
|
||||
// Helper function to set default values
|
||||
func configDefault(config ...Config) Config {
|
||||
// Return default config if nothing provided
|
||||
|
@ -126,6 +143,9 @@ func configDefault(config ...Config) Config {
|
|||
if cfg.Next == nil {
|
||||
cfg.Next = ConfigDefault.Next
|
||||
}
|
||||
if cfg.Skip == nil {
|
||||
cfg.Skip = ConfigDefault.Skip
|
||||
}
|
||||
if cfg.Done == nil {
|
||||
cfg.Done = ConfigDefault.Done
|
||||
}
|
||||
|
@ -141,8 +161,8 @@ func configDefault(config ...Config) Config {
|
|||
if int(cfg.TimeInterval) <= 0 {
|
||||
cfg.TimeInterval = ConfigDefault.TimeInterval
|
||||
}
|
||||
if cfg.Output == nil {
|
||||
cfg.Output = ConfigDefault.Output
|
||||
if cfg.Stream == nil {
|
||||
cfg.Stream = ConfigDefault.Stream
|
||||
}
|
||||
|
||||
if cfg.BeforeHandlerFunc == nil {
|
||||
|
@ -154,7 +174,7 @@ func configDefault(config ...Config) Config {
|
|||
}
|
||||
|
||||
// Enable colors if no custom format or output is given
|
||||
if !cfg.DisableColors && cfg.Output == ConfigDefault.Output {
|
||||
if !cfg.DisableColors && cfg.Stream == ConfigDefault.Stream {
|
||||
cfg.enableColors = true
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,12 @@ import (
|
|||
|
||||
// default logger for fiber
|
||||
func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
||||
// Check if Skip is defined and call it.
|
||||
// Now, if Skip(c) == true, we SKIP logging:
|
||||
if cfg.Skip != nil && cfg.Skip(c) {
|
||||
return nil // Skip logging if Skip returns true
|
||||
}
|
||||
|
||||
// Alias colors
|
||||
colors := c.App().Config().ColorScheme
|
||||
|
||||
|
@ -22,7 +28,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
|||
buf := bytebufferpool.Get()
|
||||
|
||||
// Default output when no custom Format or io.Writer is given
|
||||
if cfg.Format == defaultFormat {
|
||||
if cfg.Format == DefaultFormat {
|
||||
// Format error if exist
|
||||
formatErr := ""
|
||||
if cfg.enableColors {
|
||||
|
@ -91,7 +97,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
|||
}
|
||||
|
||||
// Write buffer to output
|
||||
writeLog(cfg.Output, buf.Bytes())
|
||||
writeLog(cfg.Stream, buf.Bytes())
|
||||
|
||||
if cfg.Done != nil {
|
||||
cfg.Done(c, buf.Bytes())
|
||||
|
@ -125,7 +131,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
|||
buf.WriteString(err.Error())
|
||||
}
|
||||
|
||||
writeLog(cfg.Output, buf.Bytes())
|
||||
writeLog(cfg.Stream, buf.Bytes())
|
||||
|
||||
if cfg.Done != nil {
|
||||
cfg.Done(c, buf.Bytes())
|
||||
|
@ -141,9 +147,9 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
|||
func beforeHandlerFunc(cfg Config) {
|
||||
// If colors are enabled, check terminal compatibility
|
||||
if cfg.enableColors {
|
||||
cfg.Output = colorable.NewColorableStdout()
|
||||
cfg.Stream = colorable.NewColorableStdout()
|
||||
if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
|
||||
cfg.Output = colorable.NewNonColorable(os.Stdout)
|
||||
cfg.Stream = colorable.NewNonColorable(os.Stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +166,7 @@ func writeLog(w io.Writer, msg []byte) {
|
|||
// Write error to output
|
||||
if _, err := w.Write([]byte(err.Error())); err != nil {
|
||||
// There is something wrong with the given io.Writer
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) //nolint: errcheck // It is fine to ignore the error
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package logger
|
||||
|
||||
const (
|
||||
// Fiber's default logger
|
||||
DefaultFormat = "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n"
|
||||
// Apache Common Log Format (CLF)
|
||||
CommonFormat = "${ip} - - [${time}] \"${method} ${url} ${protocol}\" ${status} ${bytesSent}\n"
|
||||
// Apache Combined Log Format
|
||||
CombinedFormat = "${ip} - - [${time}] \"${method} ${url} ${protocol}\" ${status} ${bytesSent} \"${referer}\" \"${ua}\"\n"
|
||||
// JSON log formats
|
||||
JSONFormat = "{\"time\":\"${time}\",\"ip\":\"${ip}\",\"method\":\"${method}\",\"url\":\"${url}\",\"status\":${status},\"bytesSent\":${bytesSent}}\n"
|
||||
// Elastic Common Schema (ECS) Log Format
|
||||
ECSFormat = "{\"@timestamp\":\"${time}\",\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":\"${ip}\"},\"http\":{\"request\":{\"method\":\"${method}\",\"url\":\"${url}\",\"protocol\":\"${protocol}\"},\"response\":{\"status_code\":${status},\"body\":{\"bytes\":${bytesSent}}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":\"${method} ${url} responded with ${status}\"}\n"
|
||||
)
|
|
@ -40,7 +40,6 @@ func New(config ...Config) fiber.Handler {
|
|||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Set PID once
|
||||
pid := strconv.Itoa(os.Getpid())
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ func Test_Logger(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${error}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(_ fiber.Ctx) error {
|
||||
|
@ -94,7 +94,7 @@ func Test_Logger_locals(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -171,6 +171,147 @@ func Test_Logger_Done(t *testing.T) {
|
|||
require.Positive(t, buf.Len(), 0)
|
||||
}
|
||||
|
||||
// Test_Logger_Filter tests the Filter functionality of the logger middleware.
|
||||
// It verifies that logs are written or skipped based on the filter condition.
|
||||
func Test_Logger_Filter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Test Not Found", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Return true to skip logging for all requests != 404
|
||||
app.Use(New(Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
return c.Response().StatusCode() != fiber.StatusNotFound
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/nonexistent", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
// Expect logs for the 404 request
|
||||
require.Contains(t, logOutput.String(), "404")
|
||||
})
|
||||
|
||||
t.Run("Test OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Return true to skip logging for all requests == 200
|
||||
app.Use(New(Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
return c.Response().StatusCode() == fiber.StatusOK
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
// We skip logging for status == 200, so "200" should not appear
|
||||
require.NotContains(t, logOutput.String(), "200")
|
||||
})
|
||||
|
||||
t.Run("Always Skip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Filter always returns true => skip all logs
|
||||
app.Use(New(Config{
|
||||
Skip: func(_ fiber.Ctx) bool {
|
||||
return true // always skip
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
app.Get("/something", func(c fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusTeapot).SendString("I'm a teapot")
|
||||
})
|
||||
|
||||
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/something", nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect NO logs
|
||||
require.Empty(t, logOutput.String())
|
||||
})
|
||||
|
||||
t.Run("Never Skip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Filter always returns false => never skip logs
|
||||
app.Use(New(Config{
|
||||
Skip: func(_ fiber.Ctx) bool {
|
||||
return false // never skip
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
app.Get("/always", func(c fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusTeapot).SendString("Teapot again")
|
||||
})
|
||||
|
||||
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/always", nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect some logging - check any substring
|
||||
require.Contains(t, logOutput.String(), strconv.Itoa(fiber.StatusTeapot))
|
||||
})
|
||||
|
||||
t.Run("Skip /healthz", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Filter returns true (skip logs) if the request path is /healthz
|
||||
app.Use(New(Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
return c.Path() == "/healthz"
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
// Normal route
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello World!")
|
||||
})
|
||||
// Health route
|
||||
app.Get("/healthz", func(c fiber.Ctx) error {
|
||||
return c.SendString("OK")
|
||||
})
|
||||
|
||||
// Request to "/" -> should be logged
|
||||
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, logOutput.String(), "200")
|
||||
|
||||
// Reset output buffer
|
||||
logOutput.Reset()
|
||||
|
||||
// Request to "/healthz" -> should be skipped
|
||||
_, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/healthz", nil))
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, logOutput.String())
|
||||
})
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_ErrorTimeZone
|
||||
func Test_Logger_ErrorTimeZone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
@ -234,7 +375,7 @@ func Test_Logger_LoggerToWriter(t *testing.T) {
|
|||
|
||||
app.Use("/"+level, New(Config{
|
||||
Format: "${error}",
|
||||
Output: LoggerToWriter(logger, tc.
|
||||
Stream: LoggerToWriter(logger, tc.
|
||||
level),
|
||||
}))
|
||||
|
||||
|
@ -276,7 +417,7 @@ func Test_Logger_ErrorOutput_WithoutColor(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
Stream: o,
|
||||
DisableColors: true,
|
||||
}))
|
||||
|
||||
|
@ -293,7 +434,7 @@ func Test_Logger_ErrorOutput(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
Stream: o,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
|
@ -312,7 +453,7 @@ func Test_Logger_All(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${pid}${reqHeaders}${referer}${scheme}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${reqHeader:test}${query:test}${form:test}${cookie:test}${non}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
// Alias colors
|
||||
|
@ -326,6 +467,124 @@ func Test_Logger_All(t *testing.T) {
|
|||
require.Equal(t, expected, buf.String())
|
||||
}
|
||||
|
||||
func Test_Logger_CLF_Format(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Format: CommonFormat,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
expected := fmt.Sprintf("0.0.0.0 - - [%s] \"%s %s %s\" %d %d\n",
|
||||
time.Now().Format("15:04:05"),
|
||||
fiber.MethodGet, "/?foo=bar", "HTTP/1.1",
|
||||
fiber.StatusNotFound,
|
||||
0)
|
||||
logResponse := buf.String()
|
||||
require.Equal(t, expected, logResponse)
|
||||
}
|
||||
|
||||
func Test_Logger_Combined_CLF_Format(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Format: CombinedFormat,
|
||||
Stream: buf,
|
||||
}))
|
||||
const expectedUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
||||
const expectedReferer = "http://example.com"
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil)
|
||||
req.Header.Set("Referer", expectedReferer)
|
||||
req.Header.Set("User-Agent", expectedUA)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
expected := fmt.Sprintf("0.0.0.0 - - [%s] %q %d %d %q %q\n",
|
||||
time.Now().Format("15:04:05"),
|
||||
fmt.Sprintf("%s %s %s", fiber.MethodGet, "/?foo=bar", "HTTP/1.1"),
|
||||
fiber.StatusNotFound,
|
||||
0,
|
||||
expectedReferer,
|
||||
expectedUA)
|
||||
logResponse := buf.String()
|
||||
require.Equal(t, expected, logResponse)
|
||||
}
|
||||
|
||||
func Test_Logger_Json_Format(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Format: JSONFormat,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
expected := fmt.Sprintf(
|
||||
"{\"time\":%q,\"ip\":%q,\"method\":%q,\"url\":%q,\"status\":%d,\"bytesSent\":%d}\n",
|
||||
time.Now().Format("15:04:05"),
|
||||
"0.0.0.0",
|
||||
fiber.MethodGet,
|
||||
"/?foo=bar",
|
||||
fiber.StatusNotFound,
|
||||
0,
|
||||
)
|
||||
logResponse := buf.String()
|
||||
require.Equal(t, expected, logResponse)
|
||||
}
|
||||
|
||||
func Test_Logger_ECS_Format(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Format: ECSFormat,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
expected := fmt.Sprintf(
|
||||
"{\"@timestamp\":%q,\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":%q},\"http\":{\"request\":{\"method\":%q,\"url\":%q,\"protocol\":%q},\"response\":{\"status_code\":%d,\"body\":{\"bytes\":%d}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":%q}\n",
|
||||
time.Now().Format("15:04:05"),
|
||||
"0.0.0.0",
|
||||
fiber.MethodGet,
|
||||
"/?foo=bar",
|
||||
"HTTP/1.1",
|
||||
fiber.StatusNotFound,
|
||||
0,
|
||||
fmt.Sprintf("%s %s responded with %d", fiber.MethodGet, "/?foo=bar", fiber.StatusNotFound),
|
||||
)
|
||||
logResponse := buf.String()
|
||||
require.Equal(t, expected, logResponse)
|
||||
}
|
||||
|
||||
func getLatencyTimeUnits() []struct {
|
||||
unit string
|
||||
div time.Duration
|
||||
|
@ -358,7 +617,7 @@ func Test_Logger_WithLatency(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
logger := New(Config{
|
||||
Output: buff,
|
||||
Stream: buff,
|
||||
Format: "${latency}",
|
||||
})
|
||||
app.Use(logger)
|
||||
|
@ -403,7 +662,7 @@ func Test_Logger_WithLatency_DefaultFormat(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
logger := New(Config{
|
||||
Output: buff,
|
||||
Stream: buff,
|
||||
})
|
||||
app.Use(logger)
|
||||
|
||||
|
@ -453,7 +712,7 @@ func Test_Query_Params(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${queryParams}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar&baz=moz", nil))
|
||||
|
@ -474,7 +733,7 @@ func Test_Response_Body(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${resBody}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -508,7 +767,7 @@ func Test_Request_Body(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Post("/", func(c fiber.Ctx) error {
|
||||
|
@ -536,7 +795,7 @@ func Test_Logger_AppendUint(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -611,7 +870,7 @@ func Test_Response_Header(t *testing.T) {
|
|||
}))
|
||||
app.Use(New(Config{
|
||||
Format: "${respHeader:X-Request-ID}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
|
@ -634,7 +893,7 @@ func Test_Req_Header(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${reqHeader:test}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
|
@ -658,7 +917,7 @@ func Test_ReqHeader_Header(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${reqHeader:test}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
|
@ -689,7 +948,7 @@ func Test_CustomTags(t *testing.T) {
|
|||
return output.WriteString(customTag)
|
||||
},
|
||||
},
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
|
@ -713,7 +972,7 @@ func Test_Logger_ByteSent_Streaming(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -724,7 +983,7 @@ func Test_Logger_ByteSent_Streaming(t *testing.T) {
|
|||
for {
|
||||
i++
|
||||
msg := fmt.Sprintf("%d - the time is %v", i, time.Now())
|
||||
fmt.Fprintf(w, "data: Message: %s\n\n", msg) //nolint:errcheck // ignore error
|
||||
fmt.Fprintf(w, "data: Message: %s\n\n", msg)
|
||||
err := w.Flush()
|
||||
if err != nil {
|
||||
break
|
||||
|
@ -759,7 +1018,7 @@ func Test_Logger_EnableColors(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
Stream: o,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
|
@ -782,7 +1041,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
|
@ -794,7 +1053,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
b.Run("DefaultFormat", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -805,7 +1064,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
DisableColors: true,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -819,7 +1078,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
logger := fiberlog.DefaultLogger()
|
||||
logger.SetOutput(io.Discard)
|
||||
app.Use(New(Config{
|
||||
Output: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
Stream: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -831,7 +1090,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
|
@ -844,7 +1103,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Locals("demo", "johndoe")
|
||||
|
@ -857,7 +1116,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/int", func(c fiber.Ctx) error {
|
||||
c.Locals("demo", 55)
|
||||
|
@ -874,7 +1133,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
io.Discard.Write(logString) //nolint:errcheck // ignore error
|
||||
}
|
||||
},
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/logging", func(ctx fiber.Ctx) error {
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
|
@ -886,7 +1145,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${pid}${reqHeaders}${referer}${scheme}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${reqHeader:test}${query:test}${form:test}${cookie:test}${non}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -898,7 +1157,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("Connection", "keep-alive")
|
||||
|
@ -908,7 +1167,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
for {
|
||||
i++
|
||||
msg := fmt.Sprintf("%d - the time is %v", i, time.Now())
|
||||
fmt.Fprintf(w, "data: Message: %s\n\n", msg) //nolint:errcheck // ignore error
|
||||
fmt.Fprintf(w, "data: Message: %s\n\n", msg)
|
||||
err := w.Flush()
|
||||
if err != nil {
|
||||
break
|
||||
|
@ -927,7 +1186,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${resBody}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Sample response body")
|
||||
|
@ -950,7 +1209,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
|
@ -962,7 +1221,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
b.Run("DefaultFormat", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -975,7 +1234,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
logger := fiberlog.DefaultLogger()
|
||||
logger.SetOutput(io.Discard)
|
||||
app.Use(New(Config{
|
||||
Output: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
Stream: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -986,7 +1245,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
DisableColors: true,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -999,7 +1258,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
|
@ -1012,7 +1271,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Locals("demo", "johndoe")
|
||||
|
@ -1025,7 +1284,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/int", func(c fiber.Ctx) error {
|
||||
c.Locals("demo", 55)
|
||||
|
@ -1042,7 +1301,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
io.Discard.Write(logString) //nolint:errcheck // ignore error
|
||||
}
|
||||
},
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/logging", func(ctx fiber.Ctx) error {
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
|
@ -1054,7 +1313,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${pid}${reqHeaders}${referer}${scheme}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${reqHeader:test}${query:test}${form:test}${cookie:test}${non}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -1066,7 +1325,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("Connection", "keep-alive")
|
||||
|
@ -1076,7 +1335,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
for {
|
||||
i++
|
||||
msg := fmt.Sprintf("%d - the time is %v", i, time.Now())
|
||||
fmt.Fprintf(w, "data: Message: %s\n\n", msg) //nolint:errcheck // ignore error
|
||||
fmt.Fprintf(w, "data: Message: %s\n\n", msg)
|
||||
err := w.Flush()
|
||||
if err != nil {
|
||||
break
|
||||
|
@ -1095,7 +1354,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${resBody}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Sample response body")
|
||||
|
|
|
@ -506,7 +506,10 @@ func Test_Proxy_Do_WithRealURL(t *testing.T) {
|
|||
return Do(c, "https://www.google.com")
|
||||
})
|
||||
|
||||
resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
|
||||
resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), fiber.TestConfig{
|
||||
Timeout: 2 * time.Second,
|
||||
FailOnTimeout: true,
|
||||
})
|
||||
require.NoError(t, err1)
|
||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
require.Equal(t, "/test", resp.Request.URL.String())
|
||||
|
@ -523,7 +526,10 @@ func Test_Proxy_Do_WithRedirect(t *testing.T) {
|
|||
return Do(c, "https://google.com")
|
||||
})
|
||||
|
||||
resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
|
||||
resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), fiber.TestConfig{
|
||||
Timeout: 2 * time.Second,
|
||||
FailOnTimeout: true,
|
||||
})
|
||||
require.NoError(t, err1)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
@ -558,7 +564,10 @@ func Test_Proxy_DoRedirects_TooManyRedirects(t *testing.T) {
|
|||
return DoRedirects(c, "http://google.com", 0)
|
||||
})
|
||||
|
||||
resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
|
||||
resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), fiber.TestConfig{
|
||||
Timeout: 2 * time.Second,
|
||||
FailOnTimeout: true,
|
||||
})
|
||||
require.NoError(t, err1)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
|
129
path.go
129
path.go
|
@ -7,9 +7,11 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
|
@ -25,6 +27,12 @@ type routeParser struct {
|
|||
plusCount int // number of plus parameters, used internally to give the plus parameter its number
|
||||
}
|
||||
|
||||
var routerParserPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &routeParser{}
|
||||
},
|
||||
}
|
||||
|
||||
// routeSegment holds the segment metadata
|
||||
type routeSegment struct {
|
||||
// const information
|
||||
|
@ -123,8 +131,6 @@ var (
|
|||
parameterConstraintSeparatorChars = []byte{paramConstraintSeparator}
|
||||
// list of parameter constraint data start
|
||||
parameterConstraintDataStartChars = []byte{paramConstraintDataStart}
|
||||
// list of parameter constraint data end
|
||||
parameterConstraintDataEndChars = []byte{paramConstraintDataEnd}
|
||||
// list of parameter constraint data separator
|
||||
parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator}
|
||||
)
|
||||
|
@ -152,11 +158,11 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
|
|||
pattern = "/" + pattern
|
||||
}
|
||||
|
||||
patternPretty := pattern
|
||||
patternPretty := []byte(pattern)
|
||||
|
||||
// Case-sensitive routing, all to lowercase
|
||||
if !config.CaseSensitive {
|
||||
patternPretty = utils.ToLower(patternPretty)
|
||||
patternPretty = utils.ToLowerBytes(patternPretty)
|
||||
path = utils.ToLower(path)
|
||||
}
|
||||
// Strict routing, remove trailing slashes
|
||||
|
@ -164,12 +170,15 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
|
|||
patternPretty = utils.TrimRight(patternPretty, '/')
|
||||
}
|
||||
|
||||
parser := parseRoute(patternPretty)
|
||||
parser, _ := routerParserPool.Get().(*routeParser) //nolint:errcheck // only contains routeParser
|
||||
parser.reset()
|
||||
parser.parseRoute(string(patternPretty))
|
||||
defer routerParserPool.Put(parser)
|
||||
|
||||
if patternPretty == "/" && path == "/" {
|
||||
if string(patternPretty) == "/" && path == "/" {
|
||||
return true
|
||||
// '*' wildcard matches any path
|
||||
} else if patternPretty == "/*" {
|
||||
} else if string(patternPretty) == "/*" {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -180,42 +189,47 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
|
|||
}
|
||||
}
|
||||
// Check for a simple match
|
||||
patternPretty = RemoveEscapeChar(patternPretty)
|
||||
if len(patternPretty) == len(path) && patternPretty == path {
|
||||
return true
|
||||
}
|
||||
// No match
|
||||
return false
|
||||
patternPretty = RemoveEscapeCharBytes(patternPretty)
|
||||
|
||||
return string(patternPretty) == path
|
||||
}
|
||||
|
||||
func (parser *routeParser) reset() {
|
||||
parser.segs = parser.segs[:0]
|
||||
parser.params = parser.params[:0]
|
||||
parser.wildCardCount = 0
|
||||
parser.plusCount = 0
|
||||
}
|
||||
|
||||
// parseRoute analyzes the route and divides it into segments for constant areas and parameters,
|
||||
// this information is needed later when assigning the requests to the declared routes
|
||||
func parseRoute(pattern string, customConstraints ...CustomConstraint) routeParser {
|
||||
parser := routeParser{}
|
||||
part := ""
|
||||
func (parser *routeParser) parseRoute(pattern string, customConstraints ...CustomConstraint) {
|
||||
var n int
|
||||
var seg *routeSegment
|
||||
for len(pattern) > 0 {
|
||||
nextParamPosition := findNextParamPosition(pattern)
|
||||
// handle the parameter part
|
||||
if nextParamPosition == 0 {
|
||||
processedPart, seg := parser.analyseParameterPart(pattern, customConstraints...)
|
||||
parser.params, parser.segs, part = append(parser.params, seg.ParamName), append(parser.segs, seg), processedPart
|
||||
n, seg = parser.analyseParameterPart(pattern, customConstraints...)
|
||||
parser.params, parser.segs = append(parser.params, seg.ParamName), append(parser.segs, seg)
|
||||
} else {
|
||||
processedPart, seg := parser.analyseConstantPart(pattern, nextParamPosition)
|
||||
parser.segs, part = append(parser.segs, seg), processedPart
|
||||
n, seg = parser.analyseConstantPart(pattern, nextParamPosition)
|
||||
parser.segs = append(parser.segs, seg)
|
||||
}
|
||||
|
||||
// reduce the pattern by the processed parts
|
||||
if len(part) == len(pattern) {
|
||||
break
|
||||
}
|
||||
pattern = pattern[len(part):]
|
||||
pattern = pattern[n:]
|
||||
}
|
||||
// mark last segment
|
||||
if len(parser.segs) > 0 {
|
||||
parser.segs[len(parser.segs)-1].IsLast = true
|
||||
}
|
||||
parser.segs = addParameterMetaInfo(parser.segs)
|
||||
}
|
||||
|
||||
// parseRoute analyzes the route and divides it into segments for constant areas and parameters,
|
||||
// this information is needed later when assigning the requests to the declared routes
|
||||
func parseRoute(pattern string, customConstraints ...CustomConstraint) routeParser {
|
||||
parser := routeParser{}
|
||||
parser.parseRoute(pattern, customConstraints...)
|
||||
return parser
|
||||
}
|
||||
|
||||
|
@ -283,7 +297,7 @@ func findNextParamPosition(pattern string) int {
|
|||
}
|
||||
|
||||
// analyseConstantPart find the end of the constant part and create the route segment
|
||||
func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (string, *routeSegment) {
|
||||
func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (int, *routeSegment) {
|
||||
// handle the constant part
|
||||
processedPart := pattern
|
||||
if nextParamPosition != -1 {
|
||||
|
@ -291,14 +305,14 @@ func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (
|
|||
processedPart = pattern[:nextParamPosition]
|
||||
}
|
||||
constPart := RemoveEscapeChar(processedPart)
|
||||
return processedPart, &routeSegment{
|
||||
return len(processedPart), &routeSegment{
|
||||
Const: constPart,
|
||||
Length: len(constPart),
|
||||
}
|
||||
}
|
||||
|
||||
// analyseParameterPart find the parameter end and create the route segment
|
||||
func (routeParser *routeParser) analyseParameterPart(pattern string, customConstraints ...CustomConstraint) (string, *routeSegment) {
|
||||
func (parser *routeParser) analyseParameterPart(pattern string, customConstraints ...CustomConstraint) (int, *routeSegment) {
|
||||
isWildCard := pattern[0] == wildcardParam
|
||||
isPlusParam := pattern[0] == plusParam
|
||||
|
||||
|
@ -317,18 +331,19 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
|||
parameterEndPosition = 0
|
||||
case parameterEndPosition == -1:
|
||||
parameterEndPosition = len(pattern) - 1
|
||||
case !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars):
|
||||
case bytes.IndexByte(parameterDelimiterChars, pattern[parameterEndPosition+1]) == -1:
|
||||
parameterEndPosition++
|
||||
}
|
||||
|
||||
// find constraint part if exists in the parameter part and remove it
|
||||
if parameterEndPosition > 0 {
|
||||
parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars)
|
||||
parameterConstraintEnd = findLastCharsetPosition(pattern[0:parameterEndPosition+1], parameterConstraintEndChars)
|
||||
parameterConstraintEnd = strings.LastIndexByte(pattern[0:parameterEndPosition+1], paramConstraintEnd)
|
||||
}
|
||||
|
||||
// cut params part
|
||||
processedPart := pattern[0 : parameterEndPosition+1]
|
||||
n := parameterEndPosition + 1
|
||||
paramName := RemoveEscapeChar(GetTrimmedParam(processedPart))
|
||||
|
||||
// Check has constraint
|
||||
|
@ -341,7 +356,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
|||
|
||||
for _, c := range userConstraints {
|
||||
start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars)
|
||||
end := findLastCharsetPosition(c, parameterConstraintDataEndChars)
|
||||
end := strings.LastIndexByte(c, paramConstraintDataEnd)
|
||||
|
||||
// Assign constraint
|
||||
if start != -1 && end != -1 {
|
||||
|
@ -384,11 +399,11 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
|||
|
||||
// add access iterator to wildcard and plus
|
||||
if isWildCard {
|
||||
routeParser.wildCardCount++
|
||||
paramName += strconv.Itoa(routeParser.wildCardCount)
|
||||
parser.wildCardCount++
|
||||
paramName += strconv.Itoa(parser.wildCardCount)
|
||||
} else if isPlusParam {
|
||||
routeParser.plusCount++
|
||||
paramName += strconv.Itoa(routeParser.plusCount)
|
||||
parser.plusCount++
|
||||
paramName += strconv.Itoa(parser.plusCount)
|
||||
}
|
||||
|
||||
segment := &routeSegment{
|
||||
|
@ -402,17 +417,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
|||
segment.Constraints = constraints
|
||||
}
|
||||
|
||||
return processedPart, segment
|
||||
}
|
||||
|
||||
// isInCharset check is the given character in the charset list
|
||||
func isInCharset(searchChar byte, charset []byte) bool {
|
||||
for _, char := range charset {
|
||||
if char == searchChar {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return n, segment
|
||||
}
|
||||
|
||||
// findNextCharsetPosition search the next char position from the charset
|
||||
|
@ -427,18 +432,6 @@ func findNextCharsetPosition(search string, charset []byte) int {
|
|||
return nextPosition
|
||||
}
|
||||
|
||||
// findLastCharsetPosition search the last char position from the charset
|
||||
func findLastCharsetPosition(search string, charset []byte) int {
|
||||
lastPosition := -1
|
||||
for _, char := range charset {
|
||||
if pos := strings.LastIndexByte(search, char); pos != -1 && (pos < lastPosition || lastPosition == -1) {
|
||||
lastPosition = pos
|
||||
}
|
||||
}
|
||||
|
||||
return lastPosition
|
||||
}
|
||||
|
||||
// findNextCharsetPositionConstraint search the next char position from the charset
|
||||
// unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern
|
||||
func findNextCharsetPositionConstraint(search string, charset []byte) int {
|
||||
|
@ -494,9 +487,9 @@ func splitNonEscaped(s, sep string) []string {
|
|||
}
|
||||
|
||||
// getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
|
||||
func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { //nolint: revive // Accepting a bool param is fine here
|
||||
func (parser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { //nolint:revive // Accepting a bool param is fine here
|
||||
var i, paramsIterator, partLen int
|
||||
for _, segment := range routeParser.segs {
|
||||
for _, segment := range parser.segs {
|
||||
partLen = len(detectionPath)
|
||||
// check const segment
|
||||
if !segment.IsParam {
|
||||
|
@ -618,7 +611,7 @@ func GetTrimmedParam(param string) string {
|
|||
return param[start:end]
|
||||
}
|
||||
|
||||
// RemoveEscapeChar remove escape characters
|
||||
// RemoveEscapeChar removes escape characters
|
||||
func RemoveEscapeChar(word string) string {
|
||||
b := []byte(word)
|
||||
dst := 0
|
||||
|
@ -632,6 +625,18 @@ func RemoveEscapeChar(word string) string {
|
|||
return string(b[:dst])
|
||||
}
|
||||
|
||||
// RemoveEscapeCharBytes removes escape characters
|
||||
func RemoveEscapeCharBytes(word []byte) []byte {
|
||||
dst := 0
|
||||
for src := 0; src < len(word); src++ {
|
||||
if word[src] != '\\' {
|
||||
word[dst] = word[src]
|
||||
dst++
|
||||
}
|
||||
}
|
||||
return word[:dst]
|
||||
}
|
||||
|
||||
func getParamConstraintType(constraintPart string) TypeConstraint {
|
||||
switch constraintPart {
|
||||
case ConstraintInt:
|
||||
|
|
|
@ -217,7 +217,7 @@ func Benchmark_Path_matchParams(t *testing.B) {
|
|||
state = "not match"
|
||||
}
|
||||
t.Run(testCollection.pattern+" | "+state+" | "+c.url, func(b *testing.B) {
|
||||
for i := 0; i <= b.N; i++ {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck); match {
|
||||
// Get testCases from the original path
|
||||
matchRes = true
|
||||
|
@ -250,7 +250,7 @@ func Benchmark_RoutePatternMatch(t *testing.B) {
|
|||
state = "not match"
|
||||
}
|
||||
t.Run(testCollection.pattern+" | "+state+" | "+c.url, func(b *testing.B) {
|
||||
for i := 0; i <= b.N; i++ {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if match := RoutePatternMatch(c.url, testCollection.pattern); match {
|
||||
// Get testCases from the original path
|
||||
matchRes = true
|
||||
|
|
|
@ -178,7 +178,7 @@ func Test_Redirect_Back_WithFlashMessages(t *testing.T) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
err := c.Redirect().With("success", "1").With("message", "test").Back("/")
|
||||
require.NoError(t, err)
|
||||
|
@ -225,7 +225,7 @@ func Test_Redirect_Route_WithFlashMessages(t *testing.T) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
err := c.Redirect().With("success", "1").With("message", "test").Route("user")
|
||||
|
||||
|
@ -259,7 +259,7 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().URI().SetQueryString("id=1&name=tom")
|
||||
err := c.Redirect().With("success", "1").With("message", "test").WithInput().Route("user")
|
||||
|
@ -294,7 +294,7 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Request().Header.Set(HeaderContentType, MIMEApplicationForm)
|
||||
c.Request().SetBodyString("id=1&name=tom")
|
||||
|
@ -330,7 +330,7 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
@ -376,7 +376,7 @@ func Test_Redirect_parseAndClearFlashMessages(t *testing.T) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
msgs := redirectionMsgs{
|
||||
{
|
||||
|
@ -464,7 +464,7 @@ func Benchmark_Redirect_Route(b *testing.B) {
|
|||
return c.JSON(c.Params("name"))
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -491,7 +491,7 @@ func Benchmark_Redirect_Route_WithQueries(b *testing.B) {
|
|||
return c.JSON(c.Params("name"))
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -523,7 +523,7 @@ func Benchmark_Redirect_Route_WithFlashMessages(b *testing.B) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -576,7 +576,7 @@ func Benchmark_Redirect_parseAndClearFlashMessages(b *testing.B) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||
require.NoError(b, err)
|
||||
|
@ -618,7 +618,7 @@ func Benchmark_Redirect_processFlashMessages(b *testing.B) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
c.Redirect().With("success", "1").With("message", "test")
|
||||
|
||||
|
@ -647,7 +647,7 @@ func Benchmark_Redirect_Messages(b *testing.B) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||
require.NoError(b, err)
|
||||
|
@ -684,7 +684,7 @@ func Benchmark_Redirect_OldInputs(b *testing.B) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||
require.NoError(b, err)
|
||||
|
@ -719,7 +719,7 @@ func Benchmark_Redirect_Message(b *testing.B) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||
require.NoError(b, err)
|
||||
|
@ -750,7 +750,7 @@ func Benchmark_Redirect_OldInput(b *testing.B) {
|
|||
return c.SendString("user")
|
||||
}).Name("user")
|
||||
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||
require.NoError(b, err)
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"mime/multipart"
|
||||
)
|
||||
|
||||
//go:generate ifacemaker --file req.go --struct DefaultReq --iface Req --pkg fiber --output req_interface_gen.go --not-exported true --iface-comment "Req"
|
||||
type DefaultReq struct {
|
||||
ctx *DefaultCtx
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Accepts(offers ...string) string {
|
||||
return r.ctx.Accepts(offers...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) AcceptsCharsets(offers ...string) string {
|
||||
return r.ctx.AcceptsCharsets(offers...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) AcceptsEncodings(offers ...string) string {
|
||||
return r.ctx.AcceptsEncodings(offers...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) AcceptsLanguages(offers ...string) string {
|
||||
return r.ctx.AcceptsLanguages(offers...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) BaseURL() string {
|
||||
return r.ctx.BaseURL()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Body() []byte {
|
||||
return r.ctx.Body()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) BodyRaw() []byte {
|
||||
return r.ctx.BodyRaw()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) ClientHelloInfo() *tls.ClientHelloInfo {
|
||||
return r.ctx.ClientHelloInfo()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Cookies(key string, defaultValue ...string) string {
|
||||
return r.ctx.Cookies(key, defaultValue...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) FormFile(key string) (*multipart.FileHeader, error) {
|
||||
return r.ctx.FormFile(key)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) FormValue(key string, defaultValue ...string) string {
|
||||
return r.ctx.FormValue(key, defaultValue...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Fresh() bool {
|
||||
return r.ctx.Fresh()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Get(key string, defaultValue ...string) string {
|
||||
return r.ctx.Get(key, defaultValue...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Host() string {
|
||||
return r.ctx.Host()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Hostname() string {
|
||||
return r.ctx.Hostname()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) IP() string {
|
||||
return r.ctx.IP()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) IPs() []string {
|
||||
return r.ctx.IPs()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Is(extension string) bool {
|
||||
return r.ctx.Is(extension)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) IsFromLocal() bool {
|
||||
return r.ctx.IsFromLocal()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) IsProxyTrusted() bool {
|
||||
return r.ctx.IsProxyTrusted()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Method(override ...string) string {
|
||||
return r.ctx.Method(override...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) MultipartForm() (*multipart.Form, error) {
|
||||
return r.ctx.MultipartForm()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) OriginalURL() string {
|
||||
return r.ctx.OriginalURL()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Params(key string, defaultValue ...string) string {
|
||||
return r.ctx.Params(key, defaultValue...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Path(override ...string) string {
|
||||
return r.ctx.Path(override...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Port() string {
|
||||
return r.ctx.Port()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Protocol() string {
|
||||
return r.ctx.Protocol()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Queries() map[string]string {
|
||||
return r.ctx.Queries()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Query(key string, defaultValue ...string) string {
|
||||
return r.ctx.Query(key, defaultValue...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Range(size int) (Range, error) {
|
||||
return r.ctx.Range(size)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Route() *Route {
|
||||
return r.ctx.Route()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) SaveFile(fileheader *multipart.FileHeader, path string) error {
|
||||
return r.ctx.SaveFile(fileheader, path)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error {
|
||||
return r.ctx.SaveFileToStorage(fileheader, path, storage)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Secure() bool {
|
||||
return r.ctx.Secure()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Stale() bool {
|
||||
return r.ctx.Stale()
|
||||
}
|
||||
|
||||
func (r *DefaultReq) Subdomains(offset ...int) []string {
|
||||
return r.ctx.Subdomains(offset...)
|
||||
}
|
||||
|
||||
func (r *DefaultReq) XHR() bool {
|
||||
return r.ctx.XHR()
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// Code generated by ifacemaker; DO NOT EDIT.
|
||||
|
||||
package fiber
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"mime/multipart"
|
||||
)
|
||||
|
||||
// Req
|
||||
type Req interface {
|
||||
Accepts(offers ...string) string
|
||||
AcceptsCharsets(offers ...string) string
|
||||
AcceptsEncodings(offers ...string) string
|
||||
AcceptsLanguages(offers ...string) string
|
||||
BaseURL() string
|
||||
Body() []byte
|
||||
BodyRaw() []byte
|
||||
ClientHelloInfo() *tls.ClientHelloInfo
|
||||
Cookies(key string, defaultValue ...string) string
|
||||
FormFile(key string) (*multipart.FileHeader, error)
|
||||
FormValue(key string, defaultValue ...string) string
|
||||
Fresh() bool
|
||||
Get(key string, defaultValue ...string) string
|
||||
Host() string
|
||||
Hostname() string
|
||||
IP() string
|
||||
IPs() []string
|
||||
Is(extension string) bool
|
||||
IsFromLocal() bool
|
||||
IsProxyTrusted() bool
|
||||
Method(override ...string) string
|
||||
MultipartForm() (*multipart.Form, error)
|
||||
OriginalURL() string
|
||||
Params(key string, defaultValue ...string) string
|
||||
Path(override ...string) string
|
||||
Port() string
|
||||
Protocol() string
|
||||
Queries() map[string]string
|
||||
Query(key string, defaultValue ...string) string
|
||||
Range(size int) (Range, error)
|
||||
Route() *Route
|
||||
SaveFile(fileheader *multipart.FileHeader, path string) error
|
||||
SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error
|
||||
Secure() bool
|
||||
Stale() bool
|
||||
Subdomains(offset ...int) []string
|
||||
XHR() bool
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
)
|
||||
|
||||
//go:generate ifacemaker --file res.go --struct DefaultRes --iface Res --pkg fiber --output res_interface_gen.go --not-exported true --iface-comment "Res"
|
||||
type DefaultRes struct {
|
||||
ctx *DefaultCtx
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Append(field string, values ...string) {
|
||||
r.ctx.Append(field, values...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Attachment(filename ...string) {
|
||||
r.ctx.Attachment(filename...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) AutoFormat(body any) error {
|
||||
return r.ctx.AutoFormat(body)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) CBOR(body any, ctype ...string) error {
|
||||
return r.ctx.CBOR(body, ctype...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) ClearCookie(key ...string) {
|
||||
r.ctx.ClearCookie(key...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Cookie(cookie *Cookie) {
|
||||
r.ctx.Cookie(cookie)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Download(file string, filename ...string) error {
|
||||
return r.ctx.Download(file, filename...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Format(handlers ...ResFmt) error {
|
||||
return r.ctx.Format(handlers...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Get(key string, defaultValue ...string) string {
|
||||
return r.ctx.GetRespHeader(key, defaultValue...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) JSON(body any, ctype ...string) error {
|
||||
return r.ctx.JSON(body, ctype...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) JSONP(data any, callback ...string) error {
|
||||
return r.ctx.JSONP(data, callback...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Links(link ...string) {
|
||||
r.ctx.Links(link...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Location(path string) {
|
||||
r.ctx.Location(path)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Render(name string, bind any, layouts ...string) error {
|
||||
return r.ctx.Render(name, bind, layouts...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Send(body []byte) error {
|
||||
return r.ctx.Send(body)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) SendFile(file string, config ...SendFile) error {
|
||||
return r.ctx.SendFile(file, config...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) SendStatus(status int) error {
|
||||
return r.ctx.SendStatus(status)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) SendString(body string) error {
|
||||
return r.ctx.SendString(body)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) SendStreamWriter(streamWriter func(*bufio.Writer)) error {
|
||||
return r.ctx.SendStreamWriter(streamWriter)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Set(key, val string) {
|
||||
r.ctx.Set(key, val)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Status(status int) Ctx {
|
||||
return r.ctx.Status(status)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Type(extension string, charset ...string) Ctx {
|
||||
return r.ctx.Type(extension, charset...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Vary(fields ...string) {
|
||||
r.ctx.Vary(fields...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Write(p []byte) (int, error) {
|
||||
return r.ctx.Write(p)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) Writef(f string, a ...any) (int, error) {
|
||||
return r.ctx.Writef(f, a...)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) WriteString(s string) (int, error) {
|
||||
return r.ctx.WriteString(s)
|
||||
}
|
||||
|
||||
func (r *DefaultRes) XML(data any) error {
|
||||
return r.ctx.XML(data)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Code generated by ifacemaker; DO NOT EDIT.
|
||||
|
||||
package fiber
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
)
|
||||
|
||||
// Res
|
||||
type Res interface {
|
||||
Append(field string, values ...string)
|
||||
Attachment(filename ...string)
|
||||
AutoFormat(body any) error
|
||||
CBOR(body any, ctype ...string) error
|
||||
ClearCookie(key ...string)
|
||||
Cookie(cookie *Cookie)
|
||||
Download(file string, filename ...string) error
|
||||
Format(handlers ...ResFmt) error
|
||||
Get(key string, defaultValue ...string) string
|
||||
JSON(body any, ctype ...string) error
|
||||
JSONP(data any, callback ...string) error
|
||||
Links(link ...string)
|
||||
Location(path string)
|
||||
Render(name string, bind any, layouts ...string) error
|
||||
Send(body []byte) error
|
||||
SendFile(file string, config ...SendFile) error
|
||||
SendStatus(status int) error
|
||||
SendString(body string) error
|
||||
SendStreamWriter(streamWriter func(*bufio.Writer)) error
|
||||
Set(key, val string)
|
||||
Status(status int) Ctx
|
||||
Type(extension string, charset ...string) Ctx
|
||||
Vary(fields ...string)
|
||||
Write(p []byte) (int, error)
|
||||
Writef(f string, a ...any) (int, error)
|
||||
WriteString(s string) (int, error)
|
||||
XML(data any) error
|
||||
}
|
85
router.go
85
router.go
|
@ -106,11 +106,11 @@ func (r *Route[TCtx]) match(detectionPath, path string, params *[maxParams]strin
|
|||
return false
|
||||
}
|
||||
|
||||
func (app *App[TCtx]) next(c TCtx) (bool, error) { //nolint: unparam // bool param might be useful for testing
|
||||
func (app *App[TCtx]) next(c TCtx) (bool, error) { //nolint:unparam // bool param might be useful for testing
|
||||
// Get stack length
|
||||
tree, ok := app.treeStack[c.getMethodINT()][c.getTreePath()]
|
||||
tree, ok := app.treeStack[c.getMethodInt()][c.getTreePathHash()]
|
||||
if !ok {
|
||||
tree = app.treeStack[c.getMethodINT()][""]
|
||||
tree = app.treeStack[c.getMethodInt()][0]
|
||||
}
|
||||
lenr := len(tree) - 1
|
||||
|
||||
|
@ -154,6 +154,61 @@ func (app *App[TCtx]) next(c TCtx) (bool, error) { //nolint: unparam // bool par
|
|||
return false, err
|
||||
}
|
||||
|
||||
func (app *App) next2222(c *DefaultCtx) (bool, error) {
|
||||
// Get stack length
|
||||
tree, ok := app.treeStack[c.methodInt][c.treePathHash]
|
||||
if !ok {
|
||||
tree = app.treeStack[c.methodInt][0]
|
||||
}
|
||||
lenTree := len(tree) - 1
|
||||
|
||||
// Loop over the route stack starting from previous index
|
||||
for c.indexRoute < lenTree {
|
||||
// Increment route index
|
||||
c.indexRoute++
|
||||
|
||||
// Get *Route
|
||||
route := tree[c.indexRoute]
|
||||
|
||||
var match bool
|
||||
var err error
|
||||
// skip for mounted apps
|
||||
if route.mount {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it matches the request path
|
||||
match = route.match(utils.UnsafeString(c.detectionPath), utils.UnsafeString(c.path), &c.values)
|
||||
if !match {
|
||||
// No match, next route
|
||||
continue
|
||||
}
|
||||
// Pass route reference and param values
|
||||
c.route = route
|
||||
|
||||
// Non use handler matched
|
||||
if !c.matched && !route.use {
|
||||
c.matched = true
|
||||
}
|
||||
|
||||
// Execute first handler of route
|
||||
c.indexHandler = 0
|
||||
if len(route.Handlers) > 0 {
|
||||
err = route.Handlers[0](c)
|
||||
}
|
||||
return match, err // Stop scanning the stack
|
||||
}
|
||||
|
||||
// If c.Next() does not match, return 404
|
||||
err := NewError(StatusNotFound, "Cannot "+c.Method()+" "+html.EscapeString(c.pathOriginal))
|
||||
if !c.matched && app.methodExist(c) {
|
||||
// If no match, scan stack again if other methods match the request
|
||||
// Moved from app.handler because middleware may break the route chain
|
||||
err = ErrMethodNotAllowed
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (app *App[TCtx]) requestHandler(rctx *fasthttp.RequestCtx) {
|
||||
// Acquire DefaultCtx from the pool
|
||||
ctx := app.AcquireCtx(rctx)
|
||||
|
@ -161,7 +216,7 @@ func (app *App[TCtx]) requestHandler(rctx *fasthttp.RequestCtx) {
|
|||
defer app.ReleaseCtx(ctx)
|
||||
|
||||
// Check if the HTTP method is valid
|
||||
if app.methodInt(ctx.Method()) == -1 {
|
||||
if ctx.methodInt == -1 {
|
||||
_ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil
|
||||
return
|
||||
}
|
||||
|
@ -363,30 +418,28 @@ func (app *App[TCtx]) buildTree() *App[TCtx] {
|
|||
|
||||
// loop all the methods and stacks and create the prefix tree
|
||||
for m := range app.config.RequestMethods {
|
||||
tsMap := make(map[string][]*Route[TCtx])
|
||||
tsMap := make(map[int][]*Route[TCtx])
|
||||
for _, route := range app.stack[m] {
|
||||
treePath := ""
|
||||
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
|
||||
treePath = route.routeParser.segs[0].Const[:3]
|
||||
treePathHash := 0
|
||||
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= maxDetectionPaths {
|
||||
treePathHash = int(route.routeParser.segs[0].Const[0])<<16 |
|
||||
int(route.routeParser.segs[0].Const[1])<<8 |
|
||||
int(route.routeParser.segs[0].Const[2])
|
||||
}
|
||||
// create tree stack
|
||||
tsMap[treePath] = append(tsMap[treePath], route)
|
||||
tsMap[treePathHash] = append(tsMap[treePathHash], route)
|
||||
}
|
||||
app.treeStack[m] = tsMap
|
||||
}
|
||||
|
||||
// loop the methods and tree stacks and add global stack and sort everything
|
||||
for m := range app.config.RequestMethods {
|
||||
tsMap := app.treeStack[m]
|
||||
for treePart := range tsMap {
|
||||
if treePart != "" {
|
||||
if treePart != 0 {
|
||||
// merge global tree routes in current tree stack
|
||||
tsMap[treePart] = uniqueRouteStack[TCtx](append(tsMap[treePart], tsMap[""]...))
|
||||
tsMap[treePart] = uniqueRouteStack[TCtx](append(tsMap[treePart], tsMap[0]...))
|
||||
}
|
||||
// sort tree slices with the positions
|
||||
slc := tsMap[treePart]
|
||||
sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
|
||||
}
|
||||
app.treeStack[m] = tsMap
|
||||
}
|
||||
app.routesRefreshed = false
|
||||
|
||||
|
|
|
@ -600,7 +600,7 @@ func Benchmark_Router_Next(b *testing.B) {
|
|||
var res bool
|
||||
var err error
|
||||
|
||||
c := app.AcquireCtx(request).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
c := app.AcquireCtx(request).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
|
@ -656,6 +656,50 @@ func Benchmark_Router_Next_Default_Parallel(b *testing.B) {
|
|||
})
|
||||
}
|
||||
|
||||
// go test -v ./... -run=^$ -bench=Benchmark_Router_Next_Default_Immutable -benchmem -count=4
|
||||
func Benchmark_Router_Next_Default_Immutable(b *testing.B) {
|
||||
app := New(Config{Immutable: true})
|
||||
app.Get("/", func(_ Ctx) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
fctx := &fasthttp.RequestCtx{}
|
||||
fctx.Request.Header.SetMethod(MethodGet)
|
||||
fctx.Request.SetRequestURI("/")
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
h(fctx)
|
||||
}
|
||||
}
|
||||
|
||||
// go test -benchmem -run=^$ -bench ^Benchmark_Router_Next_Default_Parallel_Immutable$ github.com/gofiber/fiber/v3 -count=1
|
||||
func Benchmark_Router_Next_Default_Parallel_Immutable(b *testing.B) {
|
||||
app := New(Config{Immutable: true})
|
||||
app.Get("/", func(_ Ctx) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
fctx := &fasthttp.RequestCtx{}
|
||||
fctx.Request.Header.SetMethod(MethodGet)
|
||||
fctx.Request.SetRequestURI("/")
|
||||
|
||||
for pb.Next() {
|
||||
h(fctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// go test -v ./... -run=^$ -bench=Benchmark_Route_Match -benchmem -count=4
|
||||
func Benchmark_Route_Match(b *testing.B) {
|
||||
var match bool
|
||||
|
@ -825,7 +869,7 @@ func Benchmark_Router_Github_API(b *testing.B) {
|
|||
for n := 0; n < b.N; n++ {
|
||||
c.URI().SetPath(routesFixture.TestRoutes[i].Path)
|
||||
|
||||
ctx := app.AcquireCtx(c).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed
|
||||
ctx := app.AcquireCtx(c).(*DefaultCtx) //nolint:errcheck,forcetypeassert // not needed
|
||||
|
||||
match, err = app.next(ctx)
|
||||
app.ReleaseCtx(ctx)
|
||||
|
|
Loading…
Reference in New Issue