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'
|
- title: '🧹 Updates'
|
||||||
labels:
|
labels:
|
||||||
- '🧹 Updates'
|
- '🧹 Updates'
|
||||||
|
- '⚡️ Performance'
|
||||||
- title: '🐛 Fixes'
|
- title: '🐛 Fixes'
|
||||||
labels:
|
labels:
|
||||||
- '☢️ Bug'
|
- '☢️ Bug'
|
||||||
|
@ -48,6 +49,7 @@ version-resolver:
|
||||||
- '☢️ Bug'
|
- '☢️ Bug'
|
||||||
- '🤖 Dependencies'
|
- '🤖 Dependencies'
|
||||||
- '🧹 Updates'
|
- '🧹 Updates'
|
||||||
|
- '⚡️ Performance'
|
||||||
default: patch
|
default: patch
|
||||||
template: |
|
template: |
|
||||||
$CHANGES
|
$CHANGES
|
||||||
|
|
|
@ -12,6 +12,7 @@ changelog:
|
||||||
- title: '🧹 Updates'
|
- title: '🧹 Updates'
|
||||||
labels:
|
labels:
|
||||||
- '🧹 Updates'
|
- '🧹 Updates'
|
||||||
|
- '⚡️ Performance'
|
||||||
- title: '🐛 Bug Fixes'
|
- title: '🐛 Bug Fixes'
|
||||||
labels:
|
labels:
|
||||||
- '☢️ Bug'
|
- '☢️ Bug'
|
||||||
|
|
|
@ -3,11 +3,11 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- main
|
- main
|
||||||
paths-ignore:
|
paths:
|
||||||
- "**/*.md"
|
- "**.go"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths:
|
||||||
- "**/*.md"
|
- "**.go"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
# deployments permission to deploy GitHub pages website
|
# deployments permission to deploy GitHub pages website
|
||||||
|
|
|
@ -37,4 +37,7 @@ jobs:
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
# NOTE: Keep this in sync with the version from .golangci.yml
|
# 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
|
- name: Upload coverage reports to Codecov
|
||||||
if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.23.x' }}
|
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:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
file: ./coverage.txt
|
file: ./coverage.txt
|
||||||
|
|
|
@ -7,7 +7,6 @@ run:
|
||||||
|
|
||||||
output:
|
output:
|
||||||
sort-results: true
|
sort-results: true
|
||||||
uniq-by-line: false
|
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
depguard:
|
depguard:
|
||||||
|
@ -187,7 +186,7 @@ linters-settings:
|
||||||
- name: unchecked-type-assertion
|
- name: unchecked-type-assertion
|
||||||
disabled: true # TODO: Do not disable
|
disabled: true # TODO: Do not disable
|
||||||
- name: unhandled-error
|
- name: unhandled-error
|
||||||
arguments: ['bytes\.Buffer\.Write']
|
disabled: true
|
||||||
|
|
||||||
stylecheck:
|
stylecheck:
|
||||||
checks:
|
checks:
|
||||||
|
@ -250,7 +249,10 @@ issues:
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
exclude-dirs:
|
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:
|
exclude-rules:
|
||||||
- linters:
|
- linters:
|
||||||
- err113
|
- err113
|
||||||
|
@ -263,7 +265,10 @@ issues:
|
||||||
linters:
|
linters:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- err113
|
- err113
|
||||||
# fix: true
|
- source: 'fmt.Fprintf?'
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
- revive
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
|
@ -358,7 +363,6 @@ linters:
|
||||||
- stylecheck
|
- stylecheck
|
||||||
# - tagalign # TODO: Enable
|
# - tagalign # TODO: Enable
|
||||||
- tagliatelle
|
- tagliatelle
|
||||||
- tenv
|
|
||||||
- testableexamples
|
- testableexamples
|
||||||
- testifylint
|
- testifylint
|
||||||
# - testpackage # TODO: Enable
|
# - testpackage # TODO: Enable
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -35,7 +35,7 @@ markdown:
|
||||||
## lint: 🚨 Run lint checks
|
## lint: 🚨 Run lint checks
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 run ./...
|
golangci-lint run
|
||||||
|
|
||||||
## test: 🚦 Execute all tests
|
## test: 🚦 Execute all tests
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
|
|
@ -19,17 +19,47 @@ a jitter is a way to break synchronization across the client and avoid collision
|
||||||
## Signatures
|
## Signatures
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func NewExponentialBackoff(config ...Config) *ExponentialBackoff
|
func NewExponentialBackoff(config ...retry.Config) *retry.ExponentialBackoff
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Firstly, import the addon from Fiber,
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3/addon/retry"
|
"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
|
## Default Config
|
||||||
|
@ -75,11 +105,6 @@ type Config struct {
|
||||||
//
|
//
|
||||||
// Optional. Default: 10
|
// Optional. Default: 10
|
||||||
MaxRetryCount int
|
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
|
// Route stack divided by HTTP methods
|
||||||
stack [][]*Route[TCtx]
|
stack [][]*Route[TCtx]
|
||||||
// Route stack divided by HTTP methods and route prefixes
|
// Route stack divided by HTTP methods and route prefixes
|
||||||
treeStack []map[string][]*Route[TCtx]
|
treeStack []map[int][]*Route[TCtx]
|
||||||
// custom binders
|
// custom binders
|
||||||
customBinders []CustomBinder
|
customBinders []CustomBinder
|
||||||
// customConstraints is a list of external constraints
|
// customConstraints is a list of external constraints
|
||||||
|
@ -456,17 +456,29 @@ const (
|
||||||
DefaultWriteBufferSize = 4096
|
DefaultWriteBufferSize = 4096
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
methodGet = iota
|
||||||
|
methodHead
|
||||||
|
methodPost
|
||||||
|
methodPut
|
||||||
|
methodDelete
|
||||||
|
methodConnect
|
||||||
|
methodOptions
|
||||||
|
methodTrace
|
||||||
|
methodPatch
|
||||||
|
)
|
||||||
|
|
||||||
// HTTP methods enabled by default
|
// HTTP methods enabled by default
|
||||||
var DefaultMethods = []string{
|
var DefaultMethods = []string{
|
||||||
MethodGet,
|
methodGet: MethodGet,
|
||||||
MethodHead,
|
methodHead: MethodHead,
|
||||||
MethodPost,
|
methodPost: MethodPost,
|
||||||
MethodPut,
|
methodPut: MethodPut,
|
||||||
MethodDelete,
|
methodDelete: MethodDelete,
|
||||||
MethodConnect,
|
methodConnect: MethodConnect,
|
||||||
MethodOptions,
|
methodOptions: MethodOptions,
|
||||||
MethodTrace,
|
methodTrace: MethodTrace,
|
||||||
MethodPatch,
|
methodPatch: MethodPatch,
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultErrorHandler that process return errors from handlers
|
// DefaultErrorHandler that process return errors from handlers
|
||||||
|
@ -621,7 +633,7 @@ func newApp[TCtx CtxGeneric[TCtx]](config ...Config[TCtx]) *App[TCtx] {
|
||||||
|
|
||||||
// Create router stack
|
// Create router stack
|
||||||
app.stack = make([][]*Route[TCtx], len(app.config.RequestMethods))
|
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
|
// Override colors
|
||||||
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
||||||
|
@ -1048,7 +1060,7 @@ func (app *App[TCtx]) Test(req *http.Request, config ...TestConfig) (*http.Respo
|
||||||
select {
|
select {
|
||||||
case err = <-channel:
|
case err = <-channel:
|
||||||
case <-time.After(cfg.Timeout):
|
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 {
|
if cfg.FailOnTimeout {
|
||||||
return nil, os.ErrDeadlineExceeded
|
return nil, os.ErrDeadlineExceeded
|
||||||
}
|
}
|
||||||
|
|
|
@ -403,7 +403,7 @@ func Test_App_serverErrorHandler_Internal_Error(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
app := New()
|
||||||
msg := "test err"
|
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))
|
app.serverErrorHandler(c.fasthttp, errors.New(msg))
|
||||||
require.Equal(t, string(c.fasthttp.Response.Body()), 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) {
|
func Test_App_serverErrorHandler_Network_Error(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
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{
|
app.serverErrorHandler(c.fasthttp, &net.DNSError{
|
||||||
Err: "test error",
|
Err: "test error",
|
||||||
|
|
|
@ -1378,7 +1378,7 @@ func Benchmark_Bind_URI(b *testing.B) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
app := New()
|
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{
|
c.route = &Route{
|
||||||
Params: []string{
|
Params: []string{
|
||||||
|
@ -1415,7 +1415,7 @@ func Benchmark_Bind_URI_Map(b *testing.B) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
app := New()
|
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{
|
c.route = &Route{
|
||||||
Params: []string{
|
Params: []string{
|
||||||
|
|
|
@ -28,7 +28,7 @@ Fiber provides several default binders out of the box:
|
||||||
|
|
||||||
### Binding into a Struct
|
### 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
|
```go
|
||||||
// Field names must start with an uppercase letter
|
// Field names must start with an uppercase letter
|
||||||
|
|
|
@ -58,19 +58,19 @@ func Benchmark_FormBinder_Bind(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
binder := &QueryBinding{
|
binder := &FormBinding{
|
||||||
EnableSplitting: true,
|
EnableSplitting: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Name string `query:"name"`
|
Name string `form:"name"`
|
||||||
Posts []string `query:"posts"`
|
Posts []string `form:"posts"`
|
||||||
Age int `query:"age"`
|
Age int `form:"age"`
|
||||||
}
|
}
|
||||||
var user User
|
var user User
|
||||||
|
|
||||||
req := fasthttp.AcquireRequest()
|
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")
|
req.Header.SetContentType("application/x-www-form-urlencoded")
|
||||||
|
|
||||||
b.ResetTimer()
|
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...)
|
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 {
|
func parseToStruct(aliasTag string, out any, data map[string][]string, files ...map[string][]*multipart.FileHeader) error {
|
||||||
// Get decoder from pool
|
// Get decoder from pool
|
||||||
schemaDecoder := decoderPoolMap[aliasTag].Get().(*schema.Decoder) //nolint:errcheck,forcetypeassert // not needed
|
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()
|
t.Helper()
|
||||||
|
|
||||||
app, ln, start := createHelperServer(t)
|
app, ln, start := createHelperServer(t)
|
||||||
|
|
|
@ -199,7 +199,7 @@ func parserRequestBody(c *Client, req *Request) error {
|
||||||
case filesBody:
|
case filesBody:
|
||||||
return parserRequestBodyFile(req)
|
return parserRequestBodyFile(req)
|
||||||
case rawBody:
|
case rawBody:
|
||||||
if body, ok := req.body.([]byte); ok {
|
if body, ok := req.body.([]byte); ok { //nolint:revive // ignore simplicity
|
||||||
req.RawRequest.SetBody(body)
|
req.RawRequest.SetBody(body)
|
||||||
} else {
|
} else {
|
||||||
return ErrBodyType
|
return ErrBodyType
|
||||||
|
|
|
@ -298,11 +298,12 @@ func (r *Request) Cookie(key string) string {
|
||||||
// Use maps.Collect() to gather them into a map if needed.
|
// Use maps.Collect() to gather them into a map if needed.
|
||||||
func (r *Request) Cookies() iter.Seq2[string, string] {
|
func (r *Request) Cookies() iter.Seq2[string, string] {
|
||||||
return func(yield func(string, string) bool) {
|
return func(yield func(string, string) bool) {
|
||||||
r.cookies.VisitAll(func(key, val string) {
|
for k, v := range *r.cookies {
|
||||||
if !yield(key, val) {
|
res := yield(k, v)
|
||||||
|
if !res {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,11 +344,11 @@ func (r *Request) PathParam(key string) string {
|
||||||
// Use maps.Collect() to gather them into a map if needed.
|
// Use maps.Collect() to gather them into a map if needed.
|
||||||
func (r *Request) PathParams() iter.Seq2[string, string] {
|
func (r *Request) PathParams() iter.Seq2[string, string] {
|
||||||
return func(yield func(string, string) bool) {
|
return func(yield func(string, string) bool) {
|
||||||
r.path.VisitAll(func(key, val string) {
|
for k, v := range *r.path {
|
||||||
if !yield(key, val) {
|
if !yield(k, v) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//nolint:goconst // Much easier to just ignore memory leaks in tests
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -451,6 +452,14 @@ func Test_Request_Cookies(t *testing.T) {
|
||||||
require.Equal(t, "bar", cookies["foo"])
|
require.Equal(t, "bar", cookies["foo"])
|
||||||
require.Equal(t, "foo", cookies["bar"])
|
require.Equal(t, "foo", cookies["bar"])
|
||||||
|
|
||||||
|
require.NotPanics(t, func() {
|
||||||
|
for _, v := range req.Cookies() {
|
||||||
|
if v == "bar" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
require.Len(t, cookies, 2)
|
require.Len(t, cookies, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,6 +573,14 @@ func Test_Request_PathParams(t *testing.T) {
|
||||||
require.Equal(t, "foo", pathParams["bar"])
|
require.Equal(t, "foo", pathParams["bar"])
|
||||||
|
|
||||||
require.Len(t, pathParams, 2)
|
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) {
|
func Benchmark_Request_PathParams(b *testing.B) {
|
||||||
|
@ -1579,7 +1596,7 @@ func Test_SetValWithStruct(t *testing.T) {
|
||||||
|
|
||||||
require.True(t, func() bool {
|
require.True(t, func() bool {
|
||||||
for _, v := range p.PeekMulti("TSlice") {
|
for _, v := range p.PeekMulti("TSlice") {
|
||||||
if string(v) == "bar" { //nolint:goconst // test
|
if string(v) == "bar" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
89
ctx.go
89
ctx.go
|
@ -33,8 +33,11 @@ const (
|
||||||
schemeHTTPS = "https"
|
schemeHTTPS = "https"
|
||||||
)
|
)
|
||||||
|
|
||||||
// maxParams defines the maximum number of parameters per route.
|
const (
|
||||||
const maxParams = 30
|
// 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
|
// The contextKey type is unexported to prevent collisions with context keys defined in
|
||||||
// other packages.
|
// other packages.
|
||||||
|
@ -55,20 +58,19 @@ type DefaultCtx struct {
|
||||||
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
||||||
bind *Bind // Default bind reference
|
bind *Bind // Default bind reference
|
||||||
redirect *Redirect // Default redirect reference
|
redirect *Redirect // Default redirect reference
|
||||||
|
req *DefaultReq // Default request api reference
|
||||||
|
res *DefaultRes // Default response api reference
|
||||||
values [maxParams]string // Route parameter values
|
values [maxParams]string // Route parameter values
|
||||||
viewBindMap sync.Map // Default view map to bind template engine
|
viewBindMap sync.Map // Default view map to bind template engine
|
||||||
method string // HTTP method
|
|
||||||
baseURI string // HTTP base uri
|
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
|
pathOriginal string // Original HTTP path
|
||||||
pathBuffer []byte // HTTP path buffer
|
|
||||||
detectionPathBuffer []byte // HTTP detectionPath buffer
|
|
||||||
flashMessages redirectionMsgs // Flash messages
|
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
|
indexRoute int // Index of the current route
|
||||||
indexHandler int // Index of the current handler
|
indexHandler int // Index of the current handler
|
||||||
methodINT int // HTTP method INT equivalent
|
methodInt int // HTTP method INT equivalent
|
||||||
matched bool // Non use route matched
|
matched bool // Non use route matched
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1006,19 +1008,17 @@ func (c *DefaultCtx) Location(path string) {
|
||||||
func (c *DefaultCtx) Method(override ...string) string {
|
func (c *DefaultCtx) Method(override ...string) string {
|
||||||
if len(override) == 0 {
|
if len(override) == 0 {
|
||||||
// Nothing to override, just return current method from context
|
// Nothing to override, just return current method from context
|
||||||
return c.method
|
return c.app.method(c.methodInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
method := utils.ToUpper(override[0])
|
method := utils.ToUpper(override[0])
|
||||||
mINT := c.app.methodInt(method)
|
methodInt := c.app.methodInt(method)
|
||||||
if mINT == -1 {
|
if methodInt == -1 {
|
||||||
// Provided override does not valid HTTP method, no override, return current method
|
// Provided override does not valid HTTP method, no override, return current method
|
||||||
return c.method
|
return c.app.method(c.methodInt)
|
||||||
}
|
}
|
||||||
|
c.methodInt = methodInt
|
||||||
c.method = method
|
return method
|
||||||
c.methodINT = mINT
|
|
||||||
return c.method
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultipartForm parse form entries from binary.
|
// 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.
|
// Path returns the path part of the request URL.
|
||||||
// Optionally, you could override the path.
|
// 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 {
|
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
|
// Set new path to context
|
||||||
c.pathOriginal = override[0]
|
c.pathOriginal = override[0]
|
||||||
|
|
||||||
|
@ -1136,7 +1137,7 @@ func (c *DefaultCtx) Path(override ...string) string {
|
||||||
// Prettify path
|
// Prettify path
|
||||||
c.configDependentPaths()
|
c.configDependentPaths()
|
||||||
}
|
}
|
||||||
return c.path
|
return c.app.getString(c.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheme contains the request protocol string: http or https for TLS requests.
|
// 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 {
|
for key, val := range params {
|
||||||
isSame := key == segment.ParamName || (!c.app.config.CaseSensitive && utils.EqualFold(key, segment.ParamName))
|
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 {
|
if isSame || isGreedy {
|
||||||
_, err := buf.WriteString(utils.ToString(val))
|
_, err := buf.WriteString(utils.ToString(val))
|
||||||
if err != nil {
|
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.
|
// Route returns the matched Route struct.
|
||||||
func (c *DefaultCtx) Route() *Route {
|
func (c *DefaultCtx) Route() *Route {
|
||||||
if c.route == nil {
|
if c.route == nil {
|
||||||
|
@ -1475,7 +1488,7 @@ func (c *DefaultCtx) Route() *Route {
|
||||||
return &Route{
|
return &Route{
|
||||||
path: c.pathOriginal,
|
path: c.pathOriginal,
|
||||||
Path: c.pathOriginal,
|
Path: c.pathOriginal,
|
||||||
Method: c.method,
|
Method: c.Method(),
|
||||||
Handlers: make([]Handler, 0),
|
Handlers: make([]Handler, 0),
|
||||||
Params: make([]string, 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,
|
// configDependentPaths set paths for route recognition and prepared paths for the user,
|
||||||
// here the features for caseSensitive, decoded paths, strict paths are evaluated
|
// here the features for caseSensitive, decoded paths, strict paths are evaluated
|
||||||
func (c *DefaultCtx) configDependentPaths() {
|
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 UnescapePath enabled, we decode the path and save it for the framework user
|
||||||
if c.app.config.UnescapePath {
|
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
|
// another path is specified which is for routing recognition only
|
||||||
// use the path that was changed by the previous configuration flags
|
// 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 CaseSensitive is disabled, we lowercase the original path
|
||||||
if !c.app.config.CaseSensitive {
|
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 StrictRouting is disabled, we strip all trailing slashes
|
||||||
if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' {
|
if !c.app.config.StrictRouting && len(c.detectionPath) > 1 && c.detectionPath[len(c.detectionPath)-1] == '/' {
|
||||||
c.detectionPathBuffer = utils.TrimRight(c.detectionPathBuffer, '/')
|
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,
|
// 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
|
// since the first three characters area select a list of routes
|
||||||
c.treePath = c.treePath[0:0]
|
c.treePathHash = 0
|
||||||
const maxDetectionPaths = 3
|
|
||||||
if len(c.detectionPath) >= maxDetectionPaths {
|
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
|
// Set paths
|
||||||
c.pathOriginal = c.app.getString(fctx.URI().PathOriginal())
|
c.pathOriginal = c.app.getString(fctx.URI().PathOriginal())
|
||||||
// Set method
|
// Set method
|
||||||
c.method = c.app.getString(fctx.Request.Header.Method())
|
c.methodInt = c.app.methodInt(utils.UnsafeString(fctx.Request.Header.Method()))
|
||||||
c.methodINT = c.app.methodInt(c.method)
|
|
||||||
// Attach *fasthttp.RequestCtx to ctx
|
// Attach *fasthttp.RequestCtx to ctx
|
||||||
c.fasthttp = fctx
|
c.fasthttp = fctx
|
||||||
// reset base uri
|
// reset base uri
|
||||||
|
@ -1941,20 +1952,20 @@ func (c *DefaultCtx) getBody() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods to use with next stack.
|
// Methods to use with next stack.
|
||||||
func (c *DefaultCtx) getMethodINT() int {
|
func (c *DefaultCtx) getMethodInt() int {
|
||||||
return c.methodINT
|
return c.methodInt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultCtx) getIndexRoute() int {
|
func (c *DefaultCtx) getIndexRoute() int {
|
||||||
return c.indexRoute
|
return c.indexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultCtx) getTreePath() string {
|
func (c *DefaultCtx) getTreePathHash() int {
|
||||||
return c.treePath
|
return c.treePathHash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultCtx) getDetectionPath() string {
|
func (c *DefaultCtx) getDetectionPath() string {
|
||||||
return c.detectionPath
|
return c.app.getString(c.detectionPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultCtx) getPathOriginal() string {
|
func (c *DefaultCtx) getPathOriginal() string {
|
||||||
|
|
|
@ -339,9 +339,9 @@ type CtxGeneric[T any] interface {
|
||||||
release()
|
release()
|
||||||
getBody() []byte
|
getBody() []byte
|
||||||
// Methods to use with next stack.
|
// Methods to use with next stack.
|
||||||
getMethodINT() int
|
getMethodInt() int
|
||||||
getIndexRoute() int
|
getIndexRoute() int
|
||||||
getTreePath() string
|
getTreePathHash() int
|
||||||
getDetectionPath() string
|
getDetectionPath() string
|
||||||
getPathOriginal() string
|
getPathOriginal() string
|
||||||
getValues() *[maxParams]string
|
getValues() *[maxParams]string
|
||||||
|
@ -349,11 +349,47 @@ type CtxGeneric[T any] interface {
|
||||||
setIndexHandler(handler int)
|
setIndexHandler(handler int)
|
||||||
setIndexRoute(route int)
|
setIndexRoute(route int)
|
||||||
setMatched(matched bool)
|
setMatched(matched bool)
|
||||||
setRoute(route *Route[T])
|
setRoute(route *Route)
|
||||||
// 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.
|
func NewDefaultCtx(app *App) *DefaultCtx {
|
||||||
Drop() error
|
// return ctx
|
||||||
// End immediately flushes the current response and closes the underlying connection.
|
ctx := &DefaultCtx{
|
||||||
End() error
|
// 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")
|
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.Accepts())
|
require.Equal(t, "", c.Req().Accepts())
|
||||||
require.Equal(t, ".xml", c.Accepts(".xml"))
|
require.Equal(t, ".xml", c.Accepts(".xml"))
|
||||||
require.Equal(t, "", c.Accepts(".john"))
|
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")
|
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")
|
c.Request().Header.Set(HeaderAccept, "text/*, application/json")
|
||||||
require.Equal(t, "html", c.Accepts("html"))
|
require.Equal(t, "html", c.Accepts("html"))
|
||||||
require.Equal(t, "text/html", c.Accepts("text/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, "application/json", c.Accepts("application/json"))
|
||||||
require.Equal(t, "", c.Accepts("image/png"))
|
require.Equal(t, "", c.Accepts("image/png"))
|
||||||
require.Equal(t, "", c.Accepts("png"))
|
require.Equal(t, "", c.Accepts("png"))
|
||||||
|
|
||||||
c.Request().Header.Set(HeaderAccept, "text/html, application/json")
|
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, "*/*")
|
c.Request().Header.Set(HeaderAccept, "*/*")
|
||||||
require.Equal(t, "html", c.Accepts("html"))
|
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
|
// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsCharsets -benchmem -count=4
|
||||||
func Benchmark_Ctx_AcceptsCharsets(b *testing.B) {
|
func Benchmark_Ctx_AcceptsCharsets(b *testing.B) {
|
||||||
app := New()
|
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")
|
c.Request().Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5")
|
||||||
var res string
|
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
|
// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsEncodings -benchmem -count=4
|
||||||
func Benchmark_Ctx_AcceptsEncodings(b *testing.B) {
|
func Benchmark_Ctx_AcceptsEncodings(b *testing.B) {
|
||||||
app := New()
|
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")
|
c.Request().Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5")
|
||||||
var res string
|
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
|
// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsLanguages -benchmem -count=4
|
||||||
func Benchmark_Ctx_AcceptsLanguages(b *testing.B) {
|
func Benchmark_Ctx_AcceptsLanguages(b *testing.B) {
|
||||||
app := New()
|
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")
|
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
|
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
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Append -benchmem -count=4
|
||||||
func Benchmark_Ctx_Append(b *testing.B) {
|
func Benchmark_Ctx_Append(b *testing.B) {
|
||||||
app := New()
|
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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -337,7 +337,7 @@ func Test_Ctx_Attachment(t *testing.T) {
|
||||||
// go test -v -run=^$ -bench=Benchmark_Ctx_Attachment -benchmem -count=4
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Attachment -benchmem -count=4
|
||||||
func Benchmark_Ctx_Attachment(b *testing.B) {
|
func Benchmark_Ctx_Attachment(b *testing.B) {
|
||||||
app := New()
|
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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -363,7 +363,7 @@ func Test_Ctx_BaseURL(t *testing.T) {
|
||||||
// go test -v -run=^$ -bench=Benchmark_Ctx_BaseURL -benchmem
|
// go test -v -run=^$ -bench=Benchmark_Ctx_BaseURL -benchmem
|
||||||
func Benchmark_Ctx_BaseURL(b *testing.B) {
|
func Benchmark_Ctx_BaseURL(b *testing.B) {
|
||||||
app := New()
|
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().SetHost("google.com:1337")
|
||||||
c.Request().URI().SetPath("/haha/oke/lol")
|
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) {
|
func Test_Ctx_Body(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
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"))
|
c.Request().SetBody([]byte("john=doe"))
|
||||||
require.Equal(t, []byte("john=doe"), c.Body())
|
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) {
|
func Test_Ctx_BodyRaw(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
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"))
|
c.Request().SetBodyRaw([]byte("john=doe"))
|
||||||
require.Equal(t, []byte("john=doe"), c.BodyRaw())
|
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) {
|
func Test_Ctx_BodyRaw_Immutable(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New(Config{Immutable: true})
|
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"))
|
c.Request().SetBodyRaw([]byte("john=doe"))
|
||||||
require.Equal(t, []byte("john=doe"), c.BodyRaw())
|
require.Equal(t, []byte("john=doe"), c.BodyRaw())
|
||||||
|
@ -411,7 +411,7 @@ func Benchmark_Ctx_Body(b *testing.B) {
|
||||||
const input = "john=doe"
|
const input = "john=doe"
|
||||||
|
|
||||||
app := New()
|
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))
|
c.Request().SetBody([]byte(input))
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
@ -428,7 +428,7 @@ func Benchmark_Ctx_BodyRaw(b *testing.B) {
|
||||||
const input = "john=doe"
|
const input = "john=doe"
|
||||||
|
|
||||||
app := New()
|
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))
|
c.Request().SetBodyRaw([]byte(input))
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
@ -445,7 +445,7 @@ func Benchmark_Ctx_BodyRaw_Immutable(b *testing.B) {
|
||||||
const input = "john=doe"
|
const input = "john=doe"
|
||||||
|
|
||||||
app := New(Config{Immutable: true})
|
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))
|
c.Request().SetBodyRaw([]byte(input))
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
@ -462,7 +462,7 @@ func Test_Ctx_Body_Immutable(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
app := New()
|
||||||
app.config.Immutable = true
|
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"))
|
c.Request().SetBody([]byte("john=doe"))
|
||||||
require.Equal(t, []byte("john=doe"), c.Body())
|
require.Equal(t, []byte("john=doe"), c.Body())
|
||||||
|
@ -474,7 +474,7 @@ func Benchmark_Ctx_Body_Immutable(b *testing.B) {
|
||||||
|
|
||||||
app := New()
|
app := New()
|
||||||
app.config.Immutable = true
|
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))
|
c.Request().SetBody([]byte(input))
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
@ -527,7 +527,7 @@ func Test_Ctx_Body_With_Compression(t *testing.T) {
|
||||||
t.Run(tCase.name, func(t *testing.T) {
|
t.Run(tCase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
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)
|
c.Request().Header.Set("Content-Encoding", tCase.contentEncoding)
|
||||||
|
|
||||||
if strings.Contains(tCase.contentEncoding, "gzip") {
|
if strings.Contains(tCase.contentEncoding, "gzip") {
|
||||||
|
@ -720,7 +720,7 @@ func Test_Ctx_Body_With_Compression_Immutable(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
app := New()
|
||||||
app.config.Immutable = true
|
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)
|
c.Request().Header.Set("Content-Encoding", tCase.contentEncoding)
|
||||||
|
|
||||||
if strings.Contains(tCase.contentEncoding, "gzip") {
|
if strings.Contains(tCase.contentEncoding, "gzip") {
|
||||||
|
@ -897,7 +897,7 @@ func Test_Ctx_Context(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
testKey := struct{}{}
|
testKey := struct{}{}
|
||||||
testValue := "Test Value"
|
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))
|
require.Equal(t, testValue, ctx.Value(testKey))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -910,7 +910,7 @@ func Test_Ctx_SetContext(t *testing.T) {
|
||||||
|
|
||||||
testKey := struct{}{}
|
testKey := struct{}{}
|
||||||
testValue := "Test Value"
|
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)
|
c.SetContext(ctx)
|
||||||
require.Equal(t, testValue, c.Context().Value(testKey))
|
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"))
|
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)
|
c.SetContext(ctx)
|
||||||
|
|
||||||
return c.Status(StatusOK).SendString(fmt.Sprintf("resp_%s_returned", input))
|
return c.Status(StatusOK).SendString(fmt.Sprintf("resp_%s_returned", input))
|
||||||
|
@ -968,52 +968,52 @@ func Test_Ctx_Cookie(t *testing.T) {
|
||||||
Expires: expire,
|
Expires: expire,
|
||||||
// SameSite: CookieSameSiteStrictMode, // default is "lax"
|
// SameSite: CookieSameSiteStrictMode, // default is "lax"
|
||||||
}
|
}
|
||||||
c.Cookie(cookie)
|
c.Res().Cookie(cookie)
|
||||||
expect := "username=john; expires=" + httpdate + "; path=/; SameSite=Lax"
|
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=/"
|
expect = "username=john; expires=" + httpdate + "; path=/"
|
||||||
cookie.SameSite = CookieSameSiteDisabled
|
cookie.SameSite = CookieSameSiteDisabled
|
||||||
c.Cookie(cookie)
|
c.Res().Cookie(cookie)
|
||||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||||
|
|
||||||
expect = "username=john; expires=" + httpdate + "; path=/; SameSite=Strict"
|
expect = "username=john; expires=" + httpdate + "; path=/; SameSite=Strict"
|
||||||
cookie.SameSite = CookieSameSiteStrictMode
|
cookie.SameSite = CookieSameSiteStrictMode
|
||||||
c.Cookie(cookie)
|
c.Res().Cookie(cookie)
|
||||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||||
|
|
||||||
expect = "username=john; expires=" + httpdate + "; path=/; secure; SameSite=None"
|
expect = "username=john; expires=" + httpdate + "; path=/; secure; SameSite=None"
|
||||||
cookie.Secure = true
|
cookie.Secure = true
|
||||||
cookie.SameSite = CookieSameSiteNoneMode
|
cookie.SameSite = CookieSameSiteNoneMode
|
||||||
c.Cookie(cookie)
|
c.Res().Cookie(cookie)
|
||||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||||
|
|
||||||
expect = "username=john; path=/; secure; SameSite=None"
|
expect = "username=john; path=/; secure; SameSite=None"
|
||||||
// should remove expires and max-age headers
|
// should remove expires and max-age headers
|
||||||
cookie.SessionOnly = true
|
cookie.SessionOnly = true
|
||||||
cookie.Expires = expire
|
cookie.Expires = expire
|
||||||
cookie.MaxAge = 10000
|
cookie.MaxAge = 10000
|
||||||
c.Cookie(cookie)
|
c.Res().Cookie(cookie)
|
||||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||||
|
|
||||||
expect = "username=john; path=/; secure; SameSite=None"
|
expect = "username=john; path=/; secure; SameSite=None"
|
||||||
// should remove expires and max-age headers when no expire and no MaxAge (default time)
|
// should remove expires and max-age headers when no expire and no MaxAge (default time)
|
||||||
cookie.SessionOnly = false
|
cookie.SessionOnly = false
|
||||||
cookie.Expires = time.Time{}
|
cookie.Expires = time.Time{}
|
||||||
cookie.MaxAge = 0
|
cookie.MaxAge = 0
|
||||||
c.Cookie(cookie)
|
c.Res().Cookie(cookie)
|
||||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||||
|
|
||||||
expect = "username=john; path=/; secure; SameSite=None; Partitioned"
|
expect = "username=john; path=/; secure; SameSite=None; Partitioned"
|
||||||
cookie.Partitioned = true
|
cookie.Partitioned = true
|
||||||
c.Cookie(cookie)
|
c.Res().Cookie(cookie)
|
||||||
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
|
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
|
||||||
}
|
}
|
||||||
|
|
||||||
// go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4
|
||||||
func Benchmark_Ctx_Cookie(b *testing.B) {
|
func Benchmark_Ctx_Cookie(b *testing.B) {
|
||||||
app := New()
|
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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -1033,8 +1033,8 @@ func Test_Ctx_Cookies(t *testing.T) {
|
||||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
|
||||||
c.Request().Header.Set("Cookie", "john=doe")
|
c.Request().Header.Set("Cookie", "john=doe")
|
||||||
require.Equal(t, "doe", c.Cookies("john"))
|
require.Equal(t, "doe", c.Req().Cookies("john"))
|
||||||
require.Equal(t, "default", c.Cookies("unknown", "default"))
|
require.Equal(t, "default", c.Req().Cookies("unknown", "default"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// go test -run Test_Ctx_Format
|
// 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`)
|
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", accepted)
|
||||||
require.Equal(t, "application/xhtml+xml", c.GetRespHeader(HeaderContentType))
|
require.Equal(t, "application/xhtml+xml", c.GetRespHeader(HeaderContentType))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode())
|
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", accepted)
|
||||||
require.Equal(t, "foo/bar;a=b", c.GetRespHeader(HeaderContentType))
|
require.Equal(t, "foo/bar;a=b", c.GetRespHeader(HeaderContentType))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -1165,7 +1165,7 @@ func Test_Ctx_AutoFormat(t *testing.T) {
|
||||||
require.Equal(t, "Hello, World!", string(c.Response().Body()))
|
require.Equal(t, "Hello, World!", string(c.Response().Body()))
|
||||||
|
|
||||||
c.Request().Header.Set(HeaderAccept, MIMETextHTML)
|
c.Request().Header.Set(HeaderAccept, MIMETextHTML)
|
||||||
err = c.AutoFormat("Hello, World!")
|
err = c.Res().AutoFormat("Hello, World!")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "<p>Hello, World!</p>", string(c.Response().Body()))
|
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()))
|
require.Equal(t, `"Hello, World!"`, string(c.Response().Body()))
|
||||||
|
|
||||||
c.Request().Header.Set(HeaderAccept, MIMETextPlain)
|
c.Request().Header.Set(HeaderAccept, MIMETextPlain)
|
||||||
err = c.AutoFormat(complex(1, 1))
|
err = c.Res().AutoFormat(complex(1, 1))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "(1+1i)", string(c.Response().Body()))
|
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.Run("URI", func(t *testing.T) {
|
||||||
t.Skip("URI is not ready for v3")
|
t.Skip("URI is not ready for v3")
|
||||||
//nolint:gocritic // TODO: uncomment
|
//nolint:gocritic // TODO: uncomment
|
||||||
//t.Parallel()
|
// t.Parallel()
|
||||||
//withValues(t, func(c Ctx, testStruct *TestStruct) error {
|
// withValues(t, func(c Ctx, testStruct *TestStruct) error {
|
||||||
// c.Route().Params = []string{"name", "name2", "class", "class2"}
|
// c.Route().Params = []string{"name", "name2", "class", "class2"}
|
||||||
// c.Params().value = [30]string{"foo", "bar", "111", "222"}
|
// c.Params().value = [30]string{"foo", "bar", "111", "222"}
|
||||||
// return c.Bind().URI(testStruct)
|
// return c.Bind().URI(testStruct)
|
||||||
//})
|
// })
|
||||||
})
|
})
|
||||||
t.Run("ReqHeader", func(t *testing.T) {
|
t.Run("ReqHeader", func(t *testing.T) {
|
||||||
t.Parallel()
|
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
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Params -benchmem -count=4
|
||||||
func Benchmark_Ctx_Params(b *testing.B) {
|
func Benchmark_Ctx_Params(b *testing.B) {
|
||||||
app := New()
|
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{
|
c.route = &Route{
|
||||||
Params: []string{
|
Params: []string{
|
||||||
|
@ -2939,7 +2939,7 @@ func Test_Ctx_SaveFile(t *testing.T) {
|
||||||
app := New()
|
app := New()
|
||||||
|
|
||||||
app.Post("/test", func(c Ctx) error {
|
app.Post("/test", func(c Ctx) error {
|
||||||
fh, err := c.FormFile("file")
|
fh, err := c.Req().FormFile("file")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tempFile, err := os.CreateTemp(os.TempDir(), "test-")
|
tempFile, err := os.CreateTemp(os.TempDir(), "test-")
|
||||||
|
@ -3075,7 +3075,7 @@ func Test_Ctx_ClearCookie(t *testing.T) {
|
||||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
|
||||||
c.Request().Header.Set(HeaderCookie, "john=doe")
|
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="))
|
require.True(t, strings.HasPrefix(string(c.Response().Header.Peek(HeaderSetCookie)), "john=; expires="))
|
||||||
|
|
||||||
c.Request().Header.Set(HeaderCookie, "test1=dummy")
|
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, expect, c.Response().Body())
|
||||||
require.Equal(t, `attachment; filename="Awesome+File%21"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
|
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)))
|
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
|
// test with custom error code
|
||||||
c = app.AcquireCtx(&fasthttp.RequestCtx{})
|
c = app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
err = c.Status(StatusInternalServerError).SendFile("ctx.go")
|
err = c.Res().Status(StatusInternalServerError).SendFile("ctx.go")
|
||||||
// check expectation
|
// check expectation
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectFileContent, c.Response().Body())
|
require.Equal(t, expectFileContent, c.Response().Body())
|
||||||
|
@ -3161,7 +3161,7 @@ func Test_Ctx_SendFile_ContentType(t *testing.T) {
|
||||||
|
|
||||||
// 1) simple case
|
// 1) simple case
|
||||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
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
|
// check expectation
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, StatusOK, c.Response().StatusCode())
|
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) {
|
func Benchmark_Ctx_JSON_Ctype(b *testing.B) {
|
||||||
app := New()
|
app := New()
|
||||||
// TODO: Check extra allocs because of the interface stuff
|
// 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 {
|
type SomeStruct struct {
|
||||||
Name string
|
Name string
|
||||||
Age uint8
|
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, `callback({"Age":20,"Name":"Grame"});`, string(c.Response().Body()))
|
||||||
require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))
|
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",
|
"Name": "Grame",
|
||||||
"Age": 20,
|
"Age": 20,
|
||||||
}, "john")
|
}, "john")
|
||||||
|
@ -3813,7 +3813,7 @@ func Test_Ctx_JSONP(t *testing.T) {
|
||||||
// go test -v -run=^$ -bench=Benchmark_Ctx_JSONP -benchmem -count=4
|
// go test -v -run=^$ -bench=Benchmark_Ctx_JSONP -benchmem -count=4
|
||||||
func Benchmark_Ctx_JSONP(b *testing.B) {
|
func Benchmark_Ctx_JSONP(b *testing.B) {
|
||||||
app := New()
|
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 {
|
type SomeStruct struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -3838,7 +3838,7 @@ func Benchmark_Ctx_JSONP(b *testing.B) {
|
||||||
func Test_Ctx_XML(t *testing.T) {
|
func Test_Ctx_XML(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
app := New()
|
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)))
|
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
|
// go test -run=^$ -bench=Benchmark_Ctx_XML -benchmem -count=4
|
||||||
func Benchmark_Ctx_XML(b *testing.B) {
|
func Benchmark_Ctx_XML(b *testing.B) {
|
||||||
app := New()
|
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 {
|
type SomeStruct struct {
|
||||||
Name string `xml:"Name"`
|
Name string `xml:"Name"`
|
||||||
Age uint8 `xml:"Age"`
|
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
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Links -benchmem -count=4
|
||||||
func Benchmark_Ctx_Links(b *testing.B) {
|
func Benchmark_Ctx_Links(b *testing.B) {
|
||||||
app := New()
|
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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -4006,7 +4006,7 @@ func Test_Ctx_Render(t *testing.T) {
|
||||||
err = c.Render("./.github/testdata/template-non-exists.html", nil)
|
err = c.Render("./.github/testdata/template-non-exists.html", nil)
|
||||||
require.Error(t, err)
|
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)
|
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
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Get_Location_From_Route -benchmem -count=4
|
||||||
func Benchmark_Ctx_Get_Location_From_Route(b *testing.B) {
|
func Benchmark_Ctx_Get_Location_From_Route(b *testing.B) {
|
||||||
app := New()
|
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 {
|
app.Get("/user/:name", func(c Ctx) error {
|
||||||
return c.SendString(c.Params("name"))
|
return c.SendString(c.Params("name"))
|
||||||
|
@ -4578,14 +4578,14 @@ func Test_Ctx_SendStreamWriter(t *testing.T) {
|
||||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
|
||||||
err := c.SendStreamWriter(func(w *bufio.Writer) {
|
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.NoError(t, err)
|
||||||
require.Equal(t, "Don't crash please", string(c.Response().Body()))
|
require.Equal(t, "Don't crash please", string(c.Response().Body()))
|
||||||
|
|
||||||
err = c.SendStreamWriter(func(w *bufio.Writer) {
|
err = c.SendStreamWriter(func(w *bufio.Writer) {
|
||||||
for lineNum := 1; lineNum <= 5; lineNum++ {
|
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 {
|
if err := w.Flush(); err != nil {
|
||||||
t.Errorf("unexpected error: %s", err)
|
t.Errorf("unexpected error: %s", err)
|
||||||
return
|
return
|
||||||
|
@ -4607,7 +4607,7 @@ func Test_Ctx_SendStreamWriter_Interrupted(t *testing.T) {
|
||||||
app.Get("/", func(c Ctx) error {
|
app.Get("/", func(c Ctx) error {
|
||||||
return c.SendStreamWriter(func(w *bufio.Writer) {
|
return c.SendStreamWriter(func(w *bufio.Writer) {
|
||||||
for lineNum := 1; lineNum <= 5; lineNum++ {
|
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 err := w.Flush(); err != nil {
|
||||||
if lineNum < 3 {
|
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
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Type_Charset -benchmem -count=4
|
||||||
func Benchmark_Ctx_Type_Charset(b *testing.B) {
|
func Benchmark_Ctx_Type_Charset(b *testing.B) {
|
||||||
app := New()
|
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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -4753,7 +4753,7 @@ func Test_Ctx_Vary(t *testing.T) {
|
||||||
// go test -v -run=^$ -bench=Benchmark_Ctx_Vary -benchmem -count=4
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Vary -benchmem -count=4
|
||||||
func Benchmark_Ctx_Vary(b *testing.B) {
|
func Benchmark_Ctx_Vary(b *testing.B) {
|
||||||
app := New()
|
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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -4806,7 +4806,7 @@ func Test_Ctx_Writef(t *testing.T) {
|
||||||
// go test -v -run=^$ -bench=Benchmark_Ctx_Writef -benchmem -count=4
|
// go test -v -run=^$ -bench=Benchmark_Ctx_Writef -benchmem -count=4
|
||||||
func Benchmark_Ctx_Writef(b *testing.B) {
|
func Benchmark_Ctx_Writef(b *testing.B) {
|
||||||
app := New()
|
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!"
|
world := "World!"
|
||||||
b.ReportAllocs()
|
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")
|
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["tags"])
|
||||||
require.Equal(t, "apple,orange,banana", queries["filters[tags]"])
|
require.Equal(t, "apple,orange,banana", queries["filters[tags]"])
|
||||||
require.Equal(t, "fruits", queries["filters[category][name]"])
|
require.Equal(t, "fruits", queries["filters[category][name]"])
|
||||||
|
@ -4951,11 +4951,11 @@ func Test_Ctx_BodyStreamWriter(t *testing.T) {
|
||||||
ctx := &fasthttp.RequestCtx{}
|
ctx := &fasthttp.RequestCtx{}
|
||||||
|
|
||||||
ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
|
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 {
|
if err := w.Flush(); err != nil {
|
||||||
t.Errorf("unexpected error: %s", err)
|
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())
|
require.True(t, ctx.IsBodyStream())
|
||||||
|
@ -5055,7 +5055,7 @@ func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) {
|
||||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90")
|
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 := &fasthttp.RequestCtx{}
|
||||||
fastCtx.SetRemoteAddr(localIPv6)
|
fastCtx.SetRemoteAddr(localIPv6)
|
||||||
c := app.AcquireCtx(fastCtx)
|
c := app.AcquireCtx(fastCtx)
|
||||||
require.Equal(t, "::1", c.IP())
|
require.Equal(t, "::1", c.Req().IP())
|
||||||
require.True(t, c.IsFromLocal())
|
require.True(t, c.Req().IsFromLocal())
|
||||||
}
|
}
|
||||||
// Test for the case fasthttp remoteAddr is set to "0:0:0:0:0:0:0:1".
|
// 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",
|
"label": "\uD83C\uDF0E Client",
|
||||||
"position": 5,
|
"position": 6,
|
||||||
"link": {
|
"link": {
|
||||||
"type": "generated-index",
|
"type": "generated-index",
|
||||||
"description": "HTTP client for Fiber."
|
"description": "HTTP client for Fiber."
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"label": "\uD83E\uDDE9 Extra",
|
"label": "\uD83E\uDDE9 Extra",
|
||||||
"position": 6,
|
"position": 8,
|
||||||
"link": {
|
"link": {
|
||||||
"type": "generated-index",
|
"type": "generated-index",
|
||||||
"description": "Extra contents for Fiber."
|
"description": "Extra contents for Fiber."
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"label": "\uD83D\uDCD6 Guide",
|
"label": "\uD83D\uDCD6 Guide",
|
||||||
"position": 5,
|
"position": 7,
|
||||||
"link": {
|
"link": {
|
||||||
"type": "generated-index",
|
"type": "generated-index",
|
||||||
"description": "Guides for Fiber."
|
"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.
|
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"
|
import "github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
type structValidator struct {
|
type structValidator struct {
|
||||||
|
@ -42,3 +41,71 @@ app.Post("/", func(c fiber.Ctx) error {
|
||||||
return c.JSON(user)
|
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 | "" |
|
| ContentSecurityPolicy | `string` | ContentSecurityPolicy | "" |
|
||||||
| CSPReportOnly | `bool` | CSPReportOnly | false |
|
| CSPReportOnly | `bool` | CSPReportOnly | false |
|
||||||
| HSTSPreloadEnabled | `bool` | HSTSPreloadEnabled | false |
|
| HSTSPreloadEnabled | `bool` | HSTSPreloadEnabled | false |
|
||||||
| ReferrerPolicy | `string` | ReferrerPolicy | "ReferrerPolicy" |
|
| ReferrerPolicy | `string` | ReferrerPolicy | "no-referrer" |
|
||||||
| PermissionPolicy | `string` | Permissions-Policy | "" |
|
| PermissionPolicy | `string` | Permissions-Policy | "" |
|
||||||
| CrossOriginEmbedderPolicy | `string` | Cross-Origin-Embedder-Policy | "require-corp" |
|
| CrossOriginEmbedderPolicy | `string` | Cross-Origin-Embedder-Policy | "require-corp" |
|
||||||
| CrossOriginOpenerPolicy | `string` | Cross-Origin-Opener-Policy | "same-origin" |
|
| CrossOriginOpenerPolicy | `string` | Cross-Origin-Opener-Policy | "same-origin" |
|
||||||
|
|
|
@ -55,13 +55,13 @@ app.Use(logger.New(logger.Config{
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Custom File Writer
|
// 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 {
|
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{
|
app.Use(logger.New(logger.Config{
|
||||||
Output: file,
|
Stream: accessLog,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Add Custom Tags
|
// Add Custom Tags
|
||||||
|
@ -88,6 +88,23 @@ app.Use(logger.New(logger.Config{
|
||||||
app.Use(logger.New(logger.Config{
|
app.Use(logger.New(logger.Config{
|
||||||
DisableColors: true,
|
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
|
### Use Logger Middleware with Other Loggers
|
||||||
|
@ -115,7 +132,7 @@ func main() {
|
||||||
|
|
||||||
// Use the logger middleware with zerolog logger
|
// Use the logger middleware with zerolog logger
|
||||||
app.Use(logger.New(logger.Config{
|
app.Use(logger.New(logger.Config{
|
||||||
Output: logger.LoggerToWriter(zap, log.LevelDebug),
|
Stream: logger.LoggerToWriter(zap, log.LevelDebug),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Define a route
|
// Define a route
|
||||||
|
@ -129,7 +146,7 @@ func main() {
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::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
|
||||||
|
@ -137,37 +154,49 @@ Writing to os.File is goroutine-safe, but if you are using a custom Output that
|
||||||
### Config
|
### Config
|
||||||
|
|
||||||
| Property | Type | Description | Default |
|
| Property | Type | Description | Default |
|
||||||
|:-----------------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------|
|
| :------------ | :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- |
|
||||||
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
| 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` |
|
| 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` |
|
| 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` |
|
| `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` |
|
| 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"` |
|
| 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` |
|
| 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` |
|
| 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` |
|
| 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` |
|
| 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) | - |
|
|
||||||
|
|
||||||
## Default Config
|
## Default Config
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var ConfigDefault = Config{
|
var ConfigDefault = Config{
|
||||||
Next: nil,
|
Next: nil,
|
||||||
|
Skip: nil,
|
||||||
Done: nil,
|
Done: nil,
|
||||||
Format: "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n",
|
Format: DefaultFormat,
|
||||||
TimeFormat: "15:04:05",
|
TimeFormat: "15:04:05",
|
||||||
TimeZone: "Local",
|
TimeZone: "Local",
|
||||||
TimeInterval: 500 * time.Millisecond,
|
TimeInterval: 500 * time.Millisecond,
|
||||||
Output: os.Stdout,
|
Stream: os.Stdout,
|
||||||
DisableColors: false,
|
BeforeHandlerFunc: beforeHandlerFunc,
|
||||||
LoggerFunc: defaultLoggerInstance,
|
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
|
## Constants
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
|
@ -33,6 +33,7 @@ Here's a quick overview of the changes in Fiber `v3`:
|
||||||
- [Filesystem](#filesystem)
|
- [Filesystem](#filesystem)
|
||||||
- [Monitor](#monitor)
|
- [Monitor](#monitor)
|
||||||
- [Healthcheck](#healthcheck)
|
- [Healthcheck](#healthcheck)
|
||||||
|
- [🔌 Addons](#-addons)
|
||||||
- [📋 Migration guide](#-migration-guide)
|
- [📋 Migration guide](#-migration-guide)
|
||||||
|
|
||||||
## Drop for old Go versions
|
## Drop for old Go versions
|
||||||
|
@ -915,6 +916,47 @@ func main() {
|
||||||
|
|
||||||
</details>
|
</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
|
### Filesystem
|
||||||
|
|
||||||
We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
|
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.
|
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
|
## 📋 Migration guide
|
||||||
|
|
||||||
- [🚀 App](#-app-1)
|
- [🚀 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")
|
ErrNoHandlers = errors.New("format: at least one handler is required, but none were set")
|
||||||
)
|
)
|
||||||
|
|
||||||
// gorilla/schema errors
|
// gofiber/schema errors
|
||||||
type (
|
type (
|
||||||
// ConversionError Conversion error exposes the internal schema.ConversionError for public use.
|
// ConversionError Conversion error exposes the internal schema.ConversionError for public use.
|
||||||
ConversionError = schema.ConversionError
|
ConversionError = schema.ConversionError
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -12,7 +12,7 @@ require (
|
||||||
github.com/tinylib/msgp v1.2.5
|
github.com/tinylib/msgp v1.2.5
|
||||||
github.com/valyala/bytebufferpool v1.0.0
|
github.com/valyala/bytebufferpool v1.0.0
|
||||||
github.com/valyala/fasthttp v1.59.0
|
github.com/valyala/fasthttp v1.59.0
|
||||||
golang.org/x/crypto v0.35.0
|
golang.org/x/crypto v0.36.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -23,8 +23,8 @@ require (
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
golang.org/x/net v0.35.0 // indirect
|
golang.org/x/net v0.37.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // 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/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 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
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 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
62
helpers.go
62
helpers.go
|
@ -14,6 +14,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -51,16 +52,16 @@ func getTLSConfig(ln net.Listener) *tls.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy value from pointer
|
// Copy value from pointer
|
||||||
if val := reflect.Indirect(pointer); val.Type() != nil {
|
if val := reflect.Indirect(pointer); val.IsValid() {
|
||||||
// Get private field from value
|
// 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)
|
// 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.
|
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
|
return nil
|
||||||
}
|
}
|
||||||
// Get element from pointer
|
// Get element from pointer
|
||||||
if elem := newval.Elem(); elem.Type() != nil {
|
if elem := newval.Elem(); elem.IsValid() {
|
||||||
// Cast value to *tls.Config
|
// Cast value to *tls.Config
|
||||||
c, ok := elem.Interface().(*tls.Config)
|
c, ok := elem.Interface().(*tls.Config)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -107,15 +108,15 @@ func (app *App[TCtx]) methodExist(c *DefaultCtx) bool {
|
||||||
methods := app.config.RequestMethods
|
methods := app.config.RequestMethods
|
||||||
for i := 0; i < len(methods); i++ {
|
for i := 0; i < len(methods); i++ {
|
||||||
// Skip original method
|
// Skip original method
|
||||||
if c.getMethodINT() == i {
|
if c.getMethodInt() == i {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Reset stack index
|
// Reset stack index
|
||||||
c.setIndexRoute(-1)
|
c.setIndexRoute(-1)
|
||||||
|
|
||||||
tree, ok := app.treeStack[i][c.getTreePath()]
|
tree, ok := app.treeStack[i][c.treePathHash]
|
||||||
if !ok {
|
if !ok {
|
||||||
tree = app.treeStack[i][""]
|
tree = app.treeStack[i][0]
|
||||||
}
|
}
|
||||||
// Get stack length
|
// Get stack length
|
||||||
lenr := len(tree) - 1
|
lenr := len(tree) - 1
|
||||||
|
@ -152,15 +153,15 @@ func (app *App[TCtx]) methodExistCustom(c CustomCtx[TCtx]) bool {
|
||||||
methods := app.config.RequestMethods
|
methods := app.config.RequestMethods
|
||||||
for i := 0; i < len(methods); i++ {
|
for i := 0; i < len(methods); i++ {
|
||||||
// Skip original method
|
// Skip original method
|
||||||
if c.getMethodINT() == i {
|
if c.getMethodInt() == i {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Reset stack index
|
// Reset stack index
|
||||||
c.setIndexRoute(-1)
|
c.setIndexRoute(-1)
|
||||||
|
|
||||||
tree, ok := app.treeStack[i][c.getTreePath()]
|
tree, ok := app.treeStack[i][c.getTreePathHash()]
|
||||||
if !ok {
|
if !ok {
|
||||||
tree = app.treeStack[i][""]
|
tree = app.treeStack[i][0]
|
||||||
}
|
}
|
||||||
// Get stack length
|
// Get stack length
|
||||||
lenr := len(tree) - 1
|
lenr := len(tree) - 1
|
||||||
|
@ -484,7 +485,7 @@ func getOffer(header []byte, isAccepted func(spec, offer string, specParams head
|
||||||
|
|
||||||
if len(acceptedTypes) > 1 {
|
if len(acceptedTypes) > 1 {
|
||||||
// Sort accepted types by quality and specificity, preserving order of equal elements
|
// 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
|
// 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.
|
// 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
|
// 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
|
// See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields
|
||||||
func sortAcceptedTypes(acceptedTypes *[]acceptedType) {
|
func sortAcceptedTypes(at []acceptedType) {
|
||||||
if acceptedTypes == nil || len(*acceptedTypes) < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
at := *acceptedTypes
|
|
||||||
|
|
||||||
for i := 1; i < len(at); i++ {
|
for i := 1; i < len(at); i++ {
|
||||||
lo, hi := 0, i-1
|
lo, hi := 0, i-1
|
||||||
for lo <= hi {
|
for lo <= hi {
|
||||||
mid := (lo + hi) / 2
|
mid := (lo + hi) / 2
|
||||||
if at[i].quality < at[mid].quality ||
|
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) ||
|
||||||
(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) {
|
(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
|
lo = mid + 1
|
||||||
} else {
|
} else {
|
||||||
|
@ -658,39 +654,35 @@ func getBytesImmutable(s string) []byte {
|
||||||
func (app *App[TCtx]) methodInt(s string) int {
|
func (app *App[TCtx]) methodInt(s string) int {
|
||||||
// For better performance
|
// For better performance
|
||||||
if len(app.configured.RequestMethods) == 0 {
|
if len(app.configured.RequestMethods) == 0 {
|
||||||
// TODO: Use iota instead
|
|
||||||
switch s {
|
switch s {
|
||||||
case MethodGet:
|
case MethodGet:
|
||||||
return 0
|
return methodGet
|
||||||
case MethodHead:
|
case MethodHead:
|
||||||
return 1
|
return methodHead
|
||||||
case MethodPost:
|
case MethodPost:
|
||||||
return 2
|
return methodPost
|
||||||
case MethodPut:
|
case MethodPut:
|
||||||
return 3
|
return methodPut
|
||||||
case MethodDelete:
|
case MethodDelete:
|
||||||
return 4
|
return methodDelete
|
||||||
case MethodConnect:
|
case MethodConnect:
|
||||||
return 5
|
return methodConnect
|
||||||
case MethodOptions:
|
case MethodOptions:
|
||||||
return 6
|
return methodOptions
|
||||||
case MethodTrace:
|
case MethodTrace:
|
||||||
return 7
|
return methodTrace
|
||||||
case MethodPatch:
|
case MethodPatch:
|
||||||
return 8
|
return methodPatch
|
||||||
default:
|
default:
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For method customization
|
// For method customization
|
||||||
for i, v := range app.config.RequestMethods {
|
return slices.Index(app.config.RequestMethods, s)
|
||||||
if s == v {
|
}
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1
|
func (app *App) method(methodInt int) string {
|
||||||
|
return app.config.RequestMethods[methodInt]
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMethodSafe reports whether the HTTP method is considered safe.
|
// 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/html", quality: 1, specificity: 3, order: 0},
|
||||||
{spec: "text/*", quality: 0.5, specificity: 2, order: 1},
|
{spec: "text/*", quality: 0.5, specificity: 2, order: 1},
|
||||||
{spec: "*/*", quality: 0.1, specificity: 1, order: 2},
|
{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/xml", quality: 1, specificity: 3, order: 4},
|
||||||
{spec: "application/pdf", quality: 1, specificity: 3, order: 5},
|
{spec: "application/pdf", quality: 1, specificity: 3, order: 5},
|
||||||
{spec: "image/png", quality: 1, specificity: 3, order: 6},
|
{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: "image/gif", quality: 1, specificity: 3, order: 9},
|
||||||
{spec: "text/plain", quality: 1, specificity: 3, order: 10},
|
{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, 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{
|
require.Equal(t, []acceptedType{
|
||||||
{spec: "text/html", quality: 1, specificity: 3, order: 0},
|
{spec: "text/html", quality: 1, specificity: 3, order: 0},
|
||||||
{spec: "application/xml", quality: 1, specificity: 3, order: 4},
|
{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[0] = acceptedType{spec: "text/html", quality: 1, specificity: 1, order: 0}
|
||||||
acceptedTypes[1] = acceptedType{spec: "text/*", quality: 0.5, specificity: 1, order: 1}
|
acceptedTypes[1] = acceptedType{spec: "text/*", quality: 0.5, specificity: 1, order: 1}
|
||||||
acceptedTypes[2] = acceptedType{spec: "*/*", quality: 0.1, specificity: 1, order: 2}
|
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/html", acceptedTypes[0].spec)
|
||||||
require.Equal(b, "text/*", acceptedTypes[1].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[8] = acceptedType{spec: "image/*", quality: 1, specificity: 2, order: 8}
|
||||||
acceptedTypes[9] = acceptedType{spec: "image/gif", quality: 1, specificity: 3, order: 9}
|
acceptedTypes[9] = acceptedType{spec: "image/gif", quality: 1, specificity: 3, order: 9}
|
||||||
acceptedTypes[10] = acceptedType{spec: "text/plain", quality: 1, specificity: 3, order: 10}
|
acceptedTypes[10] = acceptedType{spec: "text/plain", quality: 1, specificity: 3, order: 10}
|
||||||
sortAcceptedTypes(&acceptedTypes)
|
sortAcceptedTypes(acceptedTypes)
|
||||||
}
|
}
|
||||||
require.Equal(b, []acceptedType{
|
require.Equal(b, []acceptedType{
|
||||||
{spec: "text/html", quality: 1, specificity: 3, order: 0},
|
{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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Close early, write should fail
|
// 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"))
|
_, err = conn.Write([]byte("Response 2\n"))
|
||||||
require.ErrorIs(t, err, errTestConnClosed)
|
require.ErrorIs(t, err, errTestConnClosed)
|
||||||
|
|
||||||
|
|
|
@ -209,7 +209,7 @@ func Benchmark_Memory_Set(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
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) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
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()
|
b.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
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) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
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()
|
b.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
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
|
||||||
_ = testStore.Delete("john") //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) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
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
|
||||||
_ = testStore.Delete("john") //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
|
// 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
|
// ignore child processes
|
||||||
if IsChild() {
|
if IsChild() {
|
||||||
return
|
return
|
||||||
|
@ -366,38 +366,35 @@ func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg L
|
||||||
out = colorable.NewNonColorable(os.Stdout)
|
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, "%s\n", fmt.Sprintf(figletFiberText, colors.Red+"v"+Version+colors.Reset))
|
||||||
fmt.Fprintf(out, strings.Repeat("-", 50)+"\n") //nolint:errcheck,revive,govet // ignore error
|
fmt.Fprintf(out, strings.Repeat("-", 50)+"\n")
|
||||||
|
|
||||||
if host == "0.0.0.0" {
|
if host == "0.0.0.0" {
|
||||||
//nolint:errcheck,revive // ignore error
|
|
||||||
fmt.Fprintf(out,
|
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",
|
"%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)
|
colors.Green, colors.Reset, colors.Blue, scheme, port, colors.Reset, port)
|
||||||
} else {
|
} else {
|
||||||
//nolint:errcheck,revive // ignore error
|
|
||||||
fmt.Fprintf(out,
|
fmt.Fprintf(out,
|
||||||
"%sINFO%s Server started on: \t%s%s%s\n",
|
"%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)
|
colors.Green, colors.Reset, colors.Blue, fmt.Sprintf("%s://%s:%s", scheme, host, port), colors.Reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.config.AppName != "" {
|
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,
|
fmt.Fprintf(out,
|
||||||
"%sINFO%s Total handlers count: \t%s%s%s\n",
|
"%sINFO%s Total handlers count: \t%s%s%s\n",
|
||||||
colors.Green, colors.Reset, colors.Blue, strconv.Itoa(int(app.handlersCount)), colors.Reset)
|
colors.Green, colors.Reset, colors.Blue, strconv.Itoa(int(app.handlersCount)), colors.Reset)
|
||||||
|
|
||||||
if isPrefork == "Enabled" {
|
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 {
|
} 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 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) //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)
|
||||||
|
|
||||||
if cfg.EnablePrefork {
|
if cfg.EnablePrefork {
|
||||||
// Turn the `pids` variable (in the form ",a,b,c,d,e,f,etc") into a slice of PIDs
|
// 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)
|
totalPids := len(pidSlice)
|
||||||
rowTotalPidCount := 10
|
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] {
|
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]) {
|
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
|
// 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
|
// 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
|
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, "%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) //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)
|
||||||
|
|
||||||
for _, route := range routes {
|
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)
|
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)
|
buf.WriteString(level)
|
||||||
|
|
||||||
if len(fmtArgs) > 0 {
|
if len(fmtArgs) > 0 {
|
||||||
_, _ = fmt.Fprintf(buf, format, fmtArgs...) //nolint: errcheck // It is fine to ignore the error
|
_, _ = fmt.Fprintf(buf, format, fmtArgs...)
|
||||||
} else {
|
} 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
|
_ = 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
|
package adaptor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -68,7 +68,7 @@ func Test_HTTPHandler(t *testing.T) {
|
||||||
w.Header().Set("Header1", "value1")
|
w.Header().Set("Header1", "value1")
|
||||||
w.Header().Set("Header2", "value2")
|
w.Header().Set("Header2", "value2")
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
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 := HTTPHandlerFunc(http.HandlerFunc(nethttpH))
|
||||||
fiberH = setFiberContextValueMiddleware(fiberH, expectedContextKey, expectedContextValue)
|
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
|
// 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) {
|
func Test_CSRF_UnsafeHeaderValue(t *testing.T) {
|
||||||
// t.Parallel()
|
t.SkipNow()
|
||||||
// app := fiber.New()
|
t.Parallel()
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
// app.Use(New())
|
app.Use(New())
|
||||||
// app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
// return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
// })
|
})
|
||||||
// app.Get("/test", func(c fiber.Ctx) error {
|
app.Get("/test", func(c fiber.Ctx) error {
|
||||||
// return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
// })
|
})
|
||||||
// app.Post("/", func(c fiber.Ctx) error {
|
app.Post("/", func(c fiber.Ctx) error {
|
||||||
// return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
// })
|
})
|
||||||
|
|
||||||
// resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||||
// require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
// var token string
|
var token string
|
||||||
// for _, c := range resp.Cookies() {
|
for _, c := range resp.Cookies() {
|
||||||
// if c.Name != ConfigDefault.CookieName {
|
if c.Name != ConfigDefault.CookieName {
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
// token = c.Value
|
token = c.Value
|
||||||
// break
|
break
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fmt.Println("token", token)
|
t.Log("token", token)
|
||||||
|
|
||||||
// getReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
getReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||||
// getReq.Header.Set(HeaderName, token)
|
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 = httptest.NewRequest(fiber.MethodGet, "/test", nil)
|
getReq = httptest.NewRequest(fiber.MethodGet, "/test", nil)
|
||||||
// getReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
getReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||||
// getReq.Header.Set(fiber.HeaderCacheControl, "no")
|
getReq.Header.Set(fiber.HeaderCacheControl, "no")
|
||||||
// getReq.Header.Set(HeaderName, token)
|
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.Set(fiber.HeaderAccept, "*/*")
|
||||||
// getReq.Header.Del(HeaderName)
|
getReq.Header.Del(HeaderName)
|
||||||
// resp, err = app.Test(getReq)
|
resp, err = app.Test(getReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
// postReq := httptest.NewRequest(fiber.MethodPost, "/", nil)
|
postReq := httptest.NewRequest(fiber.MethodPost, "/", nil)
|
||||||
// postReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
postReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||||
// postReq.Header.Set(HeaderName, token)
|
postReq.Header.Set(HeaderName, token)
|
||||||
// resp, err = app.Test(postReq)
|
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
|
// go test -v -run=^$ -bench=Benchmark_Middleware_CSRF_Check -benchmem -count=4
|
||||||
func Benchmark_Middleware_CSRF_Check(b *testing.B) {
|
func Benchmark_Middleware_CSRF_Check(b *testing.B) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ type Config struct {
|
||||||
ContentSecurityPolicy string
|
ContentSecurityPolicy string
|
||||||
|
|
||||||
// ReferrerPolicy
|
// ReferrerPolicy
|
||||||
// Optional. Default value "ReferrerPolicy".
|
// Optional. Default value "no-referrer".
|
||||||
ReferrerPolicy string
|
ReferrerPolicy string
|
||||||
|
|
||||||
// Permissions-Policy
|
// Permissions-Policy
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
package keyauth
|
package keyauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -59,7 +60,10 @@ func New(config ...Config) fiber.Handler {
|
||||||
valid, err := cfg.Validator(c, key)
|
valid, err := cfg.Validator(c, key)
|
||||||
|
|
||||||
if err == nil && valid {
|
if err == nil && valid {
|
||||||
|
// Store in both Locals and Context
|
||||||
c.Locals(tokenKey, key)
|
c.Locals(tokenKey, key)
|
||||||
|
ctx := context.WithValue(c.Context(), tokenKey, key)
|
||||||
|
c.SetContext(ctx)
|
||||||
return cfg.SuccessHandler(c)
|
return cfg.SuccessHandler(c)
|
||||||
}
|
}
|
||||||
return cfg.ErrorHandler(c, err)
|
return cfg.ErrorHandler(c, err)
|
||||||
|
@ -68,12 +72,20 @@ func New(config ...Config) fiber.Handler {
|
||||||
|
|
||||||
// TokenFromContext returns the bearer token from the request context.
|
// TokenFromContext returns the bearer token from the request context.
|
||||||
// returns an empty string if the token does not exist
|
// returns an empty string if the token does not exist
|
||||||
func TokenFromContext(c fiber.Ctx) string {
|
func TokenFromContext(c any) string {
|
||||||
token, ok := c.Locals(tokenKey).(string)
|
switch ctx := c.(type) {
|
||||||
if !ok {
|
case context.Context:
|
||||||
return ""
|
if token, ok := ctx.Value(tokenKey).(string); ok {
|
||||||
}
|
|
||||||
return token
|
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 ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultipleKeySourceLookup creates a CustomKeyLookup function that checks multiple sources until one is found
|
// MultipleKeySourceLookup creates a CustomKeyLookup function that checks multiple sources until one is found
|
||||||
|
|
|
@ -503,8 +503,9 @@ func Test_TokenFromContext_None(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_TokenFromContext(t *testing.T) {
|
func Test_TokenFromContext(t *testing.T) {
|
||||||
|
// Test that TokenFromContext returns the correct token
|
||||||
|
t.Run("fiber.Ctx", func(t *testing.T) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
// Wire up keyauth middleware to set TokenFromContext now
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
KeyLookup: "header:Authorization",
|
KeyLookup: "header:Authorization",
|
||||||
AuthScheme: "Basic",
|
AuthScheme: "Basic",
|
||||||
|
@ -515,21 +516,54 @@ func Test_TokenFromContext(t *testing.T) {
|
||||||
return false, ErrMissingOrMalformedAPIKey
|
return false, ErrMissingOrMalformedAPIKey
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
// Define a test handler that checks TokenFromContext
|
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString(TokenFromContext(c))
|
return c.SendString(TokenFromContext(c))
|
||||||
})
|
})
|
||||||
|
|
||||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||||
req.Header.Add("Authorization", "Basic "+CorrectKey)
|
req.Header.Add("Authorization", "Basic "+CorrectKey)
|
||||||
// Send
|
|
||||||
res, err := app.Test(req)
|
res, err := app.Test(req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Read the response body into a string
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, CorrectKey, string(body))
|
require.Equal(t, CorrectKey, string(body))
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
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) {
|
func Test_AuthSchemeToken(t *testing.T) {
|
||||||
|
|
|
@ -10,16 +10,21 @@ import (
|
||||||
|
|
||||||
// Config defines the config for middleware.
|
// Config defines the config for middleware.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Output is a writer where logs are written
|
// Stream is a writer where logs are written
|
||||||
//
|
//
|
||||||
// Default: os.Stdout
|
// Default: os.Stdout
|
||||||
Output io.Writer
|
Stream io.Writer
|
||||||
|
|
||||||
// Next defines a function to skip this middleware when returned true.
|
// Next defines a function to skip this middleware when returned true.
|
||||||
//
|
//
|
||||||
// Optional. Default: nil
|
// Optional. Default: nil
|
||||||
Next func(c fiber.Ctx) bool
|
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,
|
// 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.
|
// and pass the log string as parameter.
|
||||||
//
|
//
|
||||||
|
@ -45,9 +50,23 @@ type Config struct {
|
||||||
|
|
||||||
timeZoneLocation *time.Location
|
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
|
Format string
|
||||||
|
|
||||||
// TimeFormat https://programming.guide/go/format-parse-string-time-date-example.html
|
// 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
|
// ConfigDefault is the default config
|
||||||
var ConfigDefault = Config{
|
var ConfigDefault = Config{
|
||||||
Next: nil,
|
Next: nil,
|
||||||
|
Skip: nil,
|
||||||
Done: nil,
|
Done: nil,
|
||||||
Format: defaultFormat,
|
Format: DefaultFormat,
|
||||||
TimeFormat: "15:04:05",
|
TimeFormat: "15:04:05",
|
||||||
TimeZone: "Local",
|
TimeZone: "Local",
|
||||||
TimeInterval: 500 * time.Millisecond,
|
TimeInterval: 500 * time.Millisecond,
|
||||||
Output: os.Stdout,
|
Stream: os.Stdout,
|
||||||
BeforeHandlerFunc: beforeHandlerFunc,
|
BeforeHandlerFunc: beforeHandlerFunc,
|
||||||
LoggerFunc: defaultLoggerInstance,
|
LoggerFunc: defaultLoggerInstance,
|
||||||
enableColors: true,
|
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
|
// Helper function to set default values
|
||||||
func configDefault(config ...Config) Config {
|
func configDefault(config ...Config) Config {
|
||||||
// Return default config if nothing provided
|
// Return default config if nothing provided
|
||||||
|
@ -126,6 +143,9 @@ func configDefault(config ...Config) Config {
|
||||||
if cfg.Next == nil {
|
if cfg.Next == nil {
|
||||||
cfg.Next = ConfigDefault.Next
|
cfg.Next = ConfigDefault.Next
|
||||||
}
|
}
|
||||||
|
if cfg.Skip == nil {
|
||||||
|
cfg.Skip = ConfigDefault.Skip
|
||||||
|
}
|
||||||
if cfg.Done == nil {
|
if cfg.Done == nil {
|
||||||
cfg.Done = ConfigDefault.Done
|
cfg.Done = ConfigDefault.Done
|
||||||
}
|
}
|
||||||
|
@ -141,8 +161,8 @@ func configDefault(config ...Config) Config {
|
||||||
if int(cfg.TimeInterval) <= 0 {
|
if int(cfg.TimeInterval) <= 0 {
|
||||||
cfg.TimeInterval = ConfigDefault.TimeInterval
|
cfg.TimeInterval = ConfigDefault.TimeInterval
|
||||||
}
|
}
|
||||||
if cfg.Output == nil {
|
if cfg.Stream == nil {
|
||||||
cfg.Output = ConfigDefault.Output
|
cfg.Stream = ConfigDefault.Stream
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.BeforeHandlerFunc == nil {
|
if cfg.BeforeHandlerFunc == nil {
|
||||||
|
@ -154,7 +174,7 @@ func configDefault(config ...Config) Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable colors if no custom format or output is given
|
// 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
|
cfg.enableColors = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,12 @@ import (
|
||||||
|
|
||||||
// default logger for fiber
|
// default logger for fiber
|
||||||
func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
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
|
// Alias colors
|
||||||
colors := c.App().Config().ColorScheme
|
colors := c.App().Config().ColorScheme
|
||||||
|
|
||||||
|
@ -22,7 +28,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
||||||
buf := bytebufferpool.Get()
|
buf := bytebufferpool.Get()
|
||||||
|
|
||||||
// Default output when no custom Format or io.Writer is given
|
// Default output when no custom Format or io.Writer is given
|
||||||
if cfg.Format == defaultFormat {
|
if cfg.Format == DefaultFormat {
|
||||||
// Format error if exist
|
// Format error if exist
|
||||||
formatErr := ""
|
formatErr := ""
|
||||||
if cfg.enableColors {
|
if cfg.enableColors {
|
||||||
|
@ -91,7 +97,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write buffer to output
|
// Write buffer to output
|
||||||
writeLog(cfg.Output, buf.Bytes())
|
writeLog(cfg.Stream, buf.Bytes())
|
||||||
|
|
||||||
if cfg.Done != nil {
|
if cfg.Done != nil {
|
||||||
cfg.Done(c, buf.Bytes())
|
cfg.Done(c, buf.Bytes())
|
||||||
|
@ -125,7 +131,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
||||||
buf.WriteString(err.Error())
|
buf.WriteString(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLog(cfg.Output, buf.Bytes())
|
writeLog(cfg.Stream, buf.Bytes())
|
||||||
|
|
||||||
if cfg.Done != nil {
|
if cfg.Done != nil {
|
||||||
cfg.Done(c, buf.Bytes())
|
cfg.Done(c, buf.Bytes())
|
||||||
|
@ -141,9 +147,9 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
||||||
func beforeHandlerFunc(cfg Config) {
|
func beforeHandlerFunc(cfg Config) {
|
||||||
// If colors are enabled, check terminal compatibility
|
// If colors are enabled, check terminal compatibility
|
||||||
if cfg.enableColors {
|
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())) {
|
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
|
// Write error to output
|
||||||
if _, err := w.Write([]byte(err.Error())); err != nil {
|
if _, err := w.Write([]byte(err.Error())); err != nil {
|
||||||
// There is something wrong with the given io.Writer
|
// 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
|
// Set PID once
|
||||||
pid := strconv.Itoa(os.Getpid())
|
pid := strconv.Itoa(os.Getpid())
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ func Test_Logger(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${error}",
|
Format: "${error}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
app.Get("/", func(_ fiber.Ctx) error {
|
app.Get("/", func(_ fiber.Ctx) error {
|
||||||
|
@ -94,7 +94,7 @@ func Test_Logger_locals(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${locals:demo}",
|
Format: "${locals:demo}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
@ -171,6 +171,147 @@ func Test_Logger_Done(t *testing.T) {
|
||||||
require.Positive(t, buf.Len(), 0)
|
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
|
// go test -run Test_Logger_ErrorTimeZone
|
||||||
func Test_Logger_ErrorTimeZone(t *testing.T) {
|
func Test_Logger_ErrorTimeZone(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -234,7 +375,7 @@ func Test_Logger_LoggerToWriter(t *testing.T) {
|
||||||
|
|
||||||
app.Use("/"+level, New(Config{
|
app.Use("/"+level, New(Config{
|
||||||
Format: "${error}",
|
Format: "${error}",
|
||||||
Output: LoggerToWriter(logger, tc.
|
Stream: LoggerToWriter(logger, tc.
|
||||||
level),
|
level),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -276,7 +417,7 @@ func Test_Logger_ErrorOutput_WithoutColor(t *testing.T) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: o,
|
Stream: o,
|
||||||
DisableColors: true,
|
DisableColors: true,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -293,7 +434,7 @@ func Test_Logger_ErrorOutput(t *testing.T) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: o,
|
Stream: o,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||||
|
@ -312,7 +453,7 @@ func Test_Logger_All(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
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}",
|
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
|
// Alias colors
|
||||||
|
@ -326,6 +467,124 @@ func Test_Logger_All(t *testing.T) {
|
||||||
require.Equal(t, expected, buf.String())
|
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 {
|
func getLatencyTimeUnits() []struct {
|
||||||
unit string
|
unit string
|
||||||
div time.Duration
|
div time.Duration
|
||||||
|
@ -358,7 +617,7 @@ func Test_Logger_WithLatency(t *testing.T) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
logger := New(Config{
|
logger := New(Config{
|
||||||
Output: buff,
|
Stream: buff,
|
||||||
Format: "${latency}",
|
Format: "${latency}",
|
||||||
})
|
})
|
||||||
app.Use(logger)
|
app.Use(logger)
|
||||||
|
@ -403,7 +662,7 @@ func Test_Logger_WithLatency_DefaultFormat(t *testing.T) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
logger := New(Config{
|
logger := New(Config{
|
||||||
Output: buff,
|
Stream: buff,
|
||||||
})
|
})
|
||||||
app.Use(logger)
|
app.Use(logger)
|
||||||
|
|
||||||
|
@ -453,7 +712,7 @@ func Test_Query_Params(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${queryParams}",
|
Format: "${queryParams}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar&baz=moz", nil))
|
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{
|
app.Use(New(Config{
|
||||||
Format: "${resBody}",
|
Format: "${resBody}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
@ -508,7 +767,7 @@ func Test_Request_Body(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
app.Post("/", func(c fiber.Ctx) error {
|
app.Post("/", func(c fiber.Ctx) error {
|
||||||
|
@ -536,7 +795,7 @@ func Test_Logger_AppendUint(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
@ -611,7 +870,7 @@ func Test_Response_Header(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${respHeader:X-Request-ID}",
|
Format: "${respHeader:X-Request-ID}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello fiber!")
|
return c.SendString("Hello fiber!")
|
||||||
|
@ -634,7 +893,7 @@ func Test_Req_Header(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${reqHeader:test}",
|
Format: "${reqHeader:test}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello fiber!")
|
return c.SendString("Hello fiber!")
|
||||||
|
@ -658,7 +917,7 @@ func Test_ReqHeader_Header(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${reqHeader:test}",
|
Format: "${reqHeader:test}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello fiber!")
|
return c.SendString("Hello fiber!")
|
||||||
|
@ -689,7 +948,7 @@ func Test_CustomTags(t *testing.T) {
|
||||||
return output.WriteString(customTag)
|
return output.WriteString(customTag)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello fiber!")
|
return c.SendString("Hello fiber!")
|
||||||
|
@ -713,7 +972,7 @@ func Test_Logger_ByteSent_Streaming(t *testing.T) {
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||||
Output: buf,
|
Stream: buf,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
@ -724,7 +983,7 @@ func Test_Logger_ByteSent_Streaming(t *testing.T) {
|
||||||
for {
|
for {
|
||||||
i++
|
i++
|
||||||
msg := fmt.Sprintf("%d - the time is %v", i, time.Now())
|
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()
|
err := w.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -759,7 +1018,7 @@ func Test_Logger_EnableColors(t *testing.T) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: o,
|
Stream: o,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||||
|
@ -782,7 +1041,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
c.Set("test", "test")
|
c.Set("test", "test")
|
||||||
|
@ -794,7 +1053,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
b.Run("DefaultFormat", func(bb *testing.B) {
|
b.Run("DefaultFormat", func(bb *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello, World!")
|
return c.SendString("Hello, World!")
|
||||||
|
@ -805,7 +1064,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
DisableColors: true,
|
DisableColors: true,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
@ -819,7 +1078,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
logger := fiberlog.DefaultLogger()
|
logger := fiberlog.DefaultLogger()
|
||||||
logger.SetOutput(io.Discard)
|
logger.SetOutput(io.Discard)
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: LoggerToWriter(logger, fiberlog.LevelDebug),
|
Stream: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello, World!")
|
return c.SendString("Hello, World!")
|
||||||
|
@ -831,7 +1090,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
c.Set("test", "test")
|
c.Set("test", "test")
|
||||||
|
@ -844,7 +1103,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${locals:demo}",
|
Format: "${locals:demo}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
c.Locals("demo", "johndoe")
|
c.Locals("demo", "johndoe")
|
||||||
|
@ -857,7 +1116,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${locals:demo}",
|
Format: "${locals:demo}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/int", func(c fiber.Ctx) error {
|
app.Get("/int", func(c fiber.Ctx) error {
|
||||||
c.Locals("demo", 55)
|
c.Locals("demo", 55)
|
||||||
|
@ -874,7 +1133,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
io.Discard.Write(logString) //nolint:errcheck // ignore error
|
io.Discard.Write(logString) //nolint:errcheck // ignore error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/logging", func(ctx fiber.Ctx) error {
|
app.Get("/logging", func(ctx fiber.Ctx) error {
|
||||||
return ctx.SendStatus(fiber.StatusOK)
|
return ctx.SendStatus(fiber.StatusOK)
|
||||||
|
@ -886,7 +1145,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
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}",
|
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 {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello, World!")
|
return c.SendString("Hello, World!")
|
||||||
|
@ -898,7 +1157,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
c.Set("Connection", "keep-alive")
|
c.Set("Connection", "keep-alive")
|
||||||
|
@ -908,7 +1167,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
for {
|
for {
|
||||||
i++
|
i++
|
||||||
msg := fmt.Sprintf("%d - the time is %v", i, time.Now())
|
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()
|
err := w.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -927,7 +1186,7 @@ func Benchmark_Logger(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${resBody}",
|
Format: "${resBody}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Sample response body")
|
return c.SendString("Sample response body")
|
||||||
|
@ -950,7 +1209,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
c.Set("test", "test")
|
c.Set("test", "test")
|
||||||
|
@ -962,7 +1221,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
b.Run("DefaultFormat", func(bb *testing.B) {
|
b.Run("DefaultFormat", func(bb *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello, World!")
|
return c.SendString("Hello, World!")
|
||||||
|
@ -975,7 +1234,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
logger := fiberlog.DefaultLogger()
|
logger := fiberlog.DefaultLogger()
|
||||||
logger.SetOutput(io.Discard)
|
logger.SetOutput(io.Discard)
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: LoggerToWriter(logger, fiberlog.LevelDebug),
|
Stream: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello, World!")
|
return c.SendString("Hello, World!")
|
||||||
|
@ -986,7 +1245,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
DisableColors: true,
|
DisableColors: true,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
|
@ -999,7 +1258,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
c.Set("test", "test")
|
c.Set("test", "test")
|
||||||
|
@ -1012,7 +1271,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${locals:demo}",
|
Format: "${locals:demo}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
c.Locals("demo", "johndoe")
|
c.Locals("demo", "johndoe")
|
||||||
|
@ -1025,7 +1284,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${locals:demo}",
|
Format: "${locals:demo}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/int", func(c fiber.Ctx) error {
|
app.Get("/int", func(c fiber.Ctx) error {
|
||||||
c.Locals("demo", 55)
|
c.Locals("demo", 55)
|
||||||
|
@ -1042,7 +1301,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
io.Discard.Write(logString) //nolint:errcheck // ignore error
|
io.Discard.Write(logString) //nolint:errcheck // ignore error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/logging", func(ctx fiber.Ctx) error {
|
app.Get("/logging", func(ctx fiber.Ctx) error {
|
||||||
return ctx.SendStatus(fiber.StatusOK)
|
return ctx.SendStatus(fiber.StatusOK)
|
||||||
|
@ -1054,7 +1313,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
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}",
|
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 {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Hello, World!")
|
return c.SendString("Hello, World!")
|
||||||
|
@ -1066,7 +1325,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
c.Set("Connection", "keep-alive")
|
c.Set("Connection", "keep-alive")
|
||||||
|
@ -1076,7 +1335,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
for {
|
for {
|
||||||
i++
|
i++
|
||||||
msg := fmt.Sprintf("%d - the time is %v", i, time.Now())
|
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()
|
err := w.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -1095,7 +1354,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(New(Config{
|
app.Use(New(Config{
|
||||||
Format: "${resBody}",
|
Format: "${resBody}",
|
||||||
Output: io.Discard,
|
Stream: io.Discard,
|
||||||
}))
|
}))
|
||||||
app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.SendString("Sample response body")
|
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")
|
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.NoError(t, err1)
|
||||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
require.Equal(t, "/test", resp.Request.URL.String())
|
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")
|
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)
|
require.NoError(t, err1)
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -558,7 +564,10 @@ func Test_Proxy_DoRedirects_TooManyRedirects(t *testing.T) {
|
||||||
return DoRedirects(c, "http://google.com", 0)
|
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)
|
require.NoError(t, err1)
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
129
path.go
129
path.go
|
@ -7,9 +7,11 @@
|
||||||
package fiber
|
package fiber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
@ -25,6 +27,12 @@ type routeParser struct {
|
||||||
plusCount int // number of plus parameters, used internally to give the plus parameter its number
|
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
|
// routeSegment holds the segment metadata
|
||||||
type routeSegment struct {
|
type routeSegment struct {
|
||||||
// const information
|
// const information
|
||||||
|
@ -123,8 +131,6 @@ var (
|
||||||
parameterConstraintSeparatorChars = []byte{paramConstraintSeparator}
|
parameterConstraintSeparatorChars = []byte{paramConstraintSeparator}
|
||||||
// list of parameter constraint data start
|
// list of parameter constraint data start
|
||||||
parameterConstraintDataStartChars = []byte{paramConstraintDataStart}
|
parameterConstraintDataStartChars = []byte{paramConstraintDataStart}
|
||||||
// list of parameter constraint data end
|
|
||||||
parameterConstraintDataEndChars = []byte{paramConstraintDataEnd}
|
|
||||||
// list of parameter constraint data separator
|
// list of parameter constraint data separator
|
||||||
parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator}
|
parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator}
|
||||||
)
|
)
|
||||||
|
@ -152,11 +158,11 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
|
||||||
pattern = "/" + pattern
|
pattern = "/" + pattern
|
||||||
}
|
}
|
||||||
|
|
||||||
patternPretty := pattern
|
patternPretty := []byte(pattern)
|
||||||
|
|
||||||
// Case-sensitive routing, all to lowercase
|
// Case-sensitive routing, all to lowercase
|
||||||
if !config.CaseSensitive {
|
if !config.CaseSensitive {
|
||||||
patternPretty = utils.ToLower(patternPretty)
|
patternPretty = utils.ToLowerBytes(patternPretty)
|
||||||
path = utils.ToLower(path)
|
path = utils.ToLower(path)
|
||||||
}
|
}
|
||||||
// Strict routing, remove trailing slashes
|
// Strict routing, remove trailing slashes
|
||||||
|
@ -164,12 +170,15 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
|
||||||
patternPretty = utils.TrimRight(patternPretty, '/')
|
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
|
return true
|
||||||
// '*' wildcard matches any path
|
// '*' wildcard matches any path
|
||||||
} else if patternPretty == "/*" {
|
} else if string(patternPretty) == "/*" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,42 +189,47 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check for a simple match
|
// Check for a simple match
|
||||||
patternPretty = RemoveEscapeChar(patternPretty)
|
patternPretty = RemoveEscapeCharBytes(patternPretty)
|
||||||
if len(patternPretty) == len(path) && patternPretty == path {
|
|
||||||
return true
|
return string(patternPretty) == path
|
||||||
}
|
}
|
||||||
// No match
|
|
||||||
return false
|
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,
|
// 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
|
// this information is needed later when assigning the requests to the declared routes
|
||||||
func parseRoute(pattern string, customConstraints ...CustomConstraint) routeParser {
|
func (parser *routeParser) parseRoute(pattern string, customConstraints ...CustomConstraint) {
|
||||||
parser := routeParser{}
|
var n int
|
||||||
part := ""
|
var seg *routeSegment
|
||||||
for len(pattern) > 0 {
|
for len(pattern) > 0 {
|
||||||
nextParamPosition := findNextParamPosition(pattern)
|
nextParamPosition := findNextParamPosition(pattern)
|
||||||
// handle the parameter part
|
// handle the parameter part
|
||||||
if nextParamPosition == 0 {
|
if nextParamPosition == 0 {
|
||||||
processedPart, seg := parser.analyseParameterPart(pattern, customConstraints...)
|
n, seg = parser.analyseParameterPart(pattern, customConstraints...)
|
||||||
parser.params, parser.segs, part = append(parser.params, seg.ParamName), append(parser.segs, seg), processedPart
|
parser.params, parser.segs = append(parser.params, seg.ParamName), append(parser.segs, seg)
|
||||||
} else {
|
} else {
|
||||||
processedPart, seg := parser.analyseConstantPart(pattern, nextParamPosition)
|
n, seg = parser.analyseConstantPart(pattern, nextParamPosition)
|
||||||
parser.segs, part = append(parser.segs, seg), processedPart
|
parser.segs = append(parser.segs, seg)
|
||||||
}
|
}
|
||||||
|
pattern = pattern[n:]
|
||||||
// reduce the pattern by the processed parts
|
|
||||||
if len(part) == len(pattern) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pattern = pattern[len(part):]
|
|
||||||
}
|
}
|
||||||
// mark last segment
|
// mark last segment
|
||||||
if len(parser.segs) > 0 {
|
if len(parser.segs) > 0 {
|
||||||
parser.segs[len(parser.segs)-1].IsLast = true
|
parser.segs[len(parser.segs)-1].IsLast = true
|
||||||
}
|
}
|
||||||
parser.segs = addParameterMetaInfo(parser.segs)
|
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
|
return parser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,7 +297,7 @@ func findNextParamPosition(pattern string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// analyseConstantPart find the end of the constant part and create the route segment
|
// 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
|
// handle the constant part
|
||||||
processedPart := pattern
|
processedPart := pattern
|
||||||
if nextParamPosition != -1 {
|
if nextParamPosition != -1 {
|
||||||
|
@ -291,14 +305,14 @@ func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (
|
||||||
processedPart = pattern[:nextParamPosition]
|
processedPart = pattern[:nextParamPosition]
|
||||||
}
|
}
|
||||||
constPart := RemoveEscapeChar(processedPart)
|
constPart := RemoveEscapeChar(processedPart)
|
||||||
return processedPart, &routeSegment{
|
return len(processedPart), &routeSegment{
|
||||||
Const: constPart,
|
Const: constPart,
|
||||||
Length: len(constPart),
|
Length: len(constPart),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// analyseParameterPart find the parameter end and create the route segment
|
// 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
|
isWildCard := pattern[0] == wildcardParam
|
||||||
isPlusParam := pattern[0] == plusParam
|
isPlusParam := pattern[0] == plusParam
|
||||||
|
|
||||||
|
@ -317,18 +331,19 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
||||||
parameterEndPosition = 0
|
parameterEndPosition = 0
|
||||||
case parameterEndPosition == -1:
|
case parameterEndPosition == -1:
|
||||||
parameterEndPosition = len(pattern) - 1
|
parameterEndPosition = len(pattern) - 1
|
||||||
case !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars):
|
case bytes.IndexByte(parameterDelimiterChars, pattern[parameterEndPosition+1]) == -1:
|
||||||
parameterEndPosition++
|
parameterEndPosition++
|
||||||
}
|
}
|
||||||
|
|
||||||
// find constraint part if exists in the parameter part and remove it
|
// find constraint part if exists in the parameter part and remove it
|
||||||
if parameterEndPosition > 0 {
|
if parameterEndPosition > 0 {
|
||||||
parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars)
|
parameterConstraintStart = findNextNonEscapedCharsetPosition(pattern[0:parameterEndPosition], parameterConstraintStartChars)
|
||||||
parameterConstraintEnd = findLastCharsetPosition(pattern[0:parameterEndPosition+1], parameterConstraintEndChars)
|
parameterConstraintEnd = strings.LastIndexByte(pattern[0:parameterEndPosition+1], paramConstraintEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cut params part
|
// cut params part
|
||||||
processedPart := pattern[0 : parameterEndPosition+1]
|
processedPart := pattern[0 : parameterEndPosition+1]
|
||||||
|
n := parameterEndPosition + 1
|
||||||
paramName := RemoveEscapeChar(GetTrimmedParam(processedPart))
|
paramName := RemoveEscapeChar(GetTrimmedParam(processedPart))
|
||||||
|
|
||||||
// Check has constraint
|
// Check has constraint
|
||||||
|
@ -341,7 +356,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
||||||
|
|
||||||
for _, c := range userConstraints {
|
for _, c := range userConstraints {
|
||||||
start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars)
|
start := findNextNonEscapedCharsetPosition(c, parameterConstraintDataStartChars)
|
||||||
end := findLastCharsetPosition(c, parameterConstraintDataEndChars)
|
end := strings.LastIndexByte(c, paramConstraintDataEnd)
|
||||||
|
|
||||||
// Assign constraint
|
// Assign constraint
|
||||||
if start != -1 && end != -1 {
|
if start != -1 && end != -1 {
|
||||||
|
@ -384,11 +399,11 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
||||||
|
|
||||||
// add access iterator to wildcard and plus
|
// add access iterator to wildcard and plus
|
||||||
if isWildCard {
|
if isWildCard {
|
||||||
routeParser.wildCardCount++
|
parser.wildCardCount++
|
||||||
paramName += strconv.Itoa(routeParser.wildCardCount)
|
paramName += strconv.Itoa(parser.wildCardCount)
|
||||||
} else if isPlusParam {
|
} else if isPlusParam {
|
||||||
routeParser.plusCount++
|
parser.plusCount++
|
||||||
paramName += strconv.Itoa(routeParser.plusCount)
|
paramName += strconv.Itoa(parser.plusCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
segment := &routeSegment{
|
segment := &routeSegment{
|
||||||
|
@ -402,17 +417,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
||||||
segment.Constraints = constraints
|
segment.Constraints = constraints
|
||||||
}
|
}
|
||||||
|
|
||||||
return processedPart, segment
|
return n, 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findNextCharsetPosition search the next char position from the charset
|
// findNextCharsetPosition search the next char position from the charset
|
||||||
|
@ -427,18 +432,6 @@ func findNextCharsetPosition(search string, charset []byte) int {
|
||||||
return nextPosition
|
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
|
// findNextCharsetPositionConstraint search the next char position from the charset
|
||||||
// unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern
|
// unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern
|
||||||
func findNextCharsetPositionConstraint(search string, charset []byte) int {
|
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
|
// 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
|
var i, paramsIterator, partLen int
|
||||||
for _, segment := range routeParser.segs {
|
for _, segment := range parser.segs {
|
||||||
partLen = len(detectionPath)
|
partLen = len(detectionPath)
|
||||||
// check const segment
|
// check const segment
|
||||||
if !segment.IsParam {
|
if !segment.IsParam {
|
||||||
|
@ -618,7 +611,7 @@ func GetTrimmedParam(param string) string {
|
||||||
return param[start:end]
|
return param[start:end]
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveEscapeChar remove escape characters
|
// RemoveEscapeChar removes escape characters
|
||||||
func RemoveEscapeChar(word string) string {
|
func RemoveEscapeChar(word string) string {
|
||||||
b := []byte(word)
|
b := []byte(word)
|
||||||
dst := 0
|
dst := 0
|
||||||
|
@ -632,6 +625,18 @@ func RemoveEscapeChar(word string) string {
|
||||||
return string(b[:dst])
|
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 {
|
func getParamConstraintType(constraintPart string) TypeConstraint {
|
||||||
switch constraintPart {
|
switch constraintPart {
|
||||||
case ConstraintInt:
|
case ConstraintInt:
|
||||||
|
|
|
@ -217,7 +217,7 @@ func Benchmark_Path_matchParams(t *testing.B) {
|
||||||
state = "not match"
|
state = "not match"
|
||||||
}
|
}
|
||||||
t.Run(testCollection.pattern+" | "+state+" | "+c.url, func(b *testing.B) {
|
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 {
|
if match := parser.getMatch(c.url, c.url, &ctxParams, c.partialCheck); match {
|
||||||
// Get testCases from the original path
|
// Get testCases from the original path
|
||||||
matchRes = true
|
matchRes = true
|
||||||
|
@ -250,7 +250,7 @@ func Benchmark_RoutePatternMatch(t *testing.B) {
|
||||||
state = "not match"
|
state = "not match"
|
||||||
}
|
}
|
||||||
t.Run(testCollection.pattern+" | "+state+" | "+c.url, func(b *testing.B) {
|
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 {
|
if match := RoutePatternMatch(c.url, testCollection.pattern); match {
|
||||||
// Get testCases from the original path
|
// Get testCases from the original path
|
||||||
matchRes = true
|
matchRes = true
|
||||||
|
|
|
@ -178,7 +178,7 @@ func Test_Redirect_Back_WithFlashMessages(t *testing.T) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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("/")
|
err := c.Redirect().With("success", "1").With("message", "test").Back("/")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -225,7 +225,7 @@ func Test_Redirect_Route_WithFlashMessages(t *testing.T) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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")
|
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")
|
return c.SendString("user")
|
||||||
}).Name("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")
|
c.Request().URI().SetQueryString("id=1&name=tom")
|
||||||
err := c.Redirect().With("success", "1").With("message", "test").WithInput().Route("user")
|
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")
|
return c.SendString("user")
|
||||||
}).Name("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().Header.Set(HeaderContentType, MIMEApplicationForm)
|
||||||
c.Request().SetBodyString("id=1&name=tom")
|
c.Request().SetBodyString("id=1&name=tom")
|
||||||
|
@ -330,7 +330,7 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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{}
|
body := &bytes.Buffer{}
|
||||||
writer := multipart.NewWriter(body)
|
writer := multipart.NewWriter(body)
|
||||||
|
@ -376,7 +376,7 @@ func Test_Redirect_parseAndClearFlashMessages(t *testing.T) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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{
|
msgs := redirectionMsgs{
|
||||||
{
|
{
|
||||||
|
@ -464,7 +464,7 @@ func Benchmark_Redirect_Route(b *testing.B) {
|
||||||
return c.JSON(c.Params("name"))
|
return c.JSON(c.Params("name"))
|
||||||
}).Name("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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -491,7 +491,7 @@ func Benchmark_Redirect_Route_WithQueries(b *testing.B) {
|
||||||
return c.JSON(c.Params("name"))
|
return c.JSON(c.Params("name"))
|
||||||
}).Name("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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -523,7 +523,7 @@ func Benchmark_Redirect_Route_WithFlashMessages(b *testing.B) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
@ -576,7 +576,7 @@ func Benchmark_Redirect_parseAndClearFlashMessages(b *testing.B) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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)
|
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
@ -618,7 +618,7 @@ func Benchmark_Redirect_processFlashMessages(b *testing.B) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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")
|
c.Redirect().With("success", "1").With("message", "test")
|
||||||
|
|
||||||
|
@ -647,7 +647,7 @@ func Benchmark_Redirect_Messages(b *testing.B) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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)
|
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
@ -684,7 +684,7 @@ func Benchmark_Redirect_OldInputs(b *testing.B) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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)
|
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
@ -719,7 +719,7 @@ func Benchmark_Redirect_Message(b *testing.B) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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)
|
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
@ -750,7 +750,7 @@ func Benchmark_Redirect_OldInput(b *testing.B) {
|
||||||
return c.SendString("user")
|
return c.SendString("user")
|
||||||
}).Name("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)
|
val, err := testredirectionMsgs.MarshalMsg(nil)
|
||||||
require.NoError(b, err)
|
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
|
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
|
// Get stack length
|
||||||
tree, ok := app.treeStack[c.getMethodINT()][c.getTreePath()]
|
tree, ok := app.treeStack[c.getMethodInt()][c.getTreePathHash()]
|
||||||
if !ok {
|
if !ok {
|
||||||
tree = app.treeStack[c.getMethodINT()][""]
|
tree = app.treeStack[c.getMethodInt()][0]
|
||||||
}
|
}
|
||||||
lenr := len(tree) - 1
|
lenr := len(tree) - 1
|
||||||
|
|
||||||
|
@ -154,6 +154,61 @@ func (app *App[TCtx]) next(c TCtx) (bool, error) { //nolint: unparam // bool par
|
||||||
return false, err
|
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) {
|
func (app *App[TCtx]) requestHandler(rctx *fasthttp.RequestCtx) {
|
||||||
// Acquire DefaultCtx from the pool
|
// Acquire DefaultCtx from the pool
|
||||||
ctx := app.AcquireCtx(rctx)
|
ctx := app.AcquireCtx(rctx)
|
||||||
|
@ -161,7 +216,7 @@ func (app *App[TCtx]) requestHandler(rctx *fasthttp.RequestCtx) {
|
||||||
defer app.ReleaseCtx(ctx)
|
defer app.ReleaseCtx(ctx)
|
||||||
|
|
||||||
// Check if the HTTP method is valid
|
// 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
|
_ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -363,30 +418,28 @@ func (app *App[TCtx]) buildTree() *App[TCtx] {
|
||||||
|
|
||||||
// loop all the methods and stacks and create the prefix tree
|
// loop all the methods and stacks and create the prefix tree
|
||||||
for m := range app.config.RequestMethods {
|
for m := range app.config.RequestMethods {
|
||||||
tsMap := make(map[string][]*Route[TCtx])
|
tsMap := make(map[int][]*Route[TCtx])
|
||||||
for _, route := range app.stack[m] {
|
for _, route := range app.stack[m] {
|
||||||
treePath := ""
|
treePathHash := 0
|
||||||
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
|
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= maxDetectionPaths {
|
||||||
treePath = route.routeParser.segs[0].Const[:3]
|
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
|
// 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 {
|
for treePart := range tsMap {
|
||||||
if treePart != "" {
|
if treePart != 0 {
|
||||||
// merge global tree routes in current tree stack
|
// 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
|
// sort tree slices with the positions
|
||||||
slc := tsMap[treePart]
|
slc := tsMap[treePart]
|
||||||
sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
|
sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
|
||||||
}
|
}
|
||||||
|
app.treeStack[m] = tsMap
|
||||||
}
|
}
|
||||||
app.routesRefreshed = false
|
app.routesRefreshed = false
|
||||||
|
|
||||||
|
|
|
@ -600,7 +600,7 @@ func Benchmark_Router_Next(b *testing.B) {
|
||||||
var res bool
|
var res bool
|
||||||
var err error
|
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()
|
b.ResetTimer()
|
||||||
for n := 0; n < b.N; n++ {
|
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
|
// go test -v ./... -run=^$ -bench=Benchmark_Route_Match -benchmem -count=4
|
||||||
func Benchmark_Route_Match(b *testing.B) {
|
func Benchmark_Route_Match(b *testing.B) {
|
||||||
var match bool
|
var match bool
|
||||||
|
@ -825,7 +869,7 @@ func Benchmark_Router_Github_API(b *testing.B) {
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
c.URI().SetPath(routesFixture.TestRoutes[i].Path)
|
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)
|
match, err = app.next(ctx)
|
||||||
app.ReleaseCtx(ctx)
|
app.ReleaseCtx(ctx)
|
||||||
|
|
Loading…
Reference in New Issue