From 1c526892e7ef932eb412932b7421d83b174d8e73 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Thu, 18 Jul 2024 07:41:39 -0400
Subject: [PATCH 01/31] =?UTF-8?q?=F0=9F=90=9B=20bug:=20Use=20Content-Lengt?=
=?UTF-8?q?h=20for=20bytesReceived=20and=20bytesSent=20tags=20in=20Logger?=
=?UTF-8?q?=20Middleware=20in=20v2=20(#3067)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Use Content-Length for bytesSent and bytesReceived in Logger
---
middleware/logger/logger_test.go | 5 +++--
middleware/logger/tags.go | 8 +++-----
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go
index 6876a7219fa..94b68d97514 100644
--- a/middleware/logger/logger_test.go
+++ b/middleware/logger/logger_test.go
@@ -386,13 +386,14 @@ func Test_Logger_AppendUint(t *testing.T) {
}))
app.Get("/", func(c *fiber.Ctx) error {
+ c.Response().Header.SetContentLength(5)
return c.SendString("hello")
})
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
- utils.AssertEqual(t, "0 5 200", buf.String())
+ utils.AssertEqual(t, "-2 5 200", buf.String())
}
// go test -run Test_Logger_Data_Race -race
@@ -629,7 +630,7 @@ func Test_Logger_ByteSent_Streaming(t *testing.T) {
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
- utils.AssertEqual(t, "0 0 200", buf.String())
+ utils.AssertEqual(t, "-2 -1 200", buf.String())
}
// go test -run Test_Logger_EnableColors
diff --git a/middleware/logger/tags.go b/middleware/logger/tags.go
index 9a10279ecac..c348871c422 100644
--- a/middleware/logger/tags.go
+++ b/middleware/logger/tags.go
@@ -2,6 +2,7 @@ package logger
import (
"fmt"
+ "strconv"
"strings"
"github.com/gofiber/fiber/v2"
@@ -85,13 +86,10 @@ func createTagMap(cfg *Config) map[string]LogFunc {
return output.Write(c.Body())
},
TagBytesReceived: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
- return appendInt(output, len(c.Request().Body()))
+ return output.WriteString(strconv.Itoa((c.Request().Header.ContentLength())))
},
TagBytesSent: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
- if c.Response().Header.ContentLength() < 0 {
- return appendInt(output, 0)
- }
- return appendInt(output, len(c.Response().Body()))
+ return output.WriteString(strconv.Itoa((c.Response().Header.ContentLength())))
},
TagRoute: func(output Buffer, c *fiber.Ctx, data *Data, extraParam string) (int, error) {
return output.WriteString(c.Route().Path)
From 87bb93ecf089247402e7f1e4ed2423ea5c14749c Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Tue, 23 Jul 2024 02:25:25 -0400
Subject: [PATCH 02/31] v2: Update benchmark-action to v1.20.3 (#3084)
Update benchmark gh action
---
.github/workflows/benchmark.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index 42e7afd6531..79e2c0d05c2 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -36,7 +36,7 @@ jobs:
key: ${{ runner.os }}-benchmark
- name: Save Benchmark Results
- uses: benchmark-action/github-action-benchmark@v1.16.2
+ uses: benchmark-action/github-action-benchmark@v1.20.3
with:
tool: "go"
output-file-path: output.txt
From ca935c3f8f0ee28a8a8e91ddcd9b9f85dcfb0419 Mon Sep 17 00:00:00 2001
From: Giovanni Rivera
Date: Wed, 28 Aug 2024 00:01:02 -0700
Subject: [PATCH 03/31] =?UTF-8?q?=F0=9F=93=9A=20Doc:=20Add=20detailed=20do?=
=?UTF-8?q?cumentation=20for=20the=20templates=20guide=20(#3113)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Organize and reword templates guide
* Add full example to templates guide
* Add advanced templating section to template guide
* Add template repo link and fix typo in Templates guide
- Add link to https://github.com/gofiber/template in Templates Guide
- Fix typo: missing period in info block about ctx.Render()
* Update docs/guide/templates.md
* Update docs/guide/templates.md
---------
Co-authored-by: RW
---
docs/guide/templates.md | 228 ++++++++++++++++++++++++++++++++++------
1 file changed, 197 insertions(+), 31 deletions(-)
diff --git a/docs/guide/templates.md b/docs/guide/templates.md
index cc52c2d9ac0..393df1bb8dd 100644
--- a/docs/guide/templates.md
+++ b/docs/guide/templates.md
@@ -8,57 +8,212 @@ sidebar_position: 3
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
-## Template interfaces
+Templates are a great tool to render dynamic content without using a separate frontend framework.
-Fiber provides a Views interface to provide your own template engine:
+## Template Engines
-
-
+Fiber allows you to provide a custom template engine at app initialization.
```go
+app := fiber.New(fiber.Config{
+ // Pass in Views Template Engine
+ Views: engine,
+
+ // Default global path to search for views (can be overriden when calling Render())
+ ViewsLayout: "layouts/main",
+
+ // Enables/Disables access to `ctx.Locals()` entries in rendered views
+ // (defaults to false)
+ PassLocalsToViews: false,
+})
+```
+
+
+### Supported Engines
+
+The Fiber team maintains a [templates](https://docs.gofiber.io/template) package that provides wrappers for multiple template engines:
+
+* [ace](https://docs.gofiber.io/template/ace/)
+* [amber](https://docs.gofiber.io/template/amber/)
+* [django](https://docs.gofiber.io/template/django/)
+* [handlebars](https://docs.gofiber.io/template/handlebars)
+* [html](https://docs.gofiber.io/template/html)
+* [jet](https://docs.gofiber.io/template/jet)
+* [mustache](https://docs.gofiber.io/template/mustache)
+* [pug](https://docs.gofiber.io/template/pug)
+* [slim](https://docs.gofiber.io/template/slim)
+
+:::info
+Custom template engines can implement the `Views` interface to be supported in Fiber.
+:::
+
+
+```go title="Views interface"
type Views interface {
+ // Fiber executes Load() on app initialization to load/parse the templates
Load() error
+
+ // Outputs a template to the provided buffer using the provided template,
+ // template name, and binded data
Render(io.Writer, string, interface{}, ...string) error
}
```
+
+:::note
+The `Render` method is linked to the [**ctx.Render\(\)**](../api/ctx.md#render) function that accepts a template name and binding data.
+:::
+
+
+## Rendering Templates
+
+Once an engine is set up, a route handler can call the [**ctx.Render\(\)**](../api/ctx.md#render) function with a template name and binded data to send the rendered template.
+
+```go title="Signature"
+func (c *Ctx) Render(name string, bind Map, layouts ...string) error
+```
+
+:::info
+By default, [**ctx.Render\(\)**](../api/ctx.md#render) searches for the template name in the `ViewsLayout` path. To override this setting, provide the path(s) in the `layouts` argument.
+:::
+
+
+
+
+
+
+```go
+app.Get("/", func(c *fiber.Ctx) error {
+ return c.Render("index", fiber.Map{
+ "Title": "Hello, World!",
+ })
+
+})
+```
+
+
+
+
+```html
+
+
+
+ {{.Title}}
+
+
+```
+
+
+
-`Views` interface contains a `Load` and `Render` method, `Load` is executed by Fiber on app initialization to load/parse the templates.
+:::caution
+If the Fiber config option `PassLocalsToViews` is enabled, then all locals set using `ctx.Locals(key, value)` will be passed to the template. It is important to avoid clashing keys when using this setting.
+:::
+
+## Advanced Templating
+
+### Custom Functions
+
+Fiber supports adding custom functions to templates.
+
+#### AddFunc
+
+Adds a global function to all templates.
+
+```go title="Signature"
+func (e *Engine) AddFunc(name string, fn interface{}) IEngineCore
+```
+
+
+
```go
-// Pass engine to Fiber's Views Engine
+// Add `ToUpper` to engine
+engine := html.New("./views", ".html")
+engine.AddFunc("ToUpper", func(s string) string {
+ return strings.ToUpper(s)
+}
+
+// Initialize Fiber App
app := fiber.New(fiber.Config{
Views: engine,
- // Views Layout is the global layout for all template render until override on Render function.
- ViewsLayout: "layouts/main"
+})
+
+app.Get("/", func (c *fiber.Ctx) error {
+ return c.Render("index", fiber.Map{
+ "Content": "hello, world!"
+ })
})
```
-The `Render` method is linked to the [**ctx.Render\(\)**](../api/ctx.md#render) function that accepts a template name and binding data. It will use global layout if layout is not being defined in `Render` function.
-If the Fiber config option `PassLocalsToViews` is enabled, then all locals set using `ctx.Locals(key, value)` will be passed to the template.
+
+
+
+```html
+
+
+
+ This will be in {{ToUpper "all caps"}}:
+ {{ToUpper .Content}}
+
+
+```
+
+
+
+
+#### AddFuncMap
+
+Adds a Map of functions (keyed by name) to all templates.
+
+```go title="Signature"
+func (e *Engine) AddFuncMap(m map[string]interface{}) IEngineCore
+```
+
+
+
```go
-app.Get("/", func(c *fiber.Ctx) error {
+// Add `ToUpper` to engine
+engine := html.New("./views", ".html")
+engine.AddFuncMap(map[string]interface{}{
+ "ToUpper": func(s string) string {
+ return strings.ToUpper(s)
+ },
+})
+
+// Initialize Fiber App
+app := fiber.New(fiber.Config{
+ Views: engine,
+})
+
+app.Get("/", func (c *fiber.Ctx) error {
return c.Render("index", fiber.Map{
- "hello": "world",
- });
+ "Content": "hello, world!"
+ })
})
```
-## Engines
+
+
-Fiber team maintains [templates](https://docs.gofiber.io/template) package that provides wrappers for multiple template engines:
+```html
+
+
+
+ This will be in {{ToUpper "all caps"}}:
+ {{ToUpper .Content}}
+
+
+```
-* [ace](https://docs.gofiber.io/template/ace/)
-* [amber](https://docs.gofiber.io/template/amber/)
-* [django](https://docs.gofiber.io/template/django/)
-* [handlebars](https://docs.gofiber.io/template/handlebars)
-* [html](https://docs.gofiber.io/template/html)
-* [jet](https://docs.gofiber.io/template/jet)
-* [mustache](https://docs.gofiber.io/template/mustache)
-* [pug](https://docs.gofiber.io/template/pug)
-* [slim](https://docs.gofiber.io/template/slim)
+
+
+
+- For more advanced template documentation, please visit the [gofiber/template GitHub Repository](https://github.com/gofiber/template).
+
+## Full Example
@@ -75,7 +230,8 @@ import (
func main() {
// Initialize standard Go html template engine
engine := html.New("./views", ".html")
- // If you want other engine, just replace with following
+ // If you want to use another engine,
+ // just replace with following:
// Create a new engine with django
// engine := django.New("./views", ".django")
@@ -85,8 +241,10 @@ func main() {
app.Get("/", func(c *fiber.Ctx) error {
// Render index template
return c.Render("index", fiber.Map{
- "Title": "Hello, World!",
- })
+ "Title": "Go Fiber Template Example",
+ "Description": "An example template",
+ "Greeting": "Hello, world!",
+ });
})
log.Fatal(app.Listen(":3000"))
@@ -95,12 +253,20 @@ func main() {
-```markup
+```html
-
- {{.Title}}
-
+
+
+ {{.Title}}
+
+
+
+ {{.Title}}
+ {{.Greeting}}
+
```
+
+
From bfcf91dab87f9219c09f8f387690507033fb925d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9?=
Date: Fri, 30 Aug 2024 17:22:07 +0200
Subject: [PATCH 04/31] fix template markdown
---
docs/guide/templates.md | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/docs/guide/templates.md b/docs/guide/templates.md
index 393df1bb8dd..18dd0e386c3 100644
--- a/docs/guide/templates.md
+++ b/docs/guide/templates.md
@@ -28,7 +28,6 @@ app := fiber.New(fiber.Config{
})
```
-
### Supported Engines
The Fiber team maintains a [templates](https://docs.gofiber.io/template) package that provides wrappers for multiple template engines:
@@ -47,7 +46,6 @@ The Fiber team maintains a [templates](https://docs.gofiber.io/template) package
Custom template engines can implement the `Views` interface to be supported in Fiber.
:::
-
```go title="Views interface"
type Views interface {
// Fiber executes Load() on app initialization to load/parse the templates
@@ -63,7 +61,6 @@ type Views interface {
The `Render` method is linked to the [**ctx.Render\(\)**](../api/ctx.md#render) function that accepts a template name and binding data.
:::
-
## Rendering Templates
Once an engine is set up, a route handler can call the [**ctx.Render\(\)**](../api/ctx.md#render) function with a template name and binded data to send the rendered template.
@@ -76,9 +73,7 @@ func (c *Ctx) Render(name string, bind Map, layouts ...string) error
By default, [**ctx.Render\(\)**](../api/ctx.md#render) searches for the template name in the `ViewsLayout` path. To override this setting, provide the path(s) in the `layouts` argument.
:::
-
-
```go
@@ -211,7 +206,7 @@ app.Get("/", func (c *fiber.Ctx) error {
-- For more advanced template documentation, please visit the [gofiber/template GitHub Repository](https://github.com/gofiber/template).
+* For more advanced template documentation, please visit the [gofiber/template GitHub Repository](https://github.com/gofiber/template).
## Full Example
@@ -250,6 +245,7 @@ func main() {
log.Fatal(app.Listen(":3000"))
}
```
+
@@ -269,4 +265,3 @@ func main() {
-
From cb06bc5f4cf27ae8abbfac227b8f4b558d97cf69 Mon Sep 17 00:00:00 2001
From: Vaibhav Gupta
Date: Fri, 6 Sep 2024 11:32:02 +0530
Subject: [PATCH 05/31] =?UTF-8?q?=F0=9F=A9=B9=20Fix:=20handle=20un-matched?=
=?UTF-8?q?=20open=20brackets=20in=20the=20query=20params=20(#3121)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add logic for counting open brackets
* Add UTs
* update increment/decrement syntax with ++/--
* Update UT to remove duplicate
---
ctx.go | 21 +++++++++++++++++----
ctx_test.go | 8 ++++++++
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/ctx.go b/ctx.go
index 2589f0e3e70..6f414ed56a9 100644
--- a/ctx.go
+++ b/ctx.go
@@ -1306,15 +1306,24 @@ func parseParamSquareBrackets(k string) (string, error) {
defer bytebufferpool.Put(bb)
kbytes := []byte(k)
+ openBracketsCount := 0
for i, b := range kbytes {
- if b == '[' && kbytes[i+1] != ']' {
- if err := bb.WriteByte('.'); err != nil {
- return "", fmt.Errorf("failed to write: %w", err)
+ if b == '[' {
+ openBracketsCount++
+ if i+1 < len(kbytes) && kbytes[i+1] != ']' {
+ if err := bb.WriteByte('.'); err != nil {
+ return "", fmt.Errorf("failed to write: %w", err)
+ }
}
+ continue
}
- if b == '[' || b == ']' {
+ if b == ']' {
+ openBracketsCount--
+ if openBracketsCount < 0 {
+ return "", errors.New("unmatched brackets")
+ }
continue
}
@@ -1323,6 +1332,10 @@ func parseParamSquareBrackets(k string) (string, error) {
}
}
+ if openBracketsCount > 0 {
+ return "", errors.New("unmatched brackets")
+ }
+
return bb.String(), nil
}
diff --git a/ctx_test.go b/ctx_test.go
index a278d2f2500..76ee3fa5f74 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -4508,6 +4508,10 @@ func Test_Ctx_QueryParser(t *testing.T) {
utils.AssertEqual(t, nil, c.QueryParser(empty))
utils.AssertEqual(t, 0, len(empty.Hobby))
+ c.Request().URI().SetQueryString("id=1&name[=tom")
+ q = new(Query)
+ utils.AssertEqual(t, "unmatched brackets", c.QueryParser(q).Error())
+
type Query2 struct {
Bool bool
ID int
@@ -4790,6 +4794,10 @@ func Test_Ctx_QueryParser_Schema(t *testing.T) {
utils.AssertEqual(t, "doe", cq.Data[1].Name)
utils.AssertEqual(t, 12, cq.Data[1].Age)
+ c.Request().URI().SetQueryString("data[0][name]=john&data[0][age]=10&data[1]name]=doe&data[1][age]=12")
+ cq = new(CollectionQuery)
+ utils.AssertEqual(t, "unmatched brackets", c.QueryParser(cq).Error())
+
c.Request().URI().SetQueryString("data.0.name=john&data.0.age=10&data.1.name=doe&data.1.age=12")
cq = new(CollectionQuery)
utils.AssertEqual(t, nil, c.QueryParser(cq))
From 6e7411403adc3f82cb941b6a038566006ed6e270 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Fri, 6 Sep 2024 14:18:20 -0400
Subject: [PATCH 06/31] v2: Add CODEOWNERS file (#3124)
Add CODEOWNERS file
---
.github/CODEOWNERS | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .github/CODEOWNERS
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000000..957d1b1926b
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @gofiber/maintainers
From 8c84b0fd8a07537572e607057b309681922a0b46 Mon Sep 17 00:00:00 2001
From: Aaron Zingerle
Date: Mon, 14 Oct 2024 15:04:25 +0200
Subject: [PATCH 07/31] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20Middleware/CORS=20R?=
=?UTF-8?q?emove=20Scheme=20Restriction=20(#3168)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
🩹 Fix: Middleware/CORS Remove Scheme Restriction (gofiber#3160)
Co-authored-by: Aaron Zingerle
---
middleware/cors/utils.go | 5 ----
middleware/cors/utils_test.go | 49 ++++++++++++++++++-----------------
2 files changed, 25 insertions(+), 29 deletions(-)
diff --git a/middleware/cors/utils.go b/middleware/cors/utils.go
index 443e6489034..42780103057 100644
--- a/middleware/cors/utils.go
+++ b/middleware/cors/utils.go
@@ -37,11 +37,6 @@ func normalizeOrigin(origin string) (bool, string) {
return false, ""
}
- // Validate the scheme is either http or https
- if parsedOrigin.Scheme != "http" && parsedOrigin.Scheme != "https" {
- return false, ""
- }
-
// Don't allow a wildcard with a protocol
// wildcards cannot be used within any other value. For example, the following header is not valid:
// Access-Control-Allow-Origin: https://*
diff --git a/middleware/cors/utils_test.go b/middleware/cors/utils_test.go
index 47dddc2c693..aff50defa25 100644
--- a/middleware/cors/utils_test.go
+++ b/middleware/cors/utils_test.go
@@ -13,30 +13,31 @@ func Test_normalizeOrigin(t *testing.T) {
expectedValid bool
expectedOrigin string
}{
- {"http://example.com", true, "http://example.com"}, // Simple case should work.
- {"http://example.com/", true, "http://example.com"}, // Trailing slash should be removed.
- {"http://example.com:3000", true, "http://example.com:3000"}, // Port should be preserved.
- {"http://example.com:3000/", true, "http://example.com:3000"}, // Trailing slash should be removed.
- {"http://", false, ""}, // Invalid origin should not be accepted.
- {"file:///etc/passwd", false, ""}, // File scheme should not be accepted.
- {"https://*example.com", false, ""}, // Wildcard domain should not be accepted.
- {"http://*.example.com", false, ""}, // Wildcard subdomain should not be accepted.
- {"http://example.com/path", false, ""}, // Path should not be accepted.
- {"http://example.com?query=123", false, ""}, // Query should not be accepted.
- {"http://example.com#fragment", false, ""}, // Fragment should not be accepted.
- {"http://localhost", true, "http://localhost"}, // Localhost should be accepted.
- {"http://127.0.0.1", true, "http://127.0.0.1"}, // IPv4 address should be accepted.
- {"http://[::1]", true, "http://[::1]"}, // IPv6 address should be accepted.
- {"http://[::1]:8080", true, "http://[::1]:8080"}, // IPv6 address with port should be accepted.
- {"http://[::1]:8080/", true, "http://[::1]:8080"}, // IPv6 address with port and trailing slash should be accepted.
- {"http://[::1]:8080/path", false, ""}, // IPv6 address with port and path should not be accepted.
- {"http://[::1]:8080?query=123", false, ""}, // IPv6 address with port and query should not be accepted.
- {"http://[::1]:8080#fragment", false, ""}, // IPv6 address with port and fragment should not be accepted.
- {"http://[::1]:8080/path?query=123#fragment", false, ""}, // IPv6 address with port, path, query, and fragment should not be accepted.
- {"http://[::1]:8080/path?query=123#fragment/", false, ""}, // IPv6 address with port, path, query, fragment, and trailing slash should not be accepted.
- {"http://[::1]:8080/path?query=123#fragment/invalid", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment should not be accepted.
- {"http://[::1]:8080/path?query=123#fragment/invalid/", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with trailing slash should not be accepted.
- {"http://[::1]:8080/path?query=123#fragment/invalid/segment", false, ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with additional segment should not be accepted.
+ {origin: "http://example.com", expectedValid: true, expectedOrigin: "http://example.com"}, // Simple case should work.
+ {origin: "http://example.com/", expectedValid: true, expectedOrigin: "http://example.com"}, // Trailing slash should be removed.
+ {origin: "http://example.com:3000", expectedValid: true, expectedOrigin: "http://example.com:3000"}, // Port should be preserved.
+ {origin: "http://example.com:3000/", expectedValid: true, expectedOrigin: "http://example.com:3000"}, // Trailing slash should be removed.
+ {origin: "app://example.com/", expectedValid: true, expectedOrigin: "app://example.com"}, // App scheme should be accepted.
+ {origin: "http://", expectedValid: false, expectedOrigin: ""}, // Invalid origin should not be accepted.
+ {origin: "file:///etc/passwd", expectedValid: false, expectedOrigin: ""}, // File scheme should not be accepted.
+ {origin: "https://*example.com", expectedValid: false, expectedOrigin: ""}, // Wildcard domain should not be accepted.
+ {origin: "http://*.example.com", expectedValid: false, expectedOrigin: ""}, // Wildcard subdomain should not be accepted.
+ {origin: "http://example.com/path", expectedValid: false, expectedOrigin: ""}, // Path should not be accepted.
+ {origin: "http://example.com?query=123", expectedValid: false, expectedOrigin: ""}, // Query should not be accepted.
+ {origin: "http://example.com#fragment", expectedValid: false, expectedOrigin: ""}, // Fragment should not be accepted.
+ {origin: "http://localhost", expectedValid: true, expectedOrigin: "http://localhost"}, // Localhost should be accepted.
+ {origin: "http://127.0.0.1", expectedValid: true, expectedOrigin: "http://127.0.0.1"}, // IPv4 address should be accepted.
+ {origin: "http://[::1]", expectedValid: true, expectedOrigin: "http://[::1]"}, // IPv6 address should be accepted.
+ {origin: "http://[::1]:8080", expectedValid: true, expectedOrigin: "http://[::1]:8080"}, // IPv6 address with port should be accepted.
+ {origin: "http://[::1]:8080/", expectedValid: true, expectedOrigin: "http://[::1]:8080"}, // IPv6 address with port and trailing slash should be accepted.
+ {origin: "http://[::1]:8080/path", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and path should not be accepted.
+ {origin: "http://[::1]:8080?query=123", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and query should not be accepted.
+ {origin: "http://[::1]:8080#fragment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port and fragment should not be accepted.
+ {origin: "http://[::1]:8080/path?query=123#fragment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, and fragment should not be accepted.
+ {origin: "http://[::1]:8080/path?query=123#fragment/", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, and trailing slash should not be accepted.
+ {origin: "http://[::1]:8080/path?query=123#fragment/invalid", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment should not be accepted.
+ {origin: "http://[::1]:8080/path?query=123#fragment/invalid/", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with trailing slash should not be accepted.
+ {origin: "http://[::1]:8080/path?query=123#fragment/invalid/segment", expectedValid: false, expectedOrigin: ""}, // IPv6 address with port, path, query, fragment, trailing slash, and invalid segment with additional segment should not be accepted.
}
for _, tc := range testCases {
From 56ff2de85895bb8eee062700fd205085bf0864c3 Mon Sep 17 00:00:00 2001
From: nickajacks1 <128185314+nickajacks1@users.noreply.github.com>
Date: Thu, 12 Dec 2024 23:28:36 -0800
Subject: [PATCH 08/31] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Respect=20Immutable?=
=?UTF-8?q?=20config=20for=20Body()=20(#3246)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 🐛 fix: respect Immutable config for Body()
* ci: add go 1.22 and 1.23 to test matrix
---
.github/workflows/test.yml | 2 +-
ctx.go | 15 +++++++++++++--
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a186908aa0b..00566dd9c00 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -17,7 +17,7 @@ jobs:
Build:
strategy:
matrix:
- go-version: [1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x]
+ go-version: [1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, 1.23.x]
platform: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
diff --git a/ctx.go b/ctx.go
index 6f414ed56a9..c7caada7020 100644
--- a/ctx.go
+++ b/ctx.go
@@ -268,7 +268,7 @@ func (c *Ctx) BaseURL() string {
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead.
func (c *Ctx) BodyRaw() []byte {
- return c.fasthttp.Request.Body()
+ return c.getBody()
}
func (c *Ctx) tryDecodeBodyInOrder(
@@ -340,7 +340,7 @@ func (c *Ctx) Body() []byte {
// rule defined at: https://www.rfc-editor.org/rfc/rfc9110#section-8.4-5
encodingOrder = getSplicedStrList(headerEncoding, encodingOrder)
if len(encodingOrder) == 0 {
- return c.fasthttp.Request.Body()
+ return c.getBody()
}
var decodesRealized uint8
@@ -354,6 +354,9 @@ func (c *Ctx) Body() []byte {
return []byte(err.Error())
}
+ if c.app.config.Immutable {
+ return utils.CopyBytes(body)
+ }
return body
}
@@ -2004,3 +2007,11 @@ func (*Ctx) isLocalHost(address string) bool {
func (c *Ctx) IsFromLocal() bool {
return c.isLocalHost(c.fasthttp.RemoteIP().String())
}
+
+func (c *Ctx) getBody() []byte {
+ if c.app.config.Immutable {
+ return utils.CopyBytes(c.fasthttp.Request.Body())
+ }
+
+ return c.fasthttp.Request.Body()
+}
From c9ff17d796ea9fea65408b8c8017d57720098d7b Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Wed, 18 Dec 2024 09:40:07 -0500
Subject: [PATCH 09/31] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20Update=20dependen?=
=?UTF-8?q?cies=20(#3254)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Update v2 dependencies
* Try fasthttp v1.55.0
* Try fasthttp v1.54.0
* Try fasthttp v1.53.0
* Try fasthttp v1.52.0
* Try fasthttp v1.51.0
* Add Makefile
* Bump msgp to v1.2.5
---
Makefile | 55 +++++++++++++++++++++++++++++++++++++++++++
go.mod | 16 +++++++------
go.sum | 64 ++++++++++++++++----------------------------------
router_test.go | 21 +++++++++++++++++
4 files changed, 105 insertions(+), 51 deletions(-)
create mode 100644 Makefile
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000000..d2191043831
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,55 @@
+## help: 💡 Display available commands
+.PHONY: help
+help:
+ @echo '⚡️ GoFiber/Fiber Development:'
+ @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
+
+## audit: 🚀 Conduct quality checks
+.PHONY: audit
+audit:
+ go mod verify
+ go vet ./...
+ go run golang.org/x/vuln/cmd/govulncheck@latest ./...
+
+## benchmark: 📈 Benchmark code performance
+.PHONY: benchmark
+benchmark:
+ go test ./... -benchmem -bench=. -run=^Benchmark_$
+
+## coverage: ☂️ Generate coverage report
+.PHONY: coverage
+coverage:
+ go run gotest.tools/gotestsum@latest -f testname -- ./... -race -count=1 -coverprofile=/tmp/coverage.out -covermode=atomic
+ go tool cover -html=/tmp/coverage.out
+
+## format: 🎨 Fix code format issues
+.PHONY: format
+format:
+ go run mvdan.cc/gofumpt@latest -w -l .
+
+## lint: 🚨 Run lint checks
+.PHONY: lint
+lint:
+ go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0 run ./...
+
+## test: 🚦 Execute all tests
+.PHONY: test
+test:
+ go run gotest.tools/gotestsum@latest -f testname -- ./... -race -count=1 -shuffle=on
+
+## longtest: 🚦 Execute all tests 10x
+.PHONY: longtest
+longtest:
+ go run gotest.tools/gotestsum@latest -f testname -- ./... -race -count=15 -shuffle=on
+
+## tidy: 📌 Clean and tidy dependencies
+.PHONY: tidy
+tidy:
+ go mod tidy -v
+
+## generate: ⚡️ Generate msgp && interface implementations
+.PHONY: generate
+generate:
+ go install github.com/tinylib/msgp@latest
+ go install github.com/vburenin/ifacemaker@975a95966976eeb2d4365a7fb236e274c54da64c
+ go generate ./...
diff --git a/go.mod b/go.mod
index 4112a1c4f1c..38efaa551ce 100644
--- a/go.mod
+++ b/go.mod
@@ -3,20 +3,22 @@ module github.com/gofiber/fiber/v2
go 1.20
require (
- github.com/google/uuid v1.5.0
+ github.com/google/uuid v1.6.0
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20
- github.com/mattn/go-runewidth v0.0.15
- github.com/tinylib/msgp v1.1.8
+ github.com/mattn/go-runewidth v0.0.16
+ github.com/tinylib/msgp v1.2.5
github.com/valyala/bytebufferpool v1.0.0
github.com/valyala/fasthttp v1.51.0
- golang.org/x/sys v0.15.0
+ golang.org/x/sys v0.28.0
)
require (
- github.com/andybalholm/brotli v1.0.5 // indirect
- github.com/klauspost/compress v1.17.0 // indirect
- github.com/philhofer/fwd v1.1.2 // indirect
+ github.com/andybalholm/brotli v1.1.0 // indirect
+ github.com/klauspost/compress v1.17.9 // indirect
+ github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
+ golang.org/x/mod v0.18.0 // indirect
+ golang.org/x/tools v0.22.0 // indirect
)
diff --git a/go.sum b/go.sum
index f000e1cacb2..06b81bad6fa 100644
--- a/go.sum
+++ b/go.sum
@@ -1,59 +1,35 @@
-github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
-github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
-github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
-github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
-github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
+github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
-github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
-github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
+github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
-github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
+github.com/tinylib/msgp v1.1.3 h1:3giwAkmtaEDLSV0MdO1lDLuPgklgPzmk8H9+So2BVfA=
+github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
+github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
+golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
diff --git a/router_test.go b/router_test.go
index 6a43db59378..b47b025efe8 100644
--- a/router_test.go
+++ b/router_test.go
@@ -868,3 +868,24 @@ type routeJSON struct {
TestRoutes []testRoute `json:"test_routes"`
GithubAPI []testRoute `json:"github_api"`
}
+
+// go test -v ./... -run=^$ -bench=Benchmark_Router_Next_Default -benchmem -count=4
+func Benchmark_Router_Next_Default(b *testing.B) {
+ app := New()
+ 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)
+ }
+}
From 47be68142a25475ba6ff84b270395ebd7b691cbb Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Sat, 21 Dec 2024 04:51:16 -0500
Subject: [PATCH 10/31] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20Add=20parallel=20?=
=?UTF-8?q?benchmark=20for=20Next()=20(#3259)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Add parallel benchmark for Next()
* Create RequestCtx outside loop
---
router_test.go | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/router_test.go b/router_test.go
index b47b025efe8..8d1e40cbd0b 100644
--- a/router_test.go
+++ b/router_test.go
@@ -889,3 +889,26 @@ func Benchmark_Router_Next_Default(b *testing.B) {
h(fctx)
}
}
+
+// go test -v ./... -run=^$ -bench=Benchmark_Router_Next_Default_Parallel -benchmem -count=4
+func Benchmark_Router_Next_Default_Parallel(b *testing.B) {
+ app := New()
+ 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)
+ }
+ })
+}
From 7eb9d255489192343c647f1d910abbb140705474 Mon Sep 17 00:00:00 2001
From: RW
Date: Tue, 31 Dec 2024 16:56:18 +0100
Subject: [PATCH 11/31] Support Square Bracket Notation in Multipart Form data
(#3268)
* Feature Request: Support Square Bracket Notation in Multipart Form Data #3224
* Feature Request: Support Square Bracket Notation in Multipart Form Data #3224
---
ctx.go | 113 ++++++++++++++--------------------------------------
ctx_test.go | 42 +++++++++++++++++++
go.mod | 2 -
go.sum | 6 ---
helpers.go | 73 +++++++++++++++++++++++++++++++++
5 files changed, 144 insertions(+), 92 deletions(-)
diff --git a/ctx.go b/ctx.go
index c7caada7020..c0a4413ab03 100644
--- a/ctx.go
+++ b/ctx.go
@@ -406,28 +406,30 @@ func (c *Ctx) BodyParser(out interface{}) error {
k := c.app.getString(key)
v := c.app.getString(val)
- if strings.Contains(k, "[") {
- k, err = parseParamSquareBrackets(k)
- }
-
- if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, bodyTag) {
- values := strings.Split(v, ",")
- for i := 0; i < len(values); i++ {
- data[k] = append(data[k], values[i])
- }
- } else {
- data[k] = append(data[k], v)
- }
+ err = formatParserData(out, data, bodyTag, k, v, c.app.config.EnableSplittingOnParsers, true)
})
+ if err != nil {
+ return err
+ }
+
return c.parseToStruct(bodyTag, out, data)
}
if strings.HasPrefix(ctype, MIMEMultipartForm) {
- data, err := c.fasthttp.MultipartForm()
+ multipartForm, err := c.fasthttp.MultipartForm()
if err != nil {
return err
}
- return c.parseToStruct(bodyTag, out, data.Value)
+
+ data := make(map[string][]string)
+ for key, values := range multipartForm.Value {
+ err = formatParserData(out, data, bodyTag, key, values, c.app.config.EnableSplittingOnParsers, true)
+ if err != nil {
+ return err
+ }
+ }
+
+ return c.parseToStruct(bodyTag, out, data)
}
if strings.HasPrefix(ctype, MIMETextXML) || strings.HasPrefix(ctype, MIMEApplicationXML) {
if err := xml.Unmarshal(c.Body(), out); err != nil {
@@ -531,18 +533,7 @@ func (c *Ctx) CookieParser(out interface{}) error {
k := c.app.getString(key)
v := c.app.getString(val)
- if strings.Contains(k, "[") {
- k, err = parseParamSquareBrackets(k)
- }
-
- if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, cookieTag) {
- values := strings.Split(v, ",")
- for i := 0; i < len(values); i++ {
- data[k] = append(data[k], values[i])
- }
- } else {
- data[k] = append(data[k], v)
- }
+ err = formatParserData(out, data, cookieTag, k, v, c.app.config.EnableSplittingOnParsers, true)
})
if err != nil {
return err
@@ -1283,18 +1274,7 @@ func (c *Ctx) QueryParser(out interface{}) error {
k := c.app.getString(key)
v := c.app.getString(val)
- if strings.Contains(k, "[") {
- k, err = parseParamSquareBrackets(k)
- }
-
- if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, queryTag) {
- values := strings.Split(v, ",")
- for i := 0; i < len(values); i++ {
- data[k] = append(data[k], values[i])
- }
- } else {
- data[k] = append(data[k], v)
- }
+ err = formatParserData(out, data, queryTag, k, v, c.app.config.EnableSplittingOnParsers, true)
})
if err != nil {
@@ -1304,61 +1284,26 @@ func (c *Ctx) QueryParser(out interface{}) error {
return c.parseToStruct(queryTag, out, data)
}
-func parseParamSquareBrackets(k string) (string, error) {
- bb := bytebufferpool.Get()
- defer bytebufferpool.Put(bb)
-
- kbytes := []byte(k)
- openBracketsCount := 0
-
- for i, b := range kbytes {
- if b == '[' {
- openBracketsCount++
- if i+1 < len(kbytes) && kbytes[i+1] != ']' {
- if err := bb.WriteByte('.'); err != nil {
- return "", fmt.Errorf("failed to write: %w", err)
- }
- }
- continue
- }
-
- if b == ']' {
- openBracketsCount--
- if openBracketsCount < 0 {
- return "", errors.New("unmatched brackets")
- }
- continue
- }
-
- if err := bb.WriteByte(b); err != nil {
- return "", fmt.Errorf("failed to write: %w", err)
- }
- }
-
- if openBracketsCount > 0 {
- return "", errors.New("unmatched brackets")
- }
-
- return bb.String(), nil
-}
-
// ReqHeaderParser binds the request header strings to a struct.
func (c *Ctx) ReqHeaderParser(out interface{}) error {
data := make(map[string][]string)
+ var err error
+
c.fasthttp.Request.Header.VisitAll(func(key, val []byte) {
+ if err != nil {
+ return
+ }
+
k := c.app.getString(key)
v := c.app.getString(val)
- if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, reqHeaderTag) {
- values := strings.Split(v, ",")
- for i := 0; i < len(values); i++ {
- data[k] = append(data[k], values[i])
- }
- } else {
- data[k] = append(data[k], v)
- }
+ err = formatParserData(out, data, reqHeaderTag, k, v, c.app.config.EnableSplittingOnParsers, false)
})
+ if err != nil {
+ return err
+ }
+
return c.parseToStruct(reqHeaderTag, out, data)
}
diff --git a/ctx_test.go b/ctx_test.go
index 76ee3fa5f74..6a3998f7fa2 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -610,6 +610,48 @@ func Test_Ctx_BodyParser(t *testing.T) {
utils.AssertEqual(t, 2, len(cq.Data))
utils.AssertEqual(t, "john", cq.Data[0].Name)
utils.AssertEqual(t, "doe", cq.Data[1].Name)
+
+ t.Run("MultipartCollectionQueryDotNotation", func(t *testing.T) {
+ c := app.AcquireCtx(&fasthttp.RequestCtx{})
+ c.Request().Reset()
+
+ buf := &bytes.Buffer{}
+ writer := multipart.NewWriter(buf)
+ utils.AssertEqual(t, nil, writer.WriteField("data.0.name", "john"))
+ utils.AssertEqual(t, nil, writer.WriteField("data.1.name", "doe"))
+ utils.AssertEqual(t, nil, writer.Close())
+
+ c.Request().Header.SetContentType(writer.FormDataContentType())
+ c.Request().SetBody(buf.Bytes())
+ c.Request().Header.SetContentLength(len(c.Body()))
+
+ cq := new(CollectionQuery)
+ utils.AssertEqual(t, nil, c.BodyParser(cq))
+ utils.AssertEqual(t, len(cq.Data), 2)
+ utils.AssertEqual(t, "john", cq.Data[0].Name)
+ utils.AssertEqual(t, "doe", cq.Data[1].Name)
+ })
+
+ t.Run("MultipartCollectionQuerySquareBrackets", func(t *testing.T) {
+ c := app.AcquireCtx(&fasthttp.RequestCtx{})
+ c.Request().Reset()
+
+ buf := &bytes.Buffer{}
+ writer := multipart.NewWriter(buf)
+ utils.AssertEqual(t, nil, writer.WriteField("data[0][name]", "john"))
+ utils.AssertEqual(t, nil, writer.WriteField("data[1][name]", "doe"))
+ utils.AssertEqual(t, nil, writer.Close())
+
+ c.Request().Header.SetContentType(writer.FormDataContentType())
+ c.Request().SetBody(buf.Bytes())
+ c.Request().Header.SetContentLength(len(c.Body()))
+
+ cq := new(CollectionQuery)
+ utils.AssertEqual(t, nil, c.BodyParser(cq))
+ utils.AssertEqual(t, len(cq.Data), 2)
+ utils.AssertEqual(t, "john", cq.Data[0].Name)
+ utils.AssertEqual(t, "doe", cq.Data[1].Name)
+ })
}
func Test_Ctx_ParamParser(t *testing.T) {
diff --git a/go.mod b/go.mod
index 38efaa551ce..4a3b38df97e 100644
--- a/go.mod
+++ b/go.mod
@@ -19,6 +19,4 @@ require (
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
- golang.org/x/mod v0.18.0 // indirect
- golang.org/x/tools v0.22.0 // indirect
)
diff --git a/go.sum b/go.sum
index 06b81bad6fa..67a47b010ac 100644
--- a/go.sum
+++ b/go.sum
@@ -15,8 +15,6 @@ github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1Gsh
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/tinylib/msgp v1.1.3 h1:3giwAkmtaEDLSV0MdO1lDLuPgklgPzmk8H9+So2BVfA=
-github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@@ -25,11 +23,7 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
-golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
-golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
-golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
diff --git a/helpers.go b/helpers.go
index dd8de15f91c..2896c2a47d9 100644
--- a/helpers.go
+++ b/helpers.go
@@ -7,6 +7,7 @@ package fiber
import (
"bytes"
"crypto/tls"
+ "errors"
"fmt"
"hash/crc32"
"io"
@@ -1151,3 +1152,75 @@ func IndexRune(str string, needle int32) bool {
}
return false
}
+
+func parseParamSquareBrackets(k string) (string, error) {
+ bb := bytebufferpool.Get()
+ defer bytebufferpool.Put(bb)
+
+ kbytes := []byte(k)
+ openBracketsCount := 0
+
+ for i, b := range kbytes {
+ if b == '[' {
+ openBracketsCount++
+ if i+1 < len(kbytes) && kbytes[i+1] != ']' {
+ if err := bb.WriteByte('.'); err != nil {
+ return "", fmt.Errorf("failed to write: %w", err)
+ }
+ }
+ continue
+ }
+
+ if b == ']' {
+ openBracketsCount--
+ if openBracketsCount < 0 {
+ return "", errors.New("unmatched brackets")
+ }
+ continue
+ }
+
+ if err := bb.WriteByte(b); err != nil {
+ return "", fmt.Errorf("failed to write: %w", err)
+ }
+ }
+
+ if openBracketsCount > 0 {
+ return "", errors.New("unmatched brackets")
+ }
+
+ return bb.String(), nil
+}
+
+func formatParserData(out interface{}, data map[string][]string, aliasTag, key string, value interface{}, enableSplitting, supportBracketNotation bool) error { //nolint:revive // it's okay
+ var err error
+ if supportBracketNotation && strings.Contains(key, "[") {
+ key, err = parseParamSquareBrackets(key)
+ if err != nil {
+ return err
+ }
+ }
+
+ switch v := value.(type) {
+ case string:
+ assignBindData(out, data, aliasTag, key, v, enableSplitting)
+ case []string:
+ for _, val := range v {
+ assignBindData(out, data, aliasTag, key, val, enableSplitting)
+ }
+ default:
+ return fmt.Errorf("unsupported value type: %T", value)
+ }
+
+ return err
+}
+
+func assignBindData(out interface{}, data map[string][]string, aliasTag, key, value string, enableSplitting bool) { //nolint:revive // it's okay
+ if enableSplitting && strings.Contains(value, ",") && equalFieldType(out, reflect.Slice, key, aliasTag) {
+ values := strings.Split(value, ",")
+ for i := 0; i < len(values); i++ {
+ data[key] = append(data[key], values[i])
+ }
+ } else {
+ data[key] = append(data[key], value)
+ }
+}
From e04f815c43cb8a84f4de90e1d1bd9507bc33fa8c Mon Sep 17 00:00:00 2001
From: RW
Date: Tue, 31 Dec 2024 18:04:19 +0100
Subject: [PATCH 12/31] prepare release v2.52.6
---
app.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app.go b/app.go
index cea6407a93c..751df9f232a 100644
--- a/app.go
+++ b/app.go
@@ -30,7 +30,7 @@ import (
)
// Version of current fiber package
-const Version = "2.52.5"
+const Version = "2.52.6"
// Handler defines a function to serve HTTP requests.
type Handler = func(*Ctx) error
From 3729281a1ebd8e3be5f7a3e008996070795b9a89 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9?=
Date: Wed, 22 Jan 2025 08:35:17 +0100
Subject: [PATCH 13/31] Doc BodyParser: Add multipart form info about the file
data
---
docs/api/ctx.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/docs/api/ctx.md b/docs/api/ctx.md
index fdb2adc0105..d24eef21617 100644
--- a/docs/api/ctx.md
+++ b/docs/api/ctx.md
@@ -276,6 +276,12 @@ It is important to specify the correct struct tag based on the content type to b
| `application/xml` | xml |
| `text/xml` | xml |
+:::note
+
+When handling `multipart/form-data`, only the form values can be directly assigned to the struct fields. Files included in the request are not automatically assigned to the struct. You must handle files separately using [`FormFile`](#FormFile) or other file-specific methods.
+
+:::
+
```go title="Signature"
func (c *Ctx) BodyParser(out interface{}) error
```
From 8b9db059d7cc2c93a97040e2d225bf40076e7859 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9?=
Date: Thu, 23 Jan 2025 08:32:43 +0100
Subject: [PATCH 14/31] Doc BodyParser: Add multipart form info about the file
data
---
docs/api/ctx.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/api/ctx.md b/docs/api/ctx.md
index d24eef21617..40d03ca5ae5 100644
--- a/docs/api/ctx.md
+++ b/docs/api/ctx.md
@@ -278,7 +278,7 @@ It is important to specify the correct struct tag based on the content type to b
:::note
-When handling `multipart/form-data`, only the form values can be directly assigned to the struct fields. Files included in the request are not automatically assigned to the struct. You must handle files separately using [`FormFile`](#FormFile) or other file-specific methods.
+When handling `multipart/form-data`, only the form values can be directly assigned to the struct fields. Files included in the request are not automatically assigned to the struct. You must handle files separately using [`FormFile`](#formfile) or other file-specific methods.
:::
From 42d921d353c0255197245d12a55d9280d0157c10 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Mon, 27 Jan 2025 09:00:51 -0500
Subject: [PATCH 15/31] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20Backport=20ctx.St?=
=?UTF-8?q?ring()=20from=20v3=20(#3294)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Backport ctx.String() to v2
* Fix lint issues
---
ctx.go | 41 +++++++++++++++++++++++++++++++++--------
1 file changed, 33 insertions(+), 8 deletions(-)
diff --git a/ctx.go b/ctx.go
index c0a4413ab03..b418dc8e906 100644
--- a/ctx.go
+++ b/ctx.go
@@ -1834,14 +1834,39 @@ func (c *Ctx) Status(status int) *Ctx {
//
// The returned value may be useful for logging.
func (c *Ctx) String() string {
- return fmt.Sprintf(
- "#%016X - %s <-> %s - %s %s",
- c.fasthttp.ID(),
- c.fasthttp.LocalAddr(),
- c.fasthttp.RemoteAddr(),
- c.fasthttp.Request.Header.Method(),
- c.fasthttp.URI().FullURI(),
- )
+ // Get buffer from pool
+ buf := bytebufferpool.Get()
+
+ // Start with the ID, converting it to a hex string without fmt.Sprintf
+ buf.WriteByte('#') //nolint:errcheck // Not needed here
+ // Convert ID to hexadecimal
+ id := strconv.FormatUint(c.fasthttp.ID(), 16)
+ // Pad with leading zeros to ensure 16 characters
+ for i := 0; i < (16 - len(id)); i++ {
+ buf.WriteByte('0') //nolint:errcheck // Not needed here
+ }
+ buf.WriteString(id) //nolint:errcheck // Not needed here
+ buf.WriteString(" - ") //nolint:errcheck // Not needed here
+
+ // Add local and remote addresses directly
+ buf.WriteString(c.fasthttp.LocalAddr().String()) //nolint:errcheck // Not needed here
+ buf.WriteString(" <-> ") //nolint:errcheck // Not needed here
+ buf.WriteString(c.fasthttp.RemoteAddr().String()) //nolint:errcheck // Not needed here
+ buf.WriteString(" - ") //nolint:errcheck // Not needed here
+
+ // Add method and URI
+ buf.Write(c.fasthttp.Request.Header.Method()) //nolint:errcheck // Not needed here
+ buf.WriteByte(' ') //nolint:errcheck // Not needed here
+ buf.Write(c.fasthttp.URI().FullURI()) //nolint:errcheck // Not needed here
+
+ // Allocate string
+ str := buf.String()
+
+ // Reset buffer
+ buf.Reset()
+ bytebufferpool.Put(buf)
+
+ return str
}
// Type sets the Content-Type HTTP header to the MIME type specified by the file extension.
From 8add1ad27043edd06d4d9c09f3da04d33ea7c0be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9?=
Date: Wed, 9 Apr 2025 15:38:16 +0200
Subject: [PATCH 16/31] update benchmarks.md
---
docs/extra/benchmarks.md | 76 +++++++++-------------------------------
1 file changed, 17 insertions(+), 59 deletions(-)
diff --git a/docs/extra/benchmarks.md b/docs/extra/benchmarks.md
index 3c2a82037dd..02bc190d111 100644
--- a/docs/extra/benchmarks.md
+++ b/docs/extra/benchmarks.md
@@ -9,15 +9,16 @@ sidebar_position: 2
## TechEmpower
-[TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite) provides a performance comparison of many web application frameworks executing fundamental tasks such as JSON serialization, database access, and server-side template composition.
+[TechEmpower](https://www.techempower.com/benchmarks/#section=data-r23) provides a performance comparison of many web application frameworks executing fundamental tasks such as JSON serialization, database access, and server-side template composition.
Each framework is operating in a realistic production configuration. Results are captured on cloud instances and on physical hardware. The test implementations are largely community-contributed and all source is available at the [GitHub repository](https://github.com/TechEmpower/FrameworkBenchmarks).
-* Fiber `v1.10.0`
-* 28 HT Cores Intel\(R\) Xeon\(R\) Gold 5120 CPU @ 2.20GHz
-* 32GB RAM
-* Ubuntu 18.04.3 4.15.0-88-generic
-* Dedicated Cisco 10-Gbit Ethernet switch.
+* Fiber `v2.52.5`
+* 56 Cores Intel(R) Xeon(R) Gold 6330 CPU @ 2.00GHz (Three homogeneous ProLiant DL360 Gen10 Plus)
+* 64GB RAM
+* Enterprise SSD
+* Ubuntu
+* Mellanox Technologies MT28908 Family ConnectX-6 40Gbps Ethernet
### Plaintext
@@ -25,8 +26,8 @@ The Plaintext test is an exercise of the request-routing fundamentals only, desi
See [Plaintext requirements](https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview#single-database-query)
-**Fiber** - **6,162,556** responses per second with an average latency of **2.0** ms.
-**Express** - **367,069** responses per second with an average latency of **354.1** ms.
+**Fiber** - **13,509,592** responses per second with an average latency of **0.9** ms.
+**Express** - **279,922** responses per second with an average latency of **551.3** ms.

@@ -34,8 +35,8 @@ See [Plaintext requirements](https://github.com/TechEmpower/FrameworkBenchmarks/
### Data Updates
-**Fiber** handled **11,846** responses per second with an average latency of **42.8** ms.
-**Express** handled **2,066** responses per second with an average latency of **390.44** ms.
+**Fiber** handled **30,884** responses per second with an average latency of **16.5** ms.
+**Express** handled **50,818** responses per second with an average latency of **10.1** ms.

@@ -43,8 +44,8 @@ See [Plaintext requirements](https://github.com/TechEmpower/FrameworkBenchmarks/
### Multiple Queries
-**Fiber** handled **19,664** responses per second with an average latency of **25.7** ms.
-**Express** handled **4,302** responses per second with an average latency of **117.2** ms.
+**Fiber** handled **55,577** responses per second with an average latency of **9.2** ms.
+**Express** handled **62,036** responses per second with an average latency of **8.3** ms.

@@ -52,8 +53,8 @@ See [Plaintext requirements](https://github.com/TechEmpower/FrameworkBenchmarks/
### Single Query
-**Fiber** handled **368,647** responses per second with an average latency of **0.7** ms.
-**Express** handled **57,880** responses per second with an average latency of **4.4** ms.
+**Fiber** handled **1,000,519** responses per second with an average latency of **0.5** ms.
+**Express** handled **214,177** responses per second with an average latency of **2.5** ms.

@@ -61,52 +62,9 @@ See [Plaintext requirements](https://github.com/TechEmpower/FrameworkBenchmarks/
### JSON Serialization
-**Fiber** handled **1,146,667** responses per second with an average latency of **0.4** ms.
-**Express** handled **244,847** responses per second with an average latency of **1.1** ms.
+**Fiber** handled **2,479,768** responses per second with an average latency of **0.2** ms.
+**Express** handled **301,213** responses per second with an average latency of **2.0** ms.


-
-## Go web framework benchmark
-
-🔗 [https://github.com/smallnest/go-web-framework-benchmark](https://github.com/smallnest/go-web-framework-benchmark)
-
-* **CPU** Intel\(R\) Xeon\(R\) Gold 6140 CPU @ 2.30GHz
-* **MEM** 4GB
-* **GO** go1.13.6 linux/amd64
-* **OS** Linux
-
-The first test case is to mock **0 ms**, **10 ms**, **100 ms**, **500 ms** processing time in handlers.
-
-
-
-The concurrency clients are **5000**.
-
-
-
-Latency is the time of real processing time by web servers. _The smaller is the better._
-
-
-
-Allocs is the heap allocations by web servers when test is running. The unit is MB. _The smaller is the better._
-
-If we enable **http pipelining**, test result as below:
-
-
-
-Concurrency test in **30 ms** processing time, the test result for **100**, **1000**, **5000** clients is:
-
-
-
-
-
-
-
-If we enable **http pipelining**, test result as below:
-
-
-
-Dependency graph for `v1.9.0`
-
-
From cf5041b9d714e10e1225c4e6e4b82c47c64b4f8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9?=
Date: Sun, 13 Apr 2025 18:37:24 +0200
Subject: [PATCH 17/31] repair readme benchmarks
---
.github/README.md | 4 ++--
.github/README_az.md | 4 ++--
.github/README_ckb.md | 4 ++--
.github/README_de.md | 4 ++--
.github/README_eg.md | 4 ++--
.github/README_es.md | 4 ++--
.github/README_fa.md | 4 ++--
.github/README_fr.md | 4 ++--
.github/README_he.md | 4 ++--
.github/README_id.md | 4 ++--
.github/README_it.md | 4 ++--
.github/README_ja.md | 4 ++--
.github/README_ko.md | 4 ++--
.github/README_nl.md | 4 ++--
.github/README_pl.md | 4 ++--
.github/README_pt.md | 4 ++--
.github/README_ru.md | 4 ++--
.github/README_sa.md | 4 ++--
.github/README_tr.md | 4 ++--
.github/README_uk.md | 4 ++--
.github/README_zh-CN.md | 4 ++--
.github/README_zh-TW.md | 4 ++--
22 files changed, 44 insertions(+), 44 deletions(-)
diff --git a/.github/README.md b/.github/README.md
index 172ecc599f5..0b19126cf85 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -124,8 +124,8 @@ func main() {
These tests are performed by [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) and [Go Web](https://github.com/smallnest/go-web-framework-benchmark). If you want to see all the results, please visit our [Wiki](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ Installation
diff --git a/.github/README_az.md b/.github/README_az.md
index f1b6174e988..e31d33037fb 100644
--- a/.github/README_az.md
+++ b/.github/README_az.md
@@ -123,8 +123,8 @@ func main() {
Bu testlər [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) və [Go Web](https://github.com/smallnest/go-web-framework-benchmark) tərəfindən aparılıb. Bütün nəticələri görmək üçün [Wiki](https://docs.gofiber.io/extra/benchmarks) səhifəsinə keçid edə bilərsiniz.
-
-
+
+
## ⚙️ Quraşdırılması
diff --git a/.github/README_ckb.md b/.github/README_ckb.md
index 5f52bf8aea5..872ad7062dd 100644
--- a/.github/README_ckb.md
+++ b/.github/README_ckb.md
@@ -120,8 +120,8 @@ func main() {
ئەم تاقیکردنەوانە لەلایەن [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) و [Go Web](https://github.com/smallnest/go-web-framework-benchmark) ئەنجام دراون. دەتوانیت هەموو ئەنجامەکان [لێرە](https://docs.gofiber.io/extra/benchmarks) ببینیت.
-
-
+
+
## ⚙️ دامەزراندن
diff --git a/.github/README_de.md b/.github/README_de.md
index 8fd89352630..49127e35512 100644
--- a/.github/README_de.md
+++ b/.github/README_de.md
@@ -122,8 +122,8 @@ func main() {
Diese Tests wurden von [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) und [Go Web](https://github.com/smallnest/go-web-framework-benchmark) ausgeführt. Falls du alle Resultate sehen möchtest, besuche bitte unser [Wiki](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ Installation
diff --git a/.github/README_eg.md b/.github/README_eg.md
index 3eea1171ac3..0c139514036 100644
--- a/.github/README_eg.md
+++ b/.github/README_eg.md
@@ -124,8 +124,8 @@ func main() {
القياسات دي اتعملت عن طريق [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) و [Go Web](https://github.com/smallnest/go-web-framework-benchmark). لو عاوز تشوف كل النتايج زور [الويكي بتاعتنا](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ التسطيب
diff --git a/.github/README_es.md b/.github/README_es.md
index b3e5b9f7bfb..2b429a6787b 100644
--- a/.github/README_es.md
+++ b/.github/README_es.md
@@ -122,8 +122,8 @@ func main() {
Estas pruebas son realizadas por [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) y [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Si desea ver todos los resultados, visite nuestra [Wiki](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ Instalación
diff --git a/.github/README_fa.md b/.github/README_fa.md
index 390922d68be..79fc594386b 100644
--- a/.github/README_fa.md
+++ b/.github/README_fa.md
@@ -137,8 +137,8 @@ func main() {
-
-
+
+
diff --git a/.github/README_fr.md b/.github/README_fr.md
index 2f93e567ded..9f6ce58f99b 100644
--- a/.github/README_fr.md
+++ b/.github/README_fr.md
@@ -122,8 +122,8 @@ func main() {
Ces tests sont effectués par [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) et [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Si vous voulez voir tous les résultats, n'hésitez pas à consulter notre [Wiki](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ Installation
diff --git a/.github/README_he.md b/.github/README_he.md
index 0ddda261df6..04dcb114dc7 100644
--- a/.github/README_he.md
+++ b/.github/README_he.md
@@ -139,8 +139,8 @@ func main() {
-
-
+
+
diff --git a/.github/README_id.md b/.github/README_id.md
index 658e86a522a..999de07d6e7 100644
--- a/.github/README_id.md
+++ b/.github/README_id.md
@@ -122,8 +122,8 @@ func main() {
Pengukuran ini dilakukan oleh [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) dan [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Apabila anda ingin melihat hasil lengkapnya, silahkan kunjungi halaman [Wiki](https://docs.gofiber.io/extra/benchmarks) kami.
-
-
+
+
## ⚙️ Instalasi
diff --git a/.github/README_it.md b/.github/README_it.md
index 159bcff9a18..eb2aefc259d 100644
--- a/.github/README_it.md
+++ b/.github/README_it.md
@@ -122,8 +122,8 @@ func main() {
Questi test sono stati eseguiti da [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) e [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Se vuoi vedere tutti i risultati, visita la nostra [Wiki](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ Installazione
diff --git a/.github/README_ja.md b/.github/README_ja.md
index 23dfb60999a..f82dc762e13 100644
--- a/.github/README_ja.md
+++ b/.github/README_ja.md
@@ -123,8 +123,8 @@ func main() {
これらのテストは[TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext)および[Go Web](https://github.com/smallnest/go-web-framework-benchmark)によって計測を行っています 。すべての結果を表示するには、 [Wiki](https://docs.gofiber.io/extra/benchmarks)にアクセスしてください。
-
-
+
+
## ⚙️ インストール
diff --git a/.github/README_ko.md b/.github/README_ko.md
index c2ed6afb9b6..e7e75da53b3 100644
--- a/.github/README_ko.md
+++ b/.github/README_ko.md
@@ -122,8 +122,8 @@ func main() {
이 테스트들은 [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext)와 [Go Web](https://github.com/smallnest/go-web-framework-benchmark)을 통해 측정되었습니다. 만약 모든 결과를 보고 싶다면, [Wiki](https://docs.gofiber.io/extra/benchmarks)를 확인해 주세요.
-
-
+
+
## ⚙️ 설치
diff --git a/.github/README_nl.md b/.github/README_nl.md
index e31040a06db..1eced32ceaf 100644
--- a/.github/README_nl.md
+++ b/.github/README_nl.md
@@ -122,8 +122,8 @@ func main() {
Deze tests zijn uitgevoerd door [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) en [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Bezoek onze [Wiki](https://fiber.wiki/benchmarks) voor alle benchmark resultaten.
-
-
+
+
## ⚙️ Installatie
diff --git a/.github/README_pl.md b/.github/README_pl.md
index 5cff10c87e2..77b75c42bd2 100644
--- a/.github/README_pl.md
+++ b/.github/README_pl.md
@@ -125,8 +125,8 @@ Testy te zostały przeprowadzone przez [TechEmpower](https://www.techempower.com
-
-
+
+
## ⚙️ Instalacja
diff --git a/.github/README_pt.md b/.github/README_pt.md
index e55f4f58b72..06c73614ec2 100644
--- a/.github/README_pt.md
+++ b/.github/README_pt.md
@@ -122,8 +122,8 @@ func main() {
Esses testes são realizados pelo [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) e [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Se você quiser ver todos os resultados, visite nosso [Wiki](https://docs.gofiber.io/extra/benchmarks) .
-
-
+
+
## ⚙️ Instalação
diff --git a/.github/README_ru.md b/.github/README_ru.md
index bfb41661d8a..f42bcc29091 100644
--- a/.github/README_ru.md
+++ b/.github/README_ru.md
@@ -122,8 +122,8 @@ func main() {
Тестирование проводилось с помощью [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) и [Go Web](https://github.com/smallnest/go-web-framework-benchmark). Если вы хотите увидеть все результаты, пожалуйста, посетите наш [Wiki](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ Установка
diff --git a/.github/README_sa.md b/.github/README_sa.md
index a5a54e9e470..e3f3f81850e 100644
--- a/.github/README_sa.md
+++ b/.github/README_sa.md
@@ -128,8 +128,8 @@ func main() {
يتم تنفيذ هذه الاختبارات من قبل [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) و [Go Web](https://github.com/smallnest/go-web-framework-benchmark). إذا كنت تريد رؤية جميع النتائج ، يرجى زيارة موقعنا [Wiki](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ تثبيت
diff --git a/.github/README_tr.md b/.github/README_tr.md
index 3ab0f15a6d8..29365154386 100644
--- a/.github/README_tr.md
+++ b/.github/README_tr.md
@@ -122,8 +122,8 @@ func main() {
Bu testler [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) ve [Go Web](https://github.com/smallnest/go-web-framework-benchmark) tarafından gerçekleştirildi. Bütün sonuçları görmek için lütfen [Wiki](https://docs.gofiber.io/extra/benchmarks) sayfasını ziyaret ediniz.
-
-
+
+
## ⚙️ Kurulum
diff --git a/.github/README_uk.md b/.github/README_uk.md
index ef9f36536b4..e3d6b11eaa2 100644
--- a/.github/README_uk.md
+++ b/.github/README_uk.md
@@ -132,8 +132,8 @@ func main() {
відвідайте наш [Wiki](https://docs.gofiber.io/extra/benchmarks).
-
-
+
+
## ⚙️ Встановлення
diff --git a/.github/README_zh-CN.md b/.github/README_zh-CN.md
index 4314e0580f6..eb80f137d23 100644
--- a/.github/README_zh-CN.md
+++ b/.github/README_zh-CN.md
@@ -124,8 +124,8 @@ func main() {
这些测试由 [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) 和 [Go Web](https://github.com/smallnest/go-web-framework-benchmark) 完成。如果您想查看所有结果,请访问我们的 [Wiki](https://docs.gofiber.io/extra/benchmarks) 。
-
-
+
+
## ⚙️ 安装
diff --git a/.github/README_zh-TW.md b/.github/README_zh-TW.md
index 7bf431a99fd..37e6eea6eba 100644
--- a/.github/README_zh-TW.md
+++ b/.github/README_zh-TW.md
@@ -126,8 +126,8 @@ func main() {
這些測試由 [TechEmpower](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=plaintext) 和 [Go Web 框架效能測試](https://github.com/smallnest/go-web-framework-benchmark) 完成。若需參閱所有結果,請參閱我們的 [Wiki](https://docs.gofiber.io/extra/benchmarks) 資訊。
-
-
+
+
## ⚙️ 安裝
From ac8b1c95c3af9424bafcaa3b7e94cacc25018c41 Mon Sep 17 00:00:00 2001
From: Isaac Andrade
Date: Sat, 26 Apr 2025 11:16:09 -0600
Subject: [PATCH 18/31] =?UTF-8?q?=F0=9F=93=92=20docs:=20Update=20usage=20o?=
=?UTF-8?q?f=20ctx.Redirect()=20(#3417)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 📚 Improve docs for Ctx.Redirect
Closes #3405
* Update ctx.md
* Only run golangci-lint for go related changes
---------
Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
---
.github/workflows/linter.yml | 11 +++++++--
docs/api/ctx.md | 47 ++++++++++++++++++++++++------------
2 files changed, 40 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 607e0fa0554..6f98d77b49d 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -1,11 +1,18 @@
-# Adapted from https://github.com/golangci/golangci-lint-action/blob/b56f6f529003f1c81d4d759be6bd5f10bf9a0fa0/README.md#how-to-use
-
name: golangci-lint
on:
push:
branches:
- v2
+ paths:
+ - "**"
+ - "!docs/**"
+ - "!**.md"
pull_request:
+ paths:
+ - "**"
+ - "!docs/**"
+ - "!**.md"
+
permissions:
contents: read
diff --git a/docs/api/ctx.md b/docs/api/ctx.md
index 40d03ca5ae5..dcde9dd96c0 100644
--- a/docs/api/ctx.md
+++ b/docs/api/ctx.md
@@ -57,7 +57,7 @@ Media-Type parameters are supported.
app.Get("/", func(c *fiber.Ctx) error {
// Extra parameters in the accept are ignored
c.Accepts("text/plain;format=flowed") // "text/plain;format=flowed"
-
+
// An offer must contain all parameters present in the Accept type
c.Accepts("application/json") // ""
@@ -239,7 +239,7 @@ app.Post("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## Body
@@ -259,7 +259,7 @@ app.Post("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## BodyParser
@@ -319,7 +319,7 @@ app.Post("/", func(c *fiber.Ctx) error {
// curl -X POST "http://localhost:3000/?name=john&pass=doe"
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## ClearCookie
@@ -489,7 +489,7 @@ app.Get("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## Download
@@ -579,7 +579,7 @@ app.Post("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## Fresh
@@ -615,7 +615,7 @@ app.Get("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## GetReqHeaders
@@ -626,7 +626,7 @@ Returns the HTTP request headers as a map. Since a header can be set multiple ti
func (c *Ctx) GetReqHeaders() map[string][]string
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## GetRespHeader
@@ -650,7 +650,7 @@ app.Get("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## GetRespHeaders
@@ -661,7 +661,7 @@ Returns the HTTP response headers as a map. Since a header can be set multiple t
func (c *Ctx) GetRespHeaders() map[string][]string
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## GetRouteURL
@@ -707,7 +707,7 @@ app.Get("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## IP
@@ -954,7 +954,7 @@ app.Post("/", func(c *fiber.Ctx) error {
## Method
-Returns a string corresponding to the HTTP method of the request: `GET`, `POST`, `PUT`, and so on.
+Returns a string corresponding to the HTTP method of the request: `GET`, `POST`, `PUT`, and so on.
Optionally, you could override the method by passing a string.
```go title="Signature"
@@ -1054,7 +1054,7 @@ app.Get("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## Params
@@ -1103,7 +1103,7 @@ app.Get("/v1/*/shop/*", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## ParamsInt
@@ -1280,7 +1280,7 @@ app.Get("/", func(c *fiber.Ctx) error {
})
```
-> _Returned value is only valid within the handler. Do not store any references.
+> _Returned value is only valid within the handler. Do not store any references.
> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation)
## QueryBool
@@ -1402,7 +1402,7 @@ app.Get("/", func(c *fiber.Ctx) error {
log.Println(p.Products) // ["shoe,hat"]
// fiber.Config{EnableSplittingOnParsers: true}
// log.Println(p.Products) // ["shoe", "hat"]
-
+
// ...
})
@@ -1444,6 +1444,21 @@ Redirects to the URL derived from the specified path, with specified status, a p
If **not** specified, status defaults to **302 Found**.
:::
+:::caution
+When the redirect status code is **302 Found**, web browsers may change the HTTP method
+(for example, converting a POST into a GET) depending on the browser implementation. To ensure that the
+original HTTP method is preserved, especially in scenarios where a non-GET method is required, you
+should explicitly use one of the following status codes:
+
+- 303 See Other
+- 307 Temporary Redirect
+- 308 Permanent Redirect
+
+For example, to preserve the HTTP method, use:
+
+`c.Redirect("/new-path", fiber.StatusSeeOther)`
+:::
+
```go title="Signature"
func (c *Ctx) Redirect(location string, status ...int) error
```
From f7a10c9a611913957b4db88a893c0d364f0f3ae3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9?=
Date: Mon, 12 May 2025 11:02:15 +0200
Subject: [PATCH 19/31] fix golangci errors Error: G104: Errors unhandled.
(gosec) Error: G104: Errors unhandled. (gosec) Error: G104: Errors unhandled.
(gosec)
---
.golangci.yml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/.golangci.yml b/.golangci.yml
index c58d52511e3..bb14703d1fc 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -15,6 +15,11 @@ linters-settings:
check-type-assertions: true
check-blank: true
disable-default-exclusions: true
+ exclude-functions:
+ - '(*bytes.Buffer).Write' # always returns nil error
+ - '(*github.com/valyala/bytebufferpool.ByteBuffer).Write' # always returns nil error
+ - '(*github.com/valyala/bytebufferpool.ByteBuffer).WriteByte' # always returns nil error
+ - '(*github.com/valyala/bytebufferpool.ByteBuffer).WriteString' # always returns nil error
errchkjson:
report-no-exported: true
From fccff196064e95411c0cdaaee8d9f67e2627e33c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9?=
Date: Mon, 12 May 2025 11:20:39 +0200
Subject: [PATCH 20/31] fix golangci errors Error: G104: Errors unhandled.
(gosec)
---
.golangci.yml | 2 ++
ctx.go | 35 +++++++++++++++---------------
ctx_test.go | 4 ++--
log/default.go | 10 ++++-----
middleware/adaptor/adaptor_test.go | 2 +-
middleware/etag/etag.go | 8 +++----
middleware/logger/logger.go | 4 ++--
middleware/session/session.go | 2 +-
8 files changed, 35 insertions(+), 32 deletions(-)
diff --git a/.golangci.yml b/.golangci.yml
index bb14703d1fc..88919e8e935 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -44,6 +44,8 @@ linters-settings:
extra-rules: true
gosec:
+ excludes:
+ - G104 # TODO: Enable this again. Mostly provided by errcheck
config:
global:
audit: true
diff --git a/ctx.go b/ctx.go
index b418dc8e906..97dfe9cf2b2 100644
--- a/ctx.go
+++ b/ctx.go
@@ -948,11 +948,11 @@ func (c *Ctx) Links(link ...string) {
bb := bytebufferpool.Get()
for i := range link {
if i%2 == 0 {
- _ = bb.WriteByte('<') //nolint:errcheck // This will never fail
- _, _ = bb.WriteString(link[i]) //nolint:errcheck // This will never fail
- _ = bb.WriteByte('>') //nolint:errcheck // This will never fail
+ _ = bb.WriteByte('<')
+ _, _ = bb.WriteString(link[i])
+ _ = bb.WriteByte('>')
} else {
- _, _ = bb.WriteString(`; rel="` + link[i] + `",`) //nolint:errcheck // This will never fail
+ _, _ = bb.WriteString(`; rel="` + link[i] + `",`)
}
}
c.setCanonical(HeaderLink, utils.TrimRight(c.app.getString(bb.Bytes()), ','))
@@ -1510,10 +1510,10 @@ func (c *Ctx) RedirectToRoute(routeName string, params Map, status ...int) error
i := 1
for k, v := range queries {
- _, _ = queryText.WriteString(k + "=" + v) //nolint:errcheck // This will never fail
+ _, _ = queryText.WriteString(k + "=" + v)
if i != len(queries) {
- _, _ = queryText.WriteString("&") //nolint:errcheck // This will never fail
+ _, _ = queryText.WriteString("&")
}
i++
}
@@ -1838,26 +1838,27 @@ func (c *Ctx) String() string {
buf := bytebufferpool.Get()
// Start with the ID, converting it to a hex string without fmt.Sprintf
- buf.WriteByte('#') //nolint:errcheck // Not needed here
+ buf.WriteByte('#')
+
// Convert ID to hexadecimal
id := strconv.FormatUint(c.fasthttp.ID(), 16)
// Pad with leading zeros to ensure 16 characters
for i := 0; i < (16 - len(id)); i++ {
- buf.WriteByte('0') //nolint:errcheck // Not needed here
+ buf.WriteByte('0')
}
- buf.WriteString(id) //nolint:errcheck // Not needed here
- buf.WriteString(" - ") //nolint:errcheck // Not needed here
+ buf.WriteString(id)
+ buf.WriteString(" - ")
// Add local and remote addresses directly
- buf.WriteString(c.fasthttp.LocalAddr().String()) //nolint:errcheck // Not needed here
- buf.WriteString(" <-> ") //nolint:errcheck // Not needed here
- buf.WriteString(c.fasthttp.RemoteAddr().String()) //nolint:errcheck // Not needed here
- buf.WriteString(" - ") //nolint:errcheck // Not needed here
+ buf.WriteString(c.fasthttp.LocalAddr().String())
+ buf.WriteString(" <-> ")
+ buf.WriteString(c.fasthttp.RemoteAddr().String())
+ buf.WriteString(" - ")
// Add method and URI
- buf.Write(c.fasthttp.Request.Header.Method()) //nolint:errcheck // Not needed here
- buf.WriteByte(' ') //nolint:errcheck // Not needed here
- buf.Write(c.fasthttp.URI().FullURI()) //nolint:errcheck // Not needed here
+ buf.Write(c.fasthttp.Request.Header.Method())
+ buf.WriteByte(' ')
+ buf.Write(c.fasthttp.URI().FullURI())
// Allocate string
str := buf.String()
diff --git a/ctx_test.go b/ctx_test.go
index 6a3998f7fa2..c1602abe9c9 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -3641,7 +3641,7 @@ func Test_Ctx_RenderWithBind(t *testing.T) {
utils.AssertEqual(t, nil, err)
buf := bytebufferpool.Get()
- _, _ = buf.WriteString("overwrite") //nolint:errcheck // This will never fail
+ _, _ = buf.WriteString("overwrite")
defer bytebufferpool.Put(buf)
utils.AssertEqual(t, "Hello, World!
", string(c.Response().Body()))
@@ -3663,7 +3663,7 @@ func Test_Ctx_RenderWithOverwrittenBind(t *testing.T) {
utils.AssertEqual(t, nil, err)
buf := bytebufferpool.Get()
- _, _ = buf.WriteString("overwrite") //nolint:errcheck // This will never fail
+ _, _ = buf.WriteString("overwrite")
defer bytebufferpool.Put(buf)
utils.AssertEqual(t, "Hello from Fiber!
", string(c.Response().Body()))
diff --git a/log/default.go b/log/default.go
index c898cd6c83a..5cc7500afc7 100644
--- a/log/default.go
+++ b/log/default.go
@@ -27,8 +27,8 @@ func (l *defaultLogger) privateLog(lv Level, fmtArgs []interface{}) {
}
level := lv.toString()
buf := bytebufferpool.Get()
- _, _ = buf.WriteString(level) //nolint:errcheck // It is fine to ignore the error
- _, _ = buf.WriteString(fmt.Sprint(fmtArgs...)) //nolint:errcheck // It is fine to ignore the error
+ _, _ = buf.WriteString(level)
+ _, _ = buf.WriteString(fmt.Sprint(fmtArgs...))
_ = l.stdlog.Output(l.depth, buf.String()) //nolint:errcheck // It is fine to ignore the error
buf.Reset()
@@ -46,7 +46,7 @@ func (l *defaultLogger) privateLogf(lv Level, format string, fmtArgs []interface
}
level := lv.toString()
buf := bytebufferpool.Get()
- _, _ = buf.WriteString(level) //nolint:errcheck // It is fine to ignore the error
+ _, _ = buf.WriteString(level)
if len(fmtArgs) > 0 {
_, _ = fmt.Fprintf(buf, format, fmtArgs...)
@@ -69,11 +69,11 @@ func (l *defaultLogger) privateLogw(lv Level, format string, keysAndValues []int
}
level := lv.toString()
buf := bytebufferpool.Get()
- _, _ = buf.WriteString(level) //nolint:errcheck // It is fine to ignore the error
+ _, _ = buf.WriteString(level)
// Write format privateLog buffer
if format != "" {
- _, _ = buf.WriteString(format) //nolint:errcheck // It is fine to ignore the error
+ _, _ = buf.WriteString(format)
}
var once sync.Once
isFirst := true
diff --git a/middleware/adaptor/adaptor_test.go b/middleware/adaptor/adaptor_test.go
index dc52704760d..03fdbc1a00d 100644
--- a/middleware/adaptor/adaptor_test.go
+++ b/middleware/adaptor/adaptor_test.go
@@ -78,7 +78,7 @@ func Test_HTTPHandler(t *testing.T) {
req.Header.SetMethod(expectedMethod)
req.SetRequestURI(expectedRequestURI)
req.Header.SetHost(expectedHost)
- req.BodyWriter().Write([]byte(expectedBody)) //nolint:errcheck, gosec // not needed
+ req.BodyWriter().Write([]byte(expectedBody)) //nolint:errcheck // not needed
for k, v := range expectedHeader {
req.Header.Set(k, v)
}
diff --git a/middleware/etag/etag.go b/middleware/etag/etag.go
index 13148fc63ba..7a37f84effb 100644
--- a/middleware/etag/etag.go
+++ b/middleware/etag/etag.go
@@ -54,14 +54,14 @@ func New(config ...Config) fiber.Handler {
// Enable weak tag
if cfg.Weak {
- _, _ = bb.Write(weakPrefix) //nolint:errcheck // This will never fail
+ _, _ = bb.Write(weakPrefix)
}
- _ = bb.WriteByte('"') //nolint:errcheck // This will never fail
+ _ = bb.WriteByte('"')
bb.B = appendUint(bb.Bytes(), uint32(len(body)))
- _ = bb.WriteByte('-') //nolint:errcheck // This will never fail
+ _ = bb.WriteByte('-')
bb.B = appendUint(bb.Bytes(), crc32.Checksum(body, crc32q))
- _ = bb.WriteByte('"') //nolint:errcheck // This will never fail
+ _ = bb.WriteByte('"')
etag := bb.Bytes()
diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go
index b03617b2b34..a41d33ef6c7 100644
--- a/middleware/logger/logger.go
+++ b/middleware/logger/logger.go
@@ -138,7 +138,7 @@ func New(config ...Config) fiber.Handler {
// Loop over template parts execute dynamic parts and add fixed parts to the buffer
for i, logFunc := range logFunChain {
if logFunc == nil {
- _, _ = buf.Write(templateChain[i]) //nolint:errcheck // This will never fail
+ _, _ = buf.Write(templateChain[i])
} else if templateChain[i] == nil {
_, err = logFunc(buf, c, data, "")
} else {
@@ -151,7 +151,7 @@ func New(config ...Config) fiber.Handler {
// Also write errors to the buffer
if err != nil {
- _, _ = buf.WriteString(err.Error()) //nolint:errcheck // This will never fail
+ _, _ = buf.WriteString(err.Error())
}
mu.Lock()
// Write buffer to output
diff --git a/middleware/session/session.go b/middleware/session/session.go
index b4115faf238..e1fa174f6d7 100644
--- a/middleware/session/session.go
+++ b/middleware/session/session.go
@@ -302,7 +302,7 @@ func (s *Session) delSession() {
// decodeSessionData decodes the session data from raw bytes.
func (s *Session) decodeSessionData(rawData []byte) error {
- _, _ = s.byteBuffer.Write(rawData) //nolint:errcheck // This will never fail
+ _, _ = s.byteBuffer.Write(rawData)
encCache := gob.NewDecoder(s.byteBuffer)
if err := encCache.Decode(&s.data.Data); err != nil {
return fmt.Errorf("failed to decode session data: %w", err)
From 94e30d7124cbf75bc2055cc49ec1ca7b8d7690e3 Mon Sep 17 00:00:00 2001
From: RW
Date: Fri, 16 May 2025 08:29:39 +0200
Subject: [PATCH 21/31] =?UTF-8?q?=F0=9F=90=9B=20Fix=20routing=20with=20mou?=
=?UTF-8?q?nt=20and=20static=20(#3454)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix routing with mount and static
[Bug]: Static server in sub app does not work #3104
https://github.com/gofiber/fiber/issues/3104
[Bug]: When mounting a subapp with mount, the static route is inaccessible. #3442
https://github.com/gofiber/fiber/issues/3442
---
app.go | 2 -
mount.go | 8 ----
mount_test.go | 6 ---
router.go | 100 ++++++++++++++++++++++++++++---------------------
router_test.go | 48 +++++++++++++++++++-----
5 files changed, 97 insertions(+), 67 deletions(-)
diff --git a/app.go b/app.go
index 751df9f232a..3c1cc3cc986 100644
--- a/app.go
+++ b/app.go
@@ -93,8 +93,6 @@ type App struct {
treeStack []map[string][]*Route
// contains the information if the route stack has been changed to build the optimized tree
routesRefreshed bool
- // Amount of registered routes
- routesCount uint32
// Amount of registered handlers
handlersCount uint32
// Ctx pool
diff --git a/mount.go b/mount.go
index abb5695e9fe..b26eecc64e8 100644
--- a/mount.go
+++ b/mount.go
@@ -174,7 +174,6 @@ func (app *App) processSubAppsRoutes() {
}
}
var handlersCount uint32
- var routePos uint32
// Iterate over the stack of the parent app
for m := range app.stack {
// Iterate over each route in the stack
@@ -183,9 +182,6 @@ func (app *App) processSubAppsRoutes() {
route := app.stack[m][i]
// Check if the route has a mounted app
if !route.mount {
- routePos++
- // If not, update the route's position and continue
- route.pos = routePos
if !route.use || (route.use && m == 0) {
handlersCount += uint32(len(route.Handlers))
}
@@ -214,11 +210,7 @@ func (app *App) processSubAppsRoutes() {
copy(newStack[i+len(subRoutes):], app.stack[m][i+1:])
app.stack[m] = newStack
- // Decrease the parent app's route count to account for the mounted app's original route
- atomic.AddUint32(&app.routesCount, ^uint32(0))
i--
- // Increase the parent app's route count to account for the sub-app's routes
- atomic.AddUint32(&app.routesCount, uint32(len(subRoutes)))
// Mark the parent app's routes as refreshed
app.routesRefreshed = true
diff --git a/mount_test.go b/mount_test.go
index c0ca6bf3c9f..63d4f1b3f21 100644
--- a/mount_test.go
+++ b/mount_test.go
@@ -89,7 +89,6 @@ func Test_App_Mount_Nested(t *testing.T) {
utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
utils.AssertEqual(t, uint32(6), app.handlersCount)
- utils.AssertEqual(t, uint32(6), app.routesCount)
}
// go test -run Test_App_Mount_Express_Behavior
@@ -139,7 +138,6 @@ func Test_App_Mount_Express_Behavior(t *testing.T) {
testEndpoint(app, "/unknown", ErrNotFound.Message, StatusNotFound)
utils.AssertEqual(t, uint32(17), app.handlersCount)
- utils.AssertEqual(t, uint32(16+9), app.routesCount)
}
// go test -run Test_App_Mount_RoutePositions
@@ -195,19 +193,15 @@ func Test_App_Mount_RoutePositions(t *testing.T) {
utils.AssertEqual(t, true, routeStackGET[1].use)
utils.AssertEqual(t, "/", routeStackGET[1].path)
- utils.AssertEqual(t, true, routeStackGET[0].pos < routeStackGET[1].pos, "wrong position of route 0")
utils.AssertEqual(t, false, routeStackGET[2].use)
utils.AssertEqual(t, "/bar", routeStackGET[2].path)
- utils.AssertEqual(t, true, routeStackGET[1].pos < routeStackGET[2].pos, "wrong position of route 1")
utils.AssertEqual(t, true, routeStackGET[3].use)
utils.AssertEqual(t, "/", routeStackGET[3].path)
- utils.AssertEqual(t, true, routeStackGET[2].pos < routeStackGET[3].pos, "wrong position of route 2")
utils.AssertEqual(t, false, routeStackGET[4].use)
utils.AssertEqual(t, "/subapp2/world", routeStackGET[4].path)
- utils.AssertEqual(t, true, routeStackGET[3].pos < routeStackGET[4].pos, "wrong position of route 3")
utils.AssertEqual(t, 5, len(routeStackGET))
}
diff --git a/router.go b/router.go
index 4afa7415377..61ddc1bf17d 100644
--- a/router.go
+++ b/router.go
@@ -7,7 +7,6 @@ package fiber
import (
"fmt"
"html"
- "sort"
"strconv"
"strings"
"sync/atomic"
@@ -47,9 +46,8 @@ type Router interface {
// Route is a struct that holds all metadata for each registered handler.
type Route struct {
- // ### important: always keep in sync with the copy method "app.copyRoute" ###
+ // ### important: always keep in sync with the copy method "app.copyRoute" and all creations of Route struct ###
// Data for routing
- pos uint32 // Position in stack -> important for the sort of the matched routes
use bool // USE matches path prefixes
mount bool // Indicated a mounted app on a specific route
star bool // Path equals '*'
@@ -215,9 +213,6 @@ func (*App) copyRoute(route *Route) *Route {
path: route.path,
routeParser: route.routeParser,
- // misc
- pos: route.pos,
-
// Public data
Path: route.Path,
Params: route.Params,
@@ -298,11 +293,11 @@ func (app *App) register(method, pathRaw string, group *Group, handlers ...Handl
for _, m := range app.config.RequestMethods {
// Create a route copy to avoid duplicates during compression
r := route
- app.addRoute(m, &r, isMount)
+ app.addRoute(m, &r)
}
} else {
// Add route to stack
- app.addRoute(method, &route, isMount)
+ app.addRoute(method, &route)
}
}
@@ -428,12 +423,20 @@ func (app *App) registerStatic(prefix, root string, config ...Static) {
// Create route metadata without pointer
route := Route{
// Router booleans
- use: true,
- root: isRoot,
+ use: true,
+ mount: false,
+ star: isStar,
+ root: isRoot,
+
+ // Path data
path: prefix,
+
+ // Group data
+ group: nil,
+
// Public data
- Method: MethodGet,
Path: prefix,
+ Method: MethodGet,
Handlers: []Handler{handler},
}
// Increment global handler count
@@ -444,13 +447,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) {
app.addRoute(MethodHead, &route)
}
-func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
- // Check mounted routes
- var mounted bool
- if len(isMounted) > 0 {
- mounted = isMounted[0]
- }
-
+func (app *App) addRoute(method string, route *Route) {
// Get unique HTTP method identifier
m := app.methodInt(method)
@@ -460,8 +457,6 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
preRoute := app.stack[m][l-1]
preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
} else {
- // Increment global route position
- route.pos = atomic.AddUint32(&app.routesCount, 1)
route.Method = method
// Add route to the stack
app.stack[m] = append(app.stack[m], route)
@@ -469,7 +464,7 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
}
// Execute onRoute hooks & change latestRoute if not adding mounted route
- if !mounted {
+ if !route.mount {
app.mutex.Lock()
app.latestRoute = route
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
@@ -481,38 +476,59 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
// buildTree build the prefix tree from the previously registered routes
func (app *App) buildTree() *App {
+ // If routes haven't been refreshed, nothing to do
if !app.routesRefreshed {
return app
}
- // loop all the methods and stacks and create the prefix tree
- for m := range app.config.RequestMethods {
- tsMap := make(map[string][]*Route)
- for _, route := range app.stack[m] {
- treePath := ""
+ // 1) First loop: determine all possible 3-char prefixes ("treePaths") for each method
+ for method := range app.config.RequestMethods {
+ prefixSet := map[string]struct{}{
+ "": {},
+ }
+ for _, route := range app.stack[method] {
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
- treePath = route.routeParser.segs[0].Const[:3]
+ prefix := route.routeParser.segs[0].Const[:3]
+ prefixSet[prefix] = struct{}{}
}
- // create tree stack
- tsMap[treePath] = append(tsMap[treePath], route)
}
- app.treeStack[m] = tsMap
- }
+ tsMap := make(map[string][]*Route, len(prefixSet))
+ for prefix := range prefixSet {
+ tsMap[prefix] = nil
+ }
+ app.treeStack[method] = tsMap
+ }
+
+ // 2) Second loop: for each method and each discovered treePath, assign matching routes
+ for method := range app.config.RequestMethods {
+ // get the map of buckets for this method
+ tsMap := app.treeStack[method]
+
+ // for every treePath key (including the empty one)
+ for treePath := range tsMap {
+ // iterate all routes of this method
+ for _, route := range app.stack[method] {
+ // compute this route's own prefix ("" or first 3 chars)
+ routePath := ""
+ if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
+ routePath = route.routeParser.segs[0].Const[:3]
+ }
- // loop the methods and tree stacks and add global stack and sort everything
- for m := range app.config.RequestMethods {
- tsMap := app.treeStack[m]
- for treePart := range tsMap {
- if treePart != "" {
- // merge global tree routes in current tree stack
- tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[""]...))
+ // if it's a global route, assign to every bucket
+ if routePath == "" {
+ tsMap[treePath] = append(tsMap[treePath], route)
+ // otherwise only assign if this route's prefix matches the current bucket's key
+ } else if routePath == treePath {
+ tsMap[treePath] = append(tsMap[treePath], route)
+ }
}
- // sort tree slices with the positions
- slc := tsMap[treePart]
- sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
+
+ // after collecting, dedupe the bucket if it's not the global one
+ tsMap[treePath] = uniqueRouteStack(tsMap[treePath])
}
}
- app.routesRefreshed = false
+ // reset the flag and return
+ app.routesRefreshed = false
return app
}
diff --git a/router_test.go b/router_test.go
index 8d1e40cbd0b..2c4424f80d5 100644
--- a/router_test.go
+++ b/router_test.go
@@ -20,7 +20,10 @@ import (
"github.com/valyala/fasthttp"
)
-var routesFixture routeJSON
+var (
+ routesFixture routeJSON
+ cssDir = "./.github/testdata/fs/css"
+)
func init() {
dat, err := os.ReadFile("./.github/testdata/testRoutes.json")
@@ -354,9 +357,8 @@ func Test_Router_Handler_Catch_Error(t *testing.T) {
func Test_Route_Static_Root(t *testing.T) {
t.Parallel()
- dir := "./.github/testdata/fs/css"
app := New()
- app.Static("/", dir, Static{
+ app.Static("/", cssDir, Static{
Browse: true,
})
@@ -373,7 +375,7 @@ func Test_Route_Static_Root(t *testing.T) {
utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color"))
app = New()
- app.Static("/", dir)
+ app.Static("/", cssDir)
resp, err = app.Test(httptest.NewRequest(MethodGet, "/", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
@@ -391,9 +393,8 @@ func Test_Route_Static_Root(t *testing.T) {
func Test_Route_Static_HasPrefix(t *testing.T) {
t.Parallel()
- dir := "./.github/testdata/fs/css"
app := New()
- app.Static("/static", dir, Static{
+ app.Static("/static", cssDir, Static{
Browse: true,
})
@@ -414,7 +415,7 @@ func Test_Route_Static_HasPrefix(t *testing.T) {
utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color"))
app = New()
- app.Static("/static/", dir, Static{
+ app.Static("/static/", cssDir, Static{
Browse: true,
})
@@ -435,7 +436,7 @@ func Test_Route_Static_HasPrefix(t *testing.T) {
utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color"))
app = New()
- app.Static("/static", dir)
+ app.Static("/static", cssDir)
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
@@ -454,7 +455,7 @@ func Test_Route_Static_HasPrefix(t *testing.T) {
utils.AssertEqual(t, true, strings.Contains(app.getString(body), "color"))
app = New()
- app.Static("/static/", dir)
+ app.Static("/static/", cssDir)
resp, err = app.Test(httptest.NewRequest(MethodGet, "/static", nil))
utils.AssertEqual(t, nil, err, "app.Test(req)")
@@ -474,6 +475,8 @@ func Test_Route_Static_HasPrefix(t *testing.T) {
}
func Test_Router_NotFound(t *testing.T) {
+ t.Parallel()
+
app := New()
app.Use(func(c *Ctx) error {
return c.Next()
@@ -491,6 +494,8 @@ func Test_Router_NotFound(t *testing.T) {
}
func Test_Router_NotFound_HTML_Inject(t *testing.T) {
+ t.Parallel()
+
app := New()
app.Use(func(c *Ctx) error {
return c.Next()
@@ -507,6 +512,31 @@ func Test_Router_NotFound_HTML_Inject(t *testing.T) {
utils.AssertEqual(t, "Cannot DELETE /does/not/exist<script>alert('foo');</script>", string(c.Response.Body()))
}
+func Test_Router_Mount_n_Static(t *testing.T) {
+ t.Parallel()
+
+ app := New()
+
+ app.Static("/static", cssDir, Static{Browse: true})
+ app.Get("/", func(c *Ctx) error {
+ return c.SendString("Home")
+ })
+
+ subApp := New()
+ app.Mount("/mount", subApp)
+ subApp.Get("/test", func(c *Ctx) error {
+ return c.SendString("Hello from /test")
+ })
+
+ app.Use(func(c *Ctx) error {
+ return c.Status(StatusNotFound).SendString("Not Found")
+ })
+
+ resp, err := app.Test(httptest.NewRequest(MethodGet, "/static/style.css", nil))
+ utils.AssertEqual(t, nil, err, "app.Test(req)")
+ utils.AssertEqual(t, 200, resp.StatusCode, "Status code")
+}
+
//////////////////////////////////////////////
///////////////// BENCHMARKS /////////////////
//////////////////////////////////////////////
From 7db10b6976b70f3fa780cffc9417923265bdafd2 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Mon, 19 May 2025 05:31:20 -0400
Subject: [PATCH 22/31] docs: Add AGENTS.md (#3461)
Create AGENTS.md
---
AGENTS.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 AGENTS.md
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000000..241f7beb7b4
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,21 @@
+# AGENTS.md
+
+## Startup script
+
+- Fetch dependencies:
+
+ ```bash
+ go get ./...
+ ```
+
+- Install CLI tools referenced in Makefile:
+
+ ```bash
+ go install gotest.tools/gotestsum@latest # test runner
+ go install golang.org/x/vuln/cmd/govulncheck@latest # vulnerability scanner
+ go install mvdan.cc/gofumpt@latest # code formatter
+ go install github.com/tinylib/msgp@latest # msgp codegen
+ go install github.com/vburenin/ifacemaker@975a95966976eeb2d4365a7fb236e274c54da64c # interface impls
+ go install github.com/dkorunic/betteralign/cmd/betteralign@latest # struct alignment
+ go mod tidy # clean up go.mod & go.sum
+ ```
From d15355116c37102bd9a8bcc252e3e3e399671af7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9?=
Date: Tue, 20 May 2025 07:29:08 +0200
Subject: [PATCH 23/31] Update AGENTS.md
---
AGENTS.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/AGENTS.md b/AGENTS.md
index 241f7beb7b4..580bf1fa527 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,5 +1,11 @@
# AGENTS.md
+## Agent Instructions
+
+> **Note for the AI agent:** Before running the startup script, check for a `vendor` directory. If it exists, skip fetching modules; otherwise proceed. This vendor-check logic is for the agent’s decision-making and is not part of the user-facing script.
+
+---
+
## Startup script
- Fetch dependencies:
From e115c08b8f059a4a031b492aa9eef0712411853d Mon Sep 17 00:00:00 2001
From: RW
Date: Thu, 22 May 2025 08:45:40 +0200
Subject: [PATCH 24/31] Merge commit from fork
* BodyParser: slice/array invalid range
- add test case
* BodyParser: slice/array invalid range
---
ctx_test.go | 25 +++++++++++++++++++++++++
internal/schema/decoder.go | 15 ++++++++++++++-
2 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/ctx_test.go b/ctx_test.go
index c1602abe9c9..6042fcff075 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -654,6 +654,31 @@ func Test_Ctx_BodyParser(t *testing.T) {
})
}
+func Test_Ctx_BodyParser_InvalidRequestData(t *testing.T) {
+ t.Parallel()
+
+ type RequestBody struct {
+ NestedContent []*struct {
+ Value string `form:"value"`
+ } `form:"nested-content"`
+ }
+ app := New()
+ c := app.AcquireCtx(&fasthttp.RequestCtx{})
+ defer app.ReleaseCtx(c)
+
+ c.Request().Reset()
+ c.Request().Header.SetContentType(MIMEApplicationForm)
+ // Test with invalid form data
+ c.Request().SetBody([]byte("nested-content[-1].value=Foo&nested-content[0].value=Bar&nested-content[1].value=FooBar"))
+ c.Request().Header.SetContentLength(len(c.Body()))
+
+ subject := new(RequestBody)
+ err := c.BodyParser(subject)
+
+ utils.AssertEqual(t, true, nil != err)
+ utils.AssertEqual(t, "failed to decode: schema: panic while decoding: reflect: slice index out of range", fmt.Sprintf("%v", err))
+}
+
func Test_Ctx_ParamParser(t *testing.T) {
t.Parallel()
app := New()
diff --git a/internal/schema/decoder.go b/internal/schema/decoder.go
index b63c45e1dcd..1f5d208e9d0 100644
--- a/internal/schema/decoder.go
+++ b/internal/schema/decoder.go
@@ -67,11 +67,24 @@ func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter)
// Keys are "paths" in dotted notation to the struct fields and nested structs.
//
// See the package documentation for a full explanation of the mechanics.
-func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
+func (d *Decoder) Decode(dst interface{}, src map[string][]string) (err error) {
v := reflect.ValueOf(dst)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return errors.New("schema: interface must be a pointer to struct")
}
+
+ // Catch panics from the decoder and return them as an error.
+ // This is needed because the decoder calls reflect and reflect panics
+ defer func() {
+ if r := recover(); r != nil {
+ if e, ok := r.(error); ok {
+ err = e
+ } else {
+ err = fmt.Errorf("schema: panic while decoding: %v", r)
+ }
+ }
+ }()
+
v = v.Elem()
t := v.Type()
multiError := MultiError{}
From 22c3c0ced919f73309018b7a7d27b029bad3e4b4 Mon Sep 17 00:00:00 2001
From: RW
Date: Thu, 22 May 2025 09:00:28 +0200
Subject: [PATCH 25/31] Update app.go
---
app.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app.go b/app.go
index 3c1cc3cc986..a11850e5fa9 100644
--- a/app.go
+++ b/app.go
@@ -30,7 +30,7 @@ import (
)
// Version of current fiber package
-const Version = "2.52.6"
+const Version = "2.52.8"
// Handler defines a function to serve HTTP requests.
type Handler = func(*Ctx) error
From b6f077275f12d25e0a4aecf59d01d77be8005ee8 Mon Sep 17 00:00:00 2001
From: RW
Date: Mon, 26 May 2025 19:48:06 +0200
Subject: [PATCH 26/31] Update AGENTS.md
---
AGENTS.md | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 580bf1fa527..4b23d8d8704 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,16 +2,16 @@
## Agent Instructions
-> **Note for the AI agent:** Before running the startup script, check for a `vendor` directory. If it exists, skip fetching modules; otherwise proceed. This vendor-check logic is for the agent’s decision-making and is not part of the user-facing script.
+> **Note for the AI agent:** Before running the startup script, verify that all dependencies and CLI tools are already available. If a `vendor/` directory exists **or** `go list -m -mod=readonly all` succeeds (indicating modules are cached), you may skip the entire startup script—including all `go mod` commands and `go install` steps. Otherwise run the script as listed below.
---
-## Startup script
+## Startup script (reference only – do not run)
- Fetch dependencies:
```bash
- go get ./...
+ go mod tidy && go mod download && go mod vendor
```
- Install CLI tools referenced in Makefile:
@@ -25,3 +25,20 @@
go install github.com/dkorunic/betteralign/cmd/betteralign@latest # struct alignment
go mod tidy # clean up go.mod & go.sum
```
+
+## Makefile commands
+
+Use `make help` to list all available commands. Common targets include:
+
+- **audit**: run `go mod verify`, `go vet`, and `govulncheck` for quality checks.
+- **benchmark**: run benchmarks with `go test`.
+- **coverage**: generate a coverage report.
+- **format**: apply formatting using `gofumpt`.
+- **lint**: execute `golangci-lint`.
+- **test**: run the test suite with `gotestsum`.
+- **longtest**: run the test suite 15 times with shuffling enabled.
+- **tidy**: clean and tidy dependencies.
+- **betteralign**: optimize struct field alignment.
+- **generate**: run `go generate` after installing msgp and ifacemaker.
+
+These targets can be invoked via `make ` as needed during development and testing.
From 40d14a9c717a3db1222ba480b9a26cddd6cd231b Mon Sep 17 00:00:00 2001
From: RW
Date: Mon, 26 May 2025 20:22:44 +0200
Subject: [PATCH 27/31] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Embedded=20struct?=
=?UTF-8?q?=20parsing=20(#3478)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Fix "[Bug]: Incorrect Parsing of Slice by QueryParser() with Embedded Structs #2859"
* Fix "[Bug]: Incorrect Parsing of Slice by QueryParser() with Embedded Structs #2859"
---
ctx.go | 41 +++++++++++++++++++++--------------------
ctx_test.go | 41 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 62 insertions(+), 20 deletions(-)
diff --git a/ctx.go b/ctx.go
index 97dfe9cf2b2..06145c5c46d 100644
--- a/ctx.go
+++ b/ctx.go
@@ -1326,43 +1326,44 @@ func (*Ctx) parseToStruct(aliasTag string, out interface{}, data map[string][]st
}
func equalFieldType(out interface{}, kind reflect.Kind, key, tag string) bool {
- // Get type of interface
outTyp := reflect.TypeOf(out).Elem()
- key = utils.ToLower(key)
- // Must be a struct to match a field
if outTyp.Kind() != reflect.Struct {
return false
}
- // Copy interface to an value to be used
- outVal := reflect.ValueOf(out).Elem()
- // Loop over each field
+ key = utils.ToLower(key)
+ return checkEqualFieldType(outTyp, kind, key, tag)
+}
+
+func checkEqualFieldType(outTyp reflect.Type, kind reflect.Kind, key, tag string) bool {
for i := 0; i < outTyp.NumField(); i++ {
- // Get field value data
- structField := outVal.Field(i)
- // Can this field be changed?
- if !structField.CanSet() {
+ typeField := outTyp.Field(i)
+
+ if typeField.Anonymous && typeField.Type.Kind() == reflect.Struct {
+ if checkEqualFieldType(typeField.Type, kind, key, tag) {
+ return true
+ }
+ }
+
+ if typeField.PkgPath != "" { // unexported field
continue
}
- // Get field key data
- typeField := outTyp.Field(i)
- // Get type of field key
- structFieldKind := structField.Kind()
- // Does the field type equals input?
- if structFieldKind != kind {
+
+ if typeField.Type.Kind() != kind {
continue
}
- // Get tag from field if exist
+
inputFieldName := typeField.Tag.Get(tag)
if inputFieldName == "" {
inputFieldName = typeField.Name
- } else {
- inputFieldName = strings.Split(inputFieldName, ",")[0]
+ } else if idx := strings.IndexByte(inputFieldName, ','); idx > -1 {
+ inputFieldName = inputFieldName[:idx]
}
- // Compare field/tag with provided key
+
if utils.ToLower(inputFieldName) == key {
return true
}
}
+
return false
}
diff --git a/ctx_test.go b/ctx_test.go
index 6042fcff075..53369a3dc89 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -4875,6 +4875,28 @@ func Test_Ctx_QueryParser_Schema(t *testing.T) {
utils.AssertEqual(t, 12, cq.Data[1].Age)
}
+func Test_Ctx_QueryParser_EmbeddedStruct(t *testing.T) {
+ t.Parallel()
+ app := New(Config{EnableSplittingOnParsers: true})
+ c := app.AcquireCtx(&fasthttp.RequestCtx{})
+ defer app.ReleaseCtx(c)
+
+ type A struct {
+ Products []string `query:"products"`
+ }
+
+ type Person struct {
+ Name string `query:"name"`
+ Pass string `query:"pass"`
+ A
+ }
+
+ c.Request().URI().SetQueryString("name=john&pass=doe&products=shoe,hat")
+ p := new(Person)
+ utils.AssertEqual(t, nil, c.QueryParser(p))
+ utils.AssertEqual(t, []string{"shoe", "hat"}, p.Products)
+}
+
// go test -run Test_Ctx_ReqHeaderParser -v
func Test_Ctx_ReqHeaderParser(t *testing.T) {
t.Parallel()
@@ -5402,6 +5424,25 @@ func Benchmark_Ctx_ReqHeaderParser(b *testing.B) {
utils.AssertEqual(b, nil, c.ReqHeaderParser(q))
}
+// go test -v -run=^$ -bench=Benchmark_equalFieldType -benchmem -count=4
+func Benchmark_equalFieldType(b *testing.B) {
+ type user struct {
+ Name string `query:"name"`
+ Age int `query:"age"`
+ }
+
+ u := new(user)
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ var res bool
+ for n := 0; n < b.N; n++ {
+ res = equalFieldType(u, reflect.String, "name", queryTag)
+ }
+
+ utils.AssertEqual(b, true, res)
+}
+
// go test -run Test_Ctx_BodyStreamWriter
func Test_Ctx_BodyStreamWriter(t *testing.T) {
t.Parallel()
From 1c037c4900cf87dd110a69e4f0b542f64ecb84b3 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Tue, 10 Jun 2025 02:45:49 -0400
Subject: [PATCH 28/31] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20Add=20upper=20ind?=
=?UTF-8?q?ex=20limit=20for=20parsers=20(#3503)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* add error for index overflow
* Update ctx_test.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update ctx_test.go
* Update ctx_test.go
* Update ctx_test.go
* Fix proxy tests and update parser index limit
* Fix proxy tests and enforce parser index limit
* test: fix index limit and proxy tests
* Fix proxy test helpers
* Update proxy_test.go
* fix index overflow error handling
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
ctx_test.go | 24 +++++++
internal/schema/cache.go | 10 ++-
internal/schema/decoder.go | 8 ++-
middleware/proxy/proxy_test.go | 122 +++++++++++++++++++++++----------
4 files changed, 125 insertions(+), 39 deletions(-)
diff --git a/ctx_test.go b/ctx_test.go
index 53369a3dc89..980f89e694e 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -679,6 +679,30 @@ func Test_Ctx_BodyParser_InvalidRequestData(t *testing.T) {
utils.AssertEqual(t, "failed to decode: schema: panic while decoding: reflect: slice index out of range", fmt.Sprintf("%v", err))
}
+func Test_Ctx_BodyParser_IndexTooLarge(t *testing.T) {
+ defer SetParserDecoder(ParserConfig{IgnoreUnknownKeys: true})
+ SetParserDecoder(ParserConfig{IgnoreUnknownKeys: false})
+ type RequestBody struct {
+ NestedContent []*struct {
+ Value string `form:"value"`
+ } `form:"nested-content"`
+ }
+ app := New()
+ c := app.AcquireCtx(&fasthttp.RequestCtx{})
+ defer app.ReleaseCtx(c)
+
+ c.Request().Reset()
+ c.Request().Header.SetContentType(MIMEApplicationForm)
+ c.Request().SetBody([]byte("nested-content.1001.value=Foo"))
+ c.Request().Header.SetContentLength(len(c.Body()))
+
+ subject := new(RequestBody)
+ err := c.BodyParser(subject)
+
+ utils.AssertEqual(t, true, nil != err)
+ utils.AssertEqual(t, "failed to decode: schema: index exceeds parser limit", fmt.Sprintf("%v", err))
+}
+
func Test_Ctx_ParamParser(t *testing.T) {
t.Parallel()
app := New()
diff --git a/internal/schema/cache.go b/internal/schema/cache.go
index bf21697cf19..9cfd503fbbc 100644
--- a/internal/schema/cache.go
+++ b/internal/schema/cache.go
@@ -12,7 +12,12 @@ import (
"sync"
)
-var errInvalidPath = errors.New("schema: invalid path")
+const maxParserIndex = 1000
+
+var (
+ errInvalidPath = errors.New("schema: invalid path")
+ errIndexTooLarge = errors.New("schema: index exceeds parser limit")
+)
// newCache returns a new cache.
func newCache() *cache {
@@ -77,6 +82,9 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
return nil, errInvalidPath
}
+ if index64 > maxParserIndex {
+ return nil, errIndexTooLarge
+ }
parts = append(parts, pathPart{
path: path,
field: field,
diff --git a/internal/schema/decoder.go b/internal/schema/decoder.go
index 1f5d208e9d0..586e4d70598 100644
--- a/internal/schema/decoder.go
+++ b/internal/schema/decoder.go
@@ -93,8 +93,12 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) (err error) {
if err = d.decode(v, path, parts, values); err != nil {
multiError[path] = err
}
- } else if !d.ignoreUnknownKeys {
- multiError[path] = UnknownKeyError{Key: path}
+ } else {
+ if errors.Is(err, errIndexTooLarge) {
+ multiError[path] = err
+ } else if !d.ignoreUnknownKeys {
+ multiError[path] = UnknownKeyError{Key: path}
+ }
}
}
multiError.merge(d.checkRequired(t, src))
diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go
index 49be2a2c958..13c71e58581 100644
--- a/middleware/proxy/proxy_test.go
+++ b/middleware/proxy/proxy_test.go
@@ -17,25 +17,60 @@ import (
"github.com/valyala/fasthttp"
)
-func createProxyTestServer(t *testing.T, handler fiber.Handler) (*fiber.App, string) {
+func startServer(t *testing.T, app *fiber.App, ln net.Listener) {
t.Helper()
+ go func() {
+ utils.AssertEqual(t, nil, app.Listener(ln))
+ }()
+ time.Sleep(200 * time.Millisecond)
+}
- target := fiber.New(fiber.Config{DisableStartupMessage: true})
+func createProxyTestServer(t *testing.T, handler fiber.Handler, network, address string) (*fiber.App, string) {
+ t.Helper()
+
+ target := fiber.New()
target.Get("/", handler)
- ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0")
+ ln, err := net.Listen(network, address)
utils.AssertEqual(t, nil, err)
- go func() {
- utils.AssertEqual(t, nil, target.Listener(ln))
- }()
-
- time.Sleep(2 * time.Second)
addr := ln.Addr().String()
+ startServer(t, target, ln)
+
return target, addr
}
+func createProxyTestServerIPv4(t *testing.T, handler fiber.Handler) (*fiber.App, string) {
+ t.Helper()
+ return createProxyTestServer(t, handler, fiber.NetworkTCP4, "127.0.0.1:0")
+}
+
+// createRedirectServer creates a simple redirecting server used in tests.
+//
+//nolint:unparam // this is only for testing
+func createRedirectServer(t *testing.T) (*fiber.App, string) {
+ t.Helper()
+ app := fiber.New()
+
+ var addr string
+ app.Get("/", func(c *fiber.Ctx) error {
+ c.Location("http://" + addr + "/final")
+ return c.Status(fiber.StatusMovedPermanently).SendString("redirect")
+ })
+ app.Get("/final", func(c *fiber.Ctx) error {
+ return c.SendString("final")
+ })
+
+ ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0")
+ utils.AssertEqual(t, nil, err)
+ addr = ln.Addr().String()
+
+ startServer(t, app, ln)
+
+ return app, addr
+}
+
// go test -run Test_Proxy_Empty_Host
func Test_Proxy_Empty_Upstream_Servers(t *testing.T) {
t.Parallel()
@@ -83,7 +118,7 @@ func Test_Proxy_Next(t *testing.T) {
func Test_Proxy(t *testing.T) {
t.Parallel()
- target, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ target, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusTeapot)
})
@@ -142,7 +177,7 @@ func Test_Proxy_Balancer_WithTlsConfig(t *testing.T) {
func Test_Proxy_Forward_WithTlsConfig_To_Http(t *testing.T) {
t.Parallel()
- _, targetAddr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, targetAddr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendString("hello from target")
})
@@ -178,7 +213,7 @@ func Test_Proxy_Forward(t *testing.T) {
app := fiber.New()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendString("forwarded")
})
@@ -231,7 +266,7 @@ func Test_Proxy_Forward_WithTlsConfig(t *testing.T) {
func Test_Proxy_Modify_Response(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.Status(500).SendString("not modified")
})
@@ -257,7 +292,7 @@ func Test_Proxy_Modify_Response(t *testing.T) {
func Test_Proxy_Modify_Request(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
b := c.Request().Body()
return c.SendString(string(b))
})
@@ -284,7 +319,7 @@ func Test_Proxy_Modify_Request(t *testing.T) {
func Test_Proxy_Timeout_Slow_Server(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
time.Sleep(2 * time.Second)
return c.SendString("fiber is awesome")
})
@@ -308,7 +343,7 @@ func Test_Proxy_Timeout_Slow_Server(t *testing.T) {
func Test_Proxy_With_Timeout(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
time.Sleep(1 * time.Second)
return c.SendString("fiber is awesome")
})
@@ -332,7 +367,7 @@ func Test_Proxy_With_Timeout(t *testing.T) {
func Test_Proxy_Buffer_Size_Response(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
long := strings.Join(make([]string, 5000), "-")
c.Set("Very-Long-Header", long)
return c.SendString("ok")
@@ -359,7 +394,7 @@ func Test_Proxy_Buffer_Size_Response(t *testing.T) {
// go test -race -run Test_Proxy_Do_RestoreOriginalURL
func Test_Proxy_Do_RestoreOriginalURL(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendString("proxied")
})
@@ -379,48 +414,60 @@ func Test_Proxy_Do_RestoreOriginalURL(t *testing.T) {
// go test -race -run Test_Proxy_Do_WithRealURL
func Test_Proxy_Do_WithRealURL(t *testing.T) {
t.Parallel()
+
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
+ return c.SendString("real url")
+ })
+
app := fiber.New()
app.Get("/test", func(c *fiber.Ctx) error {
- return Do(c, "https://www.google.com")
+ return Do(c, "http://"+addr)
})
- resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
+ resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), 2000)
utils.AssertEqual(t, nil, err1)
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
utils.AssertEqual(t, "/test", resp.Request.URL.String())
body, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
- utils.AssertEqual(t, true, strings.Contains(string(body), "https://www.google.com/"))
+ utils.AssertEqual(t, "real url", string(body))
}
// go test -race -run Test_Proxy_Do_WithRedirect
func Test_Proxy_Do_WithRedirect(t *testing.T) {
t.Parallel()
+
+ _, addr := createRedirectServer(t)
+
app := fiber.New()
app.Get("/test", func(c *fiber.Ctx) error {
- return Do(c, "https://google.com")
+ return Do(c, "http://"+addr)
})
- resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
+ resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), 2000)
utils.AssertEqual(t, nil, err1)
body, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
- utils.AssertEqual(t, true, strings.Contains(string(body), "https://www.google.com/"))
- utils.AssertEqual(t, 301, resp.StatusCode)
+ utils.AssertEqual(t, "redirect", string(body))
+ utils.AssertEqual(t, fiber.StatusMovedPermanently, resp.StatusCode)
}
// go test -race -run Test_Proxy_DoRedirects_RestoreOriginalURL
func Test_Proxy_DoRedirects_RestoreOriginalURL(t *testing.T) {
t.Parallel()
+
+ _, addr := createRedirectServer(t)
+
app := fiber.New()
app.Get("/test", func(c *fiber.Ctx) error {
- return DoRedirects(c, "http://google.com", 1)
+ return DoRedirects(c, "http://"+addr, 1)
})
- resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
+ resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), 2000)
utils.AssertEqual(t, nil, err1)
- _, err := io.ReadAll(resp.Body)
+ body, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
+ utils.AssertEqual(t, "final", string(body))
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
utils.AssertEqual(t, "/test", resp.Request.URL.String())
}
@@ -428,12 +475,15 @@ func Test_Proxy_DoRedirects_RestoreOriginalURL(t *testing.T) {
// go test -race -run Test_Proxy_DoRedirects_TooManyRedirects
func Test_Proxy_DoRedirects_TooManyRedirects(t *testing.T) {
t.Parallel()
+
+ _, addr := createRedirectServer(t)
+
app := fiber.New()
app.Get("/test", func(c *fiber.Ctx) error {
- return DoRedirects(c, "http://google.com", 0)
+ return DoRedirects(c, "http://"+addr, 0)
})
- resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil))
+ resp, err1 := app.Test(httptest.NewRequest(fiber.MethodGet, "/test", nil), 2000)
utils.AssertEqual(t, nil, err1)
body, err := io.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err)
@@ -446,7 +496,7 @@ func Test_Proxy_DoRedirects_TooManyRedirects(t *testing.T) {
func Test_Proxy_DoTimeout_RestoreOriginalURL(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendString("proxied")
})
@@ -468,7 +518,7 @@ func Test_Proxy_DoTimeout_RestoreOriginalURL(t *testing.T) {
func Test_Proxy_DoTimeout_Timeout(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
time.Sleep(time.Second * 5)
return c.SendString("proxied")
})
@@ -486,7 +536,7 @@ func Test_Proxy_DoTimeout_Timeout(t *testing.T) {
func Test_Proxy_DoDeadline_RestoreOriginalURL(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendString("proxied")
})
@@ -508,7 +558,7 @@ func Test_Proxy_DoDeadline_RestoreOriginalURL(t *testing.T) {
func Test_Proxy_DoDeadline_PastDeadline(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
time.Sleep(time.Second * 5)
return c.SendString("proxied")
})
@@ -526,7 +576,7 @@ func Test_Proxy_DoDeadline_PastDeadline(t *testing.T) {
func Test_Proxy_Do_HTTP_Prefix_URL(t *testing.T) {
t.Parallel()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendString("hello world")
})
@@ -603,7 +653,7 @@ func Test_Proxy_Forward_Local_Client(t *testing.T) {
func Test_ProxyBalancer_Custom_Client(t *testing.T) {
t.Parallel()
- target, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ target, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusTeapot)
})
@@ -672,7 +722,7 @@ func Test_Proxy_Balancer_Forward_Local(t *testing.T) {
app := fiber.New()
- _, addr := createProxyTestServer(t, func(c *fiber.Ctx) error {
+ _, addr := createProxyTestServerIPv4(t, func(c *fiber.Ctx) error {
return c.SendString("forwarded")
})
From 845f95f441718b1be1cf228f879f0a761118f317 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Mon, 23 Jun 2025 02:04:26 -0400
Subject: [PATCH 29/31] =?UTF-8?q?=F0=9F=90=9B=20bug:=20Fix=20Content-Type?=
=?UTF-8?q?=20comparison=20in=20Is()=20(#3537)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: improve Is content type matching
* Update ctx.go
---------
Co-authored-by: RW
---
ctx.go | 10 ++++++----
ctx_test.go | 10 ++++++++++
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/ctx.go b/ctx.go
index 06145c5c46d..06ab0ae1d38 100644
--- a/ctx.go
+++ b/ctx.go
@@ -877,10 +877,12 @@ func (c *Ctx) Is(extension string) bool {
return false
}
- return strings.HasPrefix(
- utils.TrimLeft(c.app.getString(c.fasthttp.Request.Header.ContentType()), ' '),
- extensionHeader,
- )
+ ct := c.app.getString(c.fasthttp.Request.Header.ContentType())
+ if i := strings.IndexByte(ct, ';'); i != -1 {
+ ct = ct[:i]
+ }
+ ct = utils.Trim(ct, ' ')
+ return utils.EqualFold(ct, extensionHeader)
}
// JSON converts any interface or string to JSON.
diff --git a/ctx_test.go b/ctx_test.go
index 980f89e694e..af08c4eeaf9 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -2091,6 +2091,16 @@ func Test_Ctx_Is(t *testing.T) {
utils.AssertEqual(t, false, c.Is("html"))
utils.AssertEqual(t, true, c.Is("txt"))
utils.AssertEqual(t, true, c.Is(".txt"))
+
+ // case-insensitive and trimmed
+ c.Request().Header.Set(HeaderContentType, "APPLICATION/JSON; charset=utf-8")
+ utils.AssertEqual(t, true, c.Is("json"))
+ utils.AssertEqual(t, true, c.Is(".json"))
+
+ // mismatched subtype should not match
+ c.Request().Header.Set(HeaderContentType, "application/json+xml")
+ utils.AssertEqual(t, false, c.Is("json"))
+ utils.AssertEqual(t, false, c.Is(".json"))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Is -benchmem -count=4
From b60408c9bde7e71faac0519aa680a6fb8d64a255 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Sun, 20 Jul 2025 08:54:11 -0400
Subject: [PATCH 30/31] =?UTF-8?q?=F0=9F=90=9B=20bug:=20Fix=20MIME=20type?=
=?UTF-8?q?=20equality=20checks=20(#3603)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ctx.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/ctx.go b/ctx.go
index 06ab0ae1d38..c42e6e6668b 100644
--- a/ctx.go
+++ b/ctx.go
@@ -394,7 +394,7 @@ func (c *Ctx) BodyParser(out interface{}) error {
if strings.HasSuffix(ctype, "json") {
return c.app.config.JSONDecoder(c.Body(), out)
}
- if strings.HasPrefix(ctype, MIMEApplicationForm) {
+ if ctype == MIMEApplicationForm {
data := make(map[string][]string)
var err error
@@ -415,7 +415,7 @@ func (c *Ctx) BodyParser(out interface{}) error {
return c.parseToStruct(bodyTag, out, data)
}
- if strings.HasPrefix(ctype, MIMEMultipartForm) {
+ if ctype == MIMEMultipartForm {
multipartForm, err := c.fasthttp.MultipartForm()
if err != nil {
return err
@@ -431,7 +431,7 @@ func (c *Ctx) BodyParser(out interface{}) error {
return c.parseToStruct(bodyTag, out, data)
}
- if strings.HasPrefix(ctype, MIMETextXML) || strings.HasPrefix(ctype, MIMEApplicationXML) {
+ if ctype == MIMETextXML || ctype == MIMEApplicationXML {
if err := xml.Unmarshal(c.Body(), out); err != nil {
return fmt.Errorf("failed to unmarshal: %w", err)
}
From 1197a22735820680ccfa241914b925f3820fcfd4 Mon Sep 17 00:00:00 2001
From: RW
Date: Sun, 20 Jul 2025 15:43:40 +0200
Subject: [PATCH 31/31] Update app.go
prepare release v2.52.9
---
app.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app.go b/app.go
index a11850e5fa9..589632ceacc 100644
--- a/app.go
+++ b/app.go
@@ -30,7 +30,7 @@ import (
)
// Version of current fiber package
-const Version = "2.52.8"
+const Version = "2.52.9"
// Handler defines a function to serve HTTP requests.
type Handler = func(*Ctx) error